<commit_before>from skbeam.core.accumulators.binned_statistic import (RadialBinnedStatistic,
                                                       RPhiBinnedStatistic,
                                                       BinnedStatistic1D,
                                                       BinnedStatisticDD)
from numpy.testing import assert_array_equal, assert_array_almost_equal, assert_raises
import numpy as np
import scipy.stats
from skbeam.core.utils import bin_edges_to_centers
from skbeam.core.utils import radial_grid, angle_grid
import pytest

stats_list = [('mean', np.mean), ('median', np.median), ('count', len),
              ('sum', np.sum), ('std', np.std)]

class TestRadialBinnedStatistic(object):
    oscillation_rate = 10.0

    def setup(self):

        # Create test image - a sinc function.
        # Integrating in phi will produce sin(x)

        self.rowsize, self.colsize = (91, 102)
        self.shape = self.rowsize, self.colsize

        rowarr = np.arange(self.rowsize)
        colarr = np.arange(self.colsize)
        self.rowgrid, self.colgrid = np.meshgrid(rowarr, colarr, indexing='ij')

    def _testRadialBinnedStatistic(self, rfac=None):
        ''' rfac : make the rgrid non-linear and supply to
            RadialBinnedStatistic.
        '''

        mykwargs = [{'origin': (0, 0),
                     'range': (10, 90)},
                    {'origin': (0, 0)},
                    {'origin': None}
                    ]
        bins, shape = 100, self.shape
        mask_ones = np.ones(self.shape)
        mask_random = np.random.randint(2, size=self.shape)

        for kwargs in mykwargs:
            for stat, stat_func in stats_list:

                if 'origin' in kwargs:
                    origin = kwargs['origin']
                else:
                    origin = None
                if origin is None:
                    origin = (self.rowsize-1)/2., (self.colsize-1)/2.

                # need to calculate these every time in loop since origin
                # changes
                # rows are y, cols are x, as in angle_grid in core.utils
                self.rgrid = np.sqrt((self.rowgrid-origin[0])**2 +
                                     (self.colgrid-origin[1])**2)
                if rfac is not None:
                    self.rgrid = self.rgrid**rfac

                self.phigrid = np.arctan2(self.rowgrid-origin[0],
                                          self.colgrid-origin[1])

                self.image = np.sinc(self.rgrid / self.oscillation_rate)

                if stat == 'sum':
                    # in this case we can compare our masked
                    # result to binned_statistic
                    mask = mask_random
                else:
                    mask = mask_ones

                # test radial case
                if rfac is None:
                    radbinstat = RadialBinnedStatistic(shape, bins,
                                                       statistic=stat,
                                                       mask=mask,
                                                       **kwargs)
                    radbinstat_f = RadialBinnedStatistic(shape, bins,
                                                         statistic=stat_func,
                                                         mask=mask,
                                                         **kwargs)
                else:
                    radbinstat = RadialBinnedStatistic(shape, bins,
                                                       statistic=stat,
                                                       mask=mask,
                                                       r_map=self.rgrid,
                                                       **kwargs)
                    radbinstat_f = RadialBinnedStatistic(shape, bins,
                                                         statistic=stat_func,
                                                         mask=mask,
                                                         r_map=self.rgrid,
                                                         **kwargs)
                binned = radbinstat(self.image)
                binned_f = radbinstat_f(self.image)

                assert_array_almost_equal(binned_f, binned)

                kwrange = kwargs.get('range', None)
                ref, edges, _ = scipy.stats.binned_statistic(
                    x=self.rgrid.ravel(),
                    values=(self.image*mask).ravel(),
                    statistic=stat,
                    range=kwrange,
                    bins=bins,
                )
                centers = bin_edges_to_centers(edges)

                assert_array_almost_equal(ref, binned)
                assert_array_almost_equal(edges, radbinstat.bin_edges)
                assert_array_almost_equal(edges, radbinstat_f.bin_edges)
                assert_array_almost_equal(centers, radbinstat.bin_centers)
                assert_array_almost_equal(centers, radbinstat_f.bin_centers)

        bins = (100, 2)
        myrphikwargs = [{'origin': (0, 0),
                         'range': ((10, 90), (0, np.pi/2))},
                        {'origin': (0, 0)},
                        {'origin': None}]
        for kwargs in myrphikwargs:
            for stat, stat_func in stats_list:
                if 'origin' in kwargs:
                    origin = kwargs['origin']
                else:
                    origin = None
                if origin is None:
                    origin = (self.rowsize-1)/2., (self.colsize-1)/2.

                # need to calculate these every time in loop since origin
                # changes
                # rows are y, cols are x, as in angle_grid in core.utils
                self.rgrid = np.sqrt((self.rowgrid-origin[0])**2 +
                                     (self.colgrid-origin[1])**2)
                self.phigrid = np.arctan2(self.rowgrid-origin[0],
                                          self.colgrid-origin[1])

                self.image = np.sinc(self.rgrid / self.oscillation_rate)

                if stat == 'sum':
                    # in this case we can compare our masked
                    # result to binned_statistic
                    mask = mask_random
                else:
                    mask = mask_ones

                # test radial case
                rphibinstat = RPhiBinnedStatistic(shape, bins,
                                                  statistic=stat,
                                                  mask=mask,
                                                  **kwargs)
                rphibinstat_f = RPhiBinnedStatistic(shape, bins,
                                                    statistic=stat_func,
                                                    mask=mask,
                                                    **kwargs)
                binned = rphibinstat(self.image)
                binned_f = rphibinstat_f(self.image)

                # this test fails only for the standard deviation where
                # there is a disagreement in the number of nan's.  I
                # don't believe this is the fault of the binned_statistic
                # code
                if stat != 'std':
                    assert_array_almost_equal(binned_f, binned)

                kwrange = kwargs.get('range', None)
                ref, redges, phiedges, _ = scipy.stats.binned_statistic_2d(
                    x=self.rgrid.ravel(),
                    y=self.phigrid.ravel(),
                    values=(self.image*mask).ravel(),
                    statistic=stat,
                    range=kwrange,
                    bins=bins,
                )

                assert_array_almost_equal(ref, binned)
                assert_array_almost_equal(redges, rphibinstat.bin_edges[0])
                assert_array_almost_equal(redges, rphibinstat_f.bin_edges[0])
                assert_array_almost_equal(phiedges, rphibinstat.bin_edges[1])
                assert_array_almost_equal(phiedges, rphibinstat_f.bin_edges[1])

        # test exception when BinnedStatistic is given array of incorrect shape
        with assert_raises(ValueError):
            radbinstat(self.image[:10, :10])

        # test exception when RadialBinnedStatistic is given 1D array
        with assert_raises(ValueError):
            RadialBinnedStatistic(self.image.shape, 10,
                                  mask=np.array([1, 2, 3, 4]))

    def test_RadialBinnedStatistic(self):
        self._testRadialBinnedStatistic()

    def test_RadialBinnedStatistic_rmap(self):
        self._testRadialBinnedStatistic(rfac=1.1)

@pytest.mark.parametrize(['stat', 'stat_f'], stats_list)
def test_BinnedStatistics1D(stat, stat_f):
    x = np.linspace(0, 2*np.pi, 100)
    values = np.sin(x * 5)

    bs = BinnedStatistic1D(x, statistic=stat, bins=10)
    bs_f = BinnedStatistic1D(x, statistic=stat_f, bins=10)

    ref, edges, _ = scipy.stats.binned_statistic(x, values,
                                                 statistic=stat, bins=10)

    assert_array_almost_equal(bs(values), ref)
    assert_array_almost_equal(bs_f(values), ref)
    assert_array_almost_equal(edges, bs.bin_edges)
    assert_array_almost_equal(edges, bs_f.bin_edges)

    rbinstat = BinnedStatistic1D(x)
    # make sure wrong shape is caught
    with assert_raises(ValueError):
        rbinstat(x[:-2])

    # try with same shape, should be fine
    rbinstat(x)

def test_binmap():
    ''' These tests can be run in notebooks and the binmaps plotted.
        If this ever returns an error, it is suggested to plot the maps and see
        if they look sensible.
    '''
    # generate fake data on the fly
    shape = np.array([100, 100])

    R = radial_grid(shape/2, shape)
    Phi = angle_grid(shape/2., shape)

    img = R*np.cos(5*Phi)
    rs = RPhiBinnedStatistic(img.shape)

    binmap = rs.binmap.reshape((-1, img.shape[0], img.shape[1]))

    assert_array_almost_equal(binmap[0][40][::10], np.array([8, 6, 5, 4, 2, 2,
                                                             2, 4, 5, 6]))

def test_binmap3d():
    ''' These tests can be run in notebooks and the binmaps plotted.
        If this ever returns an error, it is suggested to plot the maps and see
        if they look sensible.
    '''
    # test the 3d version separately
    Nx, Ny = 101, 101
    x = np.arange(Nx)-Nx/2.
    y = np.arange(Ny)-Ny/2.
    X, Y = np.meshgrid(x, y)
    X = X.ravel()
    Y = Y.ravel()
    # choose the Z variation as some arbitrary value
    Z = np.hypot(X, Y)
    # d arrays lenghtn or array length N, D array
    # try both versions of input either:
    # a tuple of the vals
    vals1 = [X, Y, Z]
    # or an N, D array (yes, it's transposed...)
    vals2 = np.array([X, Y, Z]).T
    binstat1 = BinnedStatisticDD(vals1, bins=(10, 10, 10))
    binstat2 = BinnedStatisticDD(vals2, bins=(10, 10, 10))

    rbinmap1 = binstat1.binmap
    rbinmap2 = binstat2.binmap

    assert_array_almost_equal(rbinmap1[0][::1000], rbinmap2[0][::1000])
    assert_array_almost_equal(rbinmap1[0][::1000], np.array([1, 10,  9,  8, 7,
                                                             6,  5,  4,  3, 2,
                                                             1]))
<commit_msg>Flake 8<commit_after>from skbeam.core.accumulators.binned_statistic import (RadialBinnedStatistic,
                                                       RPhiBinnedStatistic,
                                                       BinnedStatistic1D,
                                                       BinnedStatisticDD)
from numpy.testing import assert_array_almost_equal, assert_raises
import numpy as np
import scipy.stats
from skbeam.core.utils import bin_edges_to_centers
from skbeam.core.utils import radial_grid, angle_grid
import pytest

stats_list = [('mean', np.mean), ('median', np.median), ('count', len),
              ('sum', np.sum), ('std', np.std)]

class TestRadialBinnedStatistic(object):
    oscillation_rate = 10.0

    def setup(self):

        # Create test image - a sinc function.
        # Integrating in phi will produce sin(x)

        self.rowsize, self.colsize = (91, 102)
        self.shape = self.rowsize, self.colsize

        rowarr = np.arange(self.rowsize)
        colarr = np.arange(self.colsize)
        self.rowgrid, self.colgrid = np.meshgrid(rowarr, colarr, indexing='ij')

    def _testRadialBinnedStatistic(self, rfac=None):
        ''' rfac : make the rgrid non-linear and supply to
            RadialBinnedStatistic.
        '''

        mykwargs = [{'origin': (0, 0),
                     'range': (10, 90)},
                    {'origin': (0, 0)},
                    {'origin': None}
                    ]
        bins, shape = 100, self.shape
        mask_ones = np.ones(self.shape)
        mask_random = np.random.randint(2, size=self.shape)

        for kwargs in mykwargs:
            for stat, stat_func in stats_list:

                if 'origin' in kwargs:
                    origin = kwargs['origin']
                else:
                    origin = None
                if origin is None:
                    origin = (self.rowsize-1)/2., (self.colsize-1)/2.

                # need to calculate these every time in loop since origin
                # changes
                # rows are y, cols are x, as in angle_grid in core.utils
                self.rgrid = np.sqrt((self.rowgrid-origin[0])**2 +
                                     (self.colgrid-origin[1])**2)
                if rfac is not None:
                    self.rgrid = self.rgrid**rfac

                self.phigrid = np.arctan2(self.rowgrid-origin[0],
                                          self.colgrid-origin[1])

                self.image = np.sinc(self.rgrid / self.oscillation_rate)

                if stat == 'sum':
                    # in this case we can compare our masked
                    # result to binned_statistic
                    mask = mask_random
                else:
                    mask = mask_ones

                # test radial case
                if rfac is None:
                    radbinstat = RadialBinnedStatistic(shape, bins,
                                                       statistic=stat,
                                                       mask=mask,
                                                       **kwargs)
                    radbinstat_f = RadialBinnedStatistic(shape, bins,
                                                         statistic=stat_func,
                                                         mask=mask,
                                                         **kwargs)
                else:
                    radbinstat = RadialBinnedStatistic(shape, bins,
                                                       statistic=stat,
                                                       mask=mask,
                                                       r_map=self.rgrid,
                                                       **kwargs)
                    radbinstat_f = RadialBinnedStatistic(shape, bins,
                                                         statistic=stat_func,
                                                         mask=mask,
                                                         r_map=self.rgrid,
                                                         **kwargs)
                binned = radbinstat(self.image)
                binned_f = radbinstat_f(self.image)

                assert_array_almost_equal(binned_f, binned)

                kwrange = kwargs.get('range', None)
                ref, edges, _ = scipy.stats.binned_statistic(
                    x=self.rgrid.ravel(),
                    values=(self.image*mask).ravel(),
                    statistic=stat,
                    range=kwrange,
                    bins=bins,
                )
                centers = bin_edges_to_centers(edges)

                assert_array_almost_equal(ref, binned)
                assert_array_almost_equal(edges, radbinstat.bin_edges)
                assert_array_almost_equal(edges, radbinstat_f.bin_edges)
                assert_array_almost_equal(centers, radbinstat.bin_centers)
                assert_array_almost_equal(centers, radbinstat_f.bin_centers)

        bins = (100, 2)
        myrphikwargs = [{'origin': (0, 0),
                         'range': ((10, 90), (0, np.pi/2))},
                        {'origin': (0, 0)},
                        {'origin': None}]
        for kwargs in myrphikwargs:
            for stat, stat_func in stats_list:
                if 'origin' in kwargs:
                    origin = kwargs['origin']
                else:
                    origin = None
                if origin is None:
                    origin = (self.rowsize-1)/2., (self.colsize-1)/2.

                # need to calculate these every time in loop since origin
                # changes
                # rows are y, cols are x, as in angle_grid in core.utils
                self.rgrid = np.sqrt((self.rowgrid-origin[0])**2 +
                                     (self.colgrid-origin[1])**2)
                self.phigrid = np.arctan2(self.rowgrid-origin[0],
                                          self.colgrid-origin[1])

                self.image = np.sinc(self.rgrid / self.oscillation_rate)

                if stat == 'sum':
                    # in this case we can compare our masked
                    # result to binned_statistic
                    mask = mask_random
                else:
                    mask = mask_ones

                # test radial case
                rphibinstat = RPhiBinnedStatistic(shape, bins,
                                                  statistic=stat,
                                                  mask=mask,
                                                  **kwargs)
                rphibinstat_f = RPhiBinnedStatistic(shape, bins,
                                                    statistic=stat_func,
                                                    mask=mask,
                                                    **kwargs)
                binned = rphibinstat(self.image)
                binned_f = rphibinstat_f(self.image)

                # this test fails only for the standard deviation where
                # there is a disagreement in the number of nan's.  I
                # don't believe this is the fault of the binned_statistic
                # code
                if stat != 'std':
                    assert_array_almost_equal(binned_f, binned)

                kwrange = kwargs.get('range', None)
                ref, redges, phiedges, _ = scipy.stats.binned_statistic_2d(
                    x=self.rgrid.ravel(),
                    y=self.phigrid.ravel(),
                    values=(self.image*mask).ravel(),
                    statistic=stat,
                    range=kwrange,
                    bins=bins,
                )

                assert_array_almost_equal(ref, binned)
                assert_array_almost_equal(redges, rphibinstat.bin_edges[0])
                assert_array_almost_equal(redges, rphibinstat_f.bin_edges[0])
                assert_array_almost_equal(phiedges, rphibinstat.bin_edges[1])
                assert_array_almost_equal(phiedges, rphibinstat_f.bin_edges[1])

        # test exception when BinnedStatistic is given array of incorrect shape
        with assert_raises(ValueError):
            radbinstat(self.image[:10, :10])

        # test exception when RadialBinnedStatistic is given 1D array
        with assert_raises(ValueError):
            RadialBinnedStatistic(self.image.shape, 10,
                                  mask=np.array([1, 2, 3, 4]))

    def test_RadialBinnedStatistic(self):
        self._testRadialBinnedStatistic()

    def test_RadialBinnedStatistic_rmap(self):
        self._testRadialBinnedStatistic(rfac=1.1)

@pytest.mark.parametrize(['stat', 'stat_f'], stats_list)
def test_BinnedStatistics1D(stat, stat_f):
    x = np.linspace(0, 2*np.pi, 100)
    values = np.sin(x * 5)

    bs = BinnedStatistic1D(x, statistic=stat, bins=10)
    bs_f = BinnedStatistic1D(x, statistic=stat_f, bins=10)

    ref, edges, _ = scipy.stats.binned_statistic(x, values,
                                                 statistic=stat, bins=10)

    assert_array_almost_equal(bs(values), ref)
    assert_array_almost_equal(bs_f(values), ref)
    assert_array_almost_equal(edges, bs.bin_edges)
    assert_array_almost_equal(edges, bs_f.bin_edges)

    rbinstat = BinnedStatistic1D(x)
    # make sure wrong shape is caught
    with assert_raises(ValueError):
        rbinstat(x[:-2])

    # try with same shape, should be fine
    rbinstat(x)

def test_binmap():
    ''' These tests can be run in notebooks and the binmaps plotted.
        If this ever returns an error, it is suggested to plot the maps and see
        if they look sensible.
    '''
    # generate fake data on the fly
    shape = np.array([100, 100])

    R = radial_grid(shape/2, shape)
    Phi = angle_grid(shape/2., shape)

    img = R*np.cos(5*Phi)
    rs = RPhiBinnedStatistic(img.shape)

    binmap = rs.binmap.reshape((-1, img.shape[0], img.shape[1]))

    assert_array_almost_equal(binmap[0][40][::10], np.array([8, 6, 5, 4, 2, 2,
                                                             2, 4, 5, 6]))

def test_binmap3d():
    ''' These tests can be run in notebooks and the binmaps plotted.
        If this ever returns an error, it is suggested to plot the maps and see
        if they look sensible.
    '''
    # test the 3d version separately
    Nx, Ny = 101, 101
    x = np.arange(Nx)-Nx/2.
    y = np.arange(Ny)-Ny/2.
    X, Y = np.meshgrid(x, y)
    X = X.ravel()
    Y = Y.ravel()
    # choose the Z variation as some arbitrary value
    Z = np.hypot(X, Y)
    # d arrays lenghtn or array length N, D array
    # try both versions of input either:
    # a tuple of the vals
    vals1 = [X, Y, Z]
    # or an N, D array (yes, it's transposed...)
    vals2 = np.array([X, Y, Z]).T
    binstat1 = BinnedStatisticDD(vals1, bins=(10, 10, 10))
    binstat2 = BinnedStatisticDD(vals2, bins=(10, 10, 10))

    rbinmap1 = binstat1.binmap
    rbinmap2 = binstat2.binmap

    assert_array_almost_equal(rbinmap1[0][::1000], rbinmap2[0][::1000])
    assert_array_almost_equal(rbinmap1[0][::1000], np.array([1, 10,  9,  8, 7,
                                                             6,  5,  4,  3, 2,
                                                             1]))
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
# vim: tabstop=4 shiftwidth=4 softtabstop=4

# Copyright (c) 2010-2012, GEM Foundation.
#
# OpenQuake is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# OpenQuake is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with OpenQuake.  If not, see <http://www.gnu.org/licenses/>.

"""
Tests for the serialization of loss maps to NRML format.
"""

import os
import unittest

from lxml import etree

from openquake import nrml
from openquake import shapes
from openquake import xml
from tests.utils import helpers

from openquake.db.models import ExposureData
from openquake.output import risk as risk_output

TEST_LOSS_MAP_XML_OUTPUT_PATH = helpers.get_output_path('test-loss-map.xml')
TEST_NON_SCN_LOSS_MAP_XML_OUTPUT_PATH = helpers.get_output_path(
    'test-non-det-loss-map.xml')
EXPECTED_TEST_LOSS_MAP = helpers.get_data_path('expected-test-loss-map.xml')
EXPECTED_TEST_NON_SCN_LOSS_MAP = helpers.get_data_path(
    'expected-non-det-test-loss-map.xml')

NRML_SCHEMA_PATH = nrml.nrml_schema_file()

LOSS_MAP_METADATA = {
    'nrmlID': 'test_nrml_id',
    'riskResultID': 'test_rr_id',
    'lossMapID': 'test_lm_id',
    'endBranchLabel': 'test_ebl',
    'lossCategory': 'economic_loss',
    'unit': 'EUR'}

SITE_A = shapes.Site(-117.0, 38.0)
SITE_A_ASSET_ONE = {'assetID': 'a1711'}
SITE_A_LOSS_ONE = {'mean_loss': 0, 'stddev_loss': 100}

SITE_A_ASSET_TWO = {'assetID': 'a1712'}
SITE_A_LOSS_TWO = {'mean_loss': 5, 'stddev_loss': 2000.0}

SITE_B = shapes.Site(-118.0, 39.0)
SITE_B_ASSET_ONE = {'assetID': 'a1713'}
SITE_B_LOSS_ONE = {'mean_loss': 120000.0, 'stddev_loss': 2000.0}

SAMPLE_LOSS_MAP_DATA = [
    LOSS_MAP_METADATA,
    (SITE_A, [(SITE_A_LOSS_ONE, SITE_A_ASSET_ONE),
    (SITE_A_LOSS_TWO, SITE_A_ASSET_TWO)]),
    (SITE_B, [(SITE_B_LOSS_ONE, SITE_B_ASSET_ONE)])]

GML_ID_KEY = '{%s}id' % xml.GML_NS

LOSS_MAP_NODE_ATTRS = ('endBranchLabel', 'lossCategory', 'unit')
LOSS_MAP_NON_SCN_NODE_ATTRS = ('endBranchLabel', 'lossCategory', 'unit',
    'timeSpan', 'poE')

class LossMapOutputTestCase(unittest.TestCase):
    """
    Confirm that XML output (Scenario/Non Scenario) from risk
    engine is valid against schema, as well as correct given the inputs.
    """

    def setUp(self):
        # sample data to serialize for the non scenario writer
        asset_1 = ExposureData()
        asset_1.asset_ref = "a1711"

        asset_2 = ExposureData()
        asset_2.asset_ref = "a1712"

        asset_3 = ExposureData()
        asset_3.asset_ref = "a1713"

        self.non_scenario_mdata =  {
            'nrmlID': 'test_nrml_id',
            'riskResultID': 'test_rr_id',
            'lossMapID': 'test_lm_id',
            'endBranchLabel': 'test_ebl',
            'lossCategory': 'economic_loss',
            'unit': 'EUR',
            'timeSpan': 1.5,
            'poE': 0.5
        }

        self.non_scenario_data = [self.non_scenario_mdata,
            (SITE_A, [({"value": 12621260.1168}, asset_1),
            ({"value": 1913748.91617}, asset_2)]),
            (SITE_B, [({"value": 13223148.4443}, asset_3)])]

        self.xml_writer = risk_output.LossMapXMLWriter(
                TEST_LOSS_MAP_XML_OUTPUT_PATH)

        self.xml_non_scn_writer = risk_output.LossMapNonScenarioXMLWriter(
                TEST_NON_SCN_LOSS_MAP_XML_OUTPUT_PATH)

    def tearDown(self):
        self.xml_writer = None
        self.xml_non_scn_writer = None
        if os.path.exists(TEST_LOSS_MAP_XML_OUTPUT_PATH):
            os.remove(TEST_LOSS_MAP_XML_OUTPUT_PATH)

    def test_loss_map_output_writes_and_validates(self):
        xml_writer = risk_output.LossMapXMLWriter(
            TEST_LOSS_MAP_XML_OUTPUT_PATH)
        xml_writer.serialize(SAMPLE_LOSS_MAP_DATA)
        self.assertTrue(
            xml.validates_against_xml_schema(TEST_LOSS_MAP_XML_OUTPUT_PATH,
            NRML_SCHEMA_PATH),
            "NRML instance file %s does not validate against schema" %
            TEST_LOSS_MAP_XML_OUTPUT_PATH)

        xml_non_det_writer = risk_output.LossMapNonScenarioXMLWriter(
            TEST_NON_SCN_LOSS_MAP_XML_OUTPUT_PATH)
        xml_non_det_writer.serialize(self.non_scenario_data)
        self.assertTrue(
            xml.validates_against_xml_schema(
                TEST_NON_SCN_LOSS_MAP_XML_OUTPUT_PATH, NRML_SCHEMA_PATH),
            "NRML instance file %s does not validate against schema" %
            TEST_NON_SCN_LOSS_MAP_XML_OUTPUT_PATH)

    def test_write_metadata(self):
        """
        Tests the :py:meth:`risk_output.LossMapXMLWriter.write_metadata`
        method using a normal use case.
        """
        for xml_writer, loss_map_data in ((self.xml_writer, LOSS_MAP_METADATA),
                (self.xml_non_scn_writer, self.non_scenario_mdata)):
            xml_writer.write_metadata(loss_map_data)

            # Check the gml:ids first
            for key, node in (
                ('nrmlID', xml_writer.root_node),
                ('riskResultID', xml_writer.risk_result_node),
                ('lossMapID', xml_writer.map_container)):

                self.assertEqual(
                    loss_map_data[key],
                    node.attrib[GML_ID_KEY])

            if isinstance(xml_writer,
                    risk_output.LossMapNonScenarioXMLWriter):
                map_container_attrs = LOSS_MAP_NON_SCN_NODE_ATTRS
            else:
                map_container_attrs = LOSS_MAP_NODE_ATTRS

            # Verify the <lossMap> attributes
            for key in map_container_attrs:
                self.assertEqual(
                    str(loss_map_data[key]),
                    xml_writer.map_container.attrib[key])

    def test_write_metadata_with_some_defaults(self):
        """
        Tests the :py:meth:`risk_output.LossMapXMLWriter.write_metadata`
        method using only partial metadata. For the metadata items not
        specified, this test ensures that defaults are used.
        """
        partial_meta = {
            'lossMapID': 'test_lm_id',
            'endBranchLabel': 'test_ebl',
            'lossCategory': 'economic_loss'}

        for xml_writer, loss_map_data in ((self.xml_writer, LOSS_MAP_METADATA),
                (self.xml_non_scn_writer, self.non_scenario_mdata)):

            xml_writer.write_metadata(loss_map_data)
            xml_writer.write_metadata(partial_meta)

            self.assertEqual(
                'undefined',
                xml_writer.root_node.attrib[GML_ID_KEY])

            self.assertEqual(
                'undefined',
                xml_writer.risk_result_node.attrib[GML_ID_KEY])

            self.assertEqual(
                'undefined',
                xml_writer.map_container.attrib['unit'])

    def test_serialize_with_no_meta(self):
        """
        This test serializes some sample data to an XML file. The sample data
        does not include metadata. This test will verify that defaults are used
        for all metadata.
        """
        for xml_writer, _ in ((self.xml_writer, LOSS_MAP_METADATA),
                (self.xml_non_scn_writer, self.non_scenario_mdata)):

            if isinstance(xml_writer,
                    risk_output.LossMapNonScenarioXMLWriter):
                map_container_attrs = LOSS_MAP_NON_SCN_NODE_ATTRS
                # everything but metadata
                test_data = self.non_scenario_data[1:]
            else:
                map_container_attrs = LOSS_MAP_NODE_ATTRS
                # everything but metadata
                test_data = SAMPLE_LOSS_MAP_DATA[1:]

            xml_writer.serialize(test_data)

            for key, node, in (
                ('nrmlID', xml_writer.root_node),
                ('riskResultID', xml_writer.risk_result_node),
                ('lossMapID', xml_writer.map_container)):
                self.assertEqual(
                    'undefined',
                    node.attrib[GML_ID_KEY])

            for key in map_container_attrs:
                self.assertEqual(
                    'undefined',
                    xml_writer.map_container.attrib[key])

    def test_loss_map_xml_content_is_correct(self):
        """
        This tests writes some sample data to an xml file, then verifies the
        contents.

        In particular, we want to make sure the site and loss data is correct.
        """

        def get_xml_elems_list(path, events=('start', 'end')):
            """
            Use :py:function:`lxml.etree.iterparse` to build a list of the
            elements in the given xml file.

            Note: This function will hold element data in a single list. This
            should work fine for small files, but may not perform well for
            large files.

            :param path: Path to an xml file, including file name
            :param events: Specify open tags ('start',), close tags ('end',),
                or both ('start', 'end'). Default is both.

            :returns: A list of 2-tuples.
                The first tuple item will be 'start' or 'end'.
                The second will be a :py:class:`lxml.etree._Element` object
            """
            elems = []

            for event, elem in etree.iterparse(path, events=events):
                elems.append((event, elem))

            return elems

        for xml_writer, loss_map_data in (
                (self.xml_writer, SAMPLE_LOSS_MAP_DATA),
                (self.xml_non_scn_writer, self.non_scenario_data)):
            xml_writer.serialize(loss_map_data)

            if isinstance(xml_writer, risk_output.LossMapNonScenarioXMLWriter):
                expected_loss_map = EXPECTED_TEST_NON_SCN_LOSS_MAP
                lossmap_xml_out_path = TEST_NON_SCN_LOSS_MAP_XML_OUTPUT_PATH
            else:
                expected_loss_map = EXPECTED_TEST_LOSS_MAP
                lossmap_xml_out_path = TEST_LOSS_MAP_XML_OUTPUT_PATH

            expected_elems = get_xml_elems_list(expected_loss_map)
            actual_elems = get_xml_elems_list(lossmap_xml_out_path)

            # first, make sure out elem list from the expected data
            # is _not_ empty (otherwise, this would be an epic test fail)
            self.assertTrue(len(expected_elems) > 0)

            # then verify the lengths of the elem lists are same
            self.assertEqual(len(expected_elems), len(actual_elems))

            # and finally, verify the actual contents are correct
            for i, (event, elem) in enumerate(expected_elems):
                actual_event, actual_elem = actual_elems[i]
                self.assertEqual(event, actual_event)
                self.assertEqual(elem.items(), actual_elem.items())
<commit_msg>PEP8<commit_after># -*- coding: utf-8 -*-
# vim: tabstop=4 shiftwidth=4 softtabstop=4

# Copyright (c) 2010-2012, GEM Foundation.
#
# OpenQuake is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# OpenQuake is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with OpenQuake.  If not, see <http://www.gnu.org/licenses/>.

"""
Tests for the serialization of loss maps to NRML format.
"""

import os
import unittest

from lxml import etree

from openquake import nrml
from openquake import shapes
from openquake import xml
from tests.utils import helpers

from openquake.db.models import ExposureData
from openquake.output import risk as risk_output

TEST_LOSS_MAP_XML_OUTPUT_PATH = helpers.get_output_path('test-loss-map.xml')
TEST_NON_SCN_LOSS_MAP_XML_OUTPUT_PATH = helpers.get_output_path(
    'test-non-det-loss-map.xml')
EXPECTED_TEST_LOSS_MAP = helpers.get_data_path('expected-test-loss-map.xml')
EXPECTED_TEST_NON_SCN_LOSS_MAP = helpers.get_data_path(
    'expected-non-det-test-loss-map.xml')

NRML_SCHEMA_PATH = nrml.nrml_schema_file()

LOSS_MAP_METADATA = {
    'nrmlID': 'test_nrml_id',
    'riskResultID': 'test_rr_id',
    'lossMapID': 'test_lm_id',
    'endBranchLabel': 'test_ebl',
    'lossCategory': 'economic_loss',
    'unit': 'EUR'}

SITE_A = shapes.Site(-117.0, 38.0)
SITE_A_ASSET_ONE = {'assetID': 'a1711'}
SITE_A_LOSS_ONE = {'mean_loss': 0, 'stddev_loss': 100}

SITE_A_ASSET_TWO = {'assetID': 'a1712'}
SITE_A_LOSS_TWO = {'mean_loss': 5, 'stddev_loss': 2000.0}

SITE_B = shapes.Site(-118.0, 39.0)
SITE_B_ASSET_ONE = {'assetID': 'a1713'}
SITE_B_LOSS_ONE = {'mean_loss': 120000.0, 'stddev_loss': 2000.0}

SAMPLE_LOSS_MAP_DATA = [
    LOSS_MAP_METADATA,
    (SITE_A, [(SITE_A_LOSS_ONE, SITE_A_ASSET_ONE),
    (SITE_A_LOSS_TWO, SITE_A_ASSET_TWO)]),
    (SITE_B, [(SITE_B_LOSS_ONE, SITE_B_ASSET_ONE)])]

GML_ID_KEY = '{%s}id' % xml.GML_NS

LOSS_MAP_NODE_ATTRS = ('endBranchLabel', 'lossCategory', 'unit')
LOSS_MAP_NON_SCN_NODE_ATTRS = ('endBranchLabel', 'lossCategory', 'unit',
    'timeSpan', 'poE')

class LossMapOutputTestCase(unittest.TestCase):
    """
    Confirm that XML output (Scenario/Non Scenario) from risk
    engine is valid against schema, as well as correct given the inputs.
    """

    def setUp(self):
        # sample data to serialize for the non scenario writer
        asset_1 = ExposureData()
        asset_1.asset_ref = "a1711"

        asset_2 = ExposureData()
        asset_2.asset_ref = "a1712"

        asset_3 = ExposureData()
        asset_3.asset_ref = "a1713"

        self.non_scenario_mdata = {
            'nrmlID': 'test_nrml_id',
            'riskResultID': 'test_rr_id',
            'lossMapID': 'test_lm_id',
            'endBranchLabel': 'test_ebl',
            'lossCategory': 'economic_loss',
            'unit': 'EUR',
            'timeSpan': 1.5,
            'poE': 0.5
        }

        self.non_scenario_data = [self.non_scenario_mdata,
            (SITE_A, [({"value": 12621260.1168}, asset_1),
            ({"value": 1913748.91617}, asset_2)]),
            (SITE_B, [({"value": 13223148.4443}, asset_3)])]

        self.xml_writer = risk_output.LossMapXMLWriter(
                TEST_LOSS_MAP_XML_OUTPUT_PATH)

        self.xml_non_scn_writer = risk_output.LossMapNonScenarioXMLWriter(
                TEST_NON_SCN_LOSS_MAP_XML_OUTPUT_PATH)

    def tearDown(self):
        self.xml_writer = None
        self.xml_non_scn_writer = None
        if os.path.exists(TEST_LOSS_MAP_XML_OUTPUT_PATH):
            os.remove(TEST_LOSS_MAP_XML_OUTPUT_PATH)

    def test_loss_map_output_writes_and_validates(self):
        xml_writer = risk_output.LossMapXMLWriter(
            TEST_LOSS_MAP_XML_OUTPUT_PATH)
        xml_writer.serialize(SAMPLE_LOSS_MAP_DATA)
        self.assertTrue(
            xml.validates_against_xml_schema(TEST_LOSS_MAP_XML_OUTPUT_PATH,
            NRML_SCHEMA_PATH),
            "NRML instance file %s does not validate against schema" %
            TEST_LOSS_MAP_XML_OUTPUT_PATH)

        xml_non_det_writer = risk_output.LossMapNonScenarioXMLWriter(
            TEST_NON_SCN_LOSS_MAP_XML_OUTPUT_PATH)
        xml_non_det_writer.serialize(self.non_scenario_data)
        self.assertTrue(
            xml.validates_against_xml_schema(
                TEST_NON_SCN_LOSS_MAP_XML_OUTPUT_PATH, NRML_SCHEMA_PATH),
            "NRML instance file %s does not validate against schema" %
            TEST_NON_SCN_LOSS_MAP_XML_OUTPUT_PATH)

    def test_write_metadata(self):
        """
        Tests the :py:meth:`risk_output.LossMapXMLWriter.write_metadata`
        method using a normal use case.
        """
        for xml_writer, loss_map_data in ((self.xml_writer, LOSS_MAP_METADATA),
                (self.xml_non_scn_writer, self.non_scenario_mdata)):
            xml_writer.write_metadata(loss_map_data)

            # Check the gml:ids first
            for key, node in (
                ('nrmlID', xml_writer.root_node),
                ('riskResultID', xml_writer.risk_result_node),
                ('lossMapID', xml_writer.map_container)):

                self.assertEqual(
                    loss_map_data[key],
                    node.attrib[GML_ID_KEY])

            if isinstance(xml_writer,
                    risk_output.LossMapNonScenarioXMLWriter):
                map_container_attrs = LOSS_MAP_NON_SCN_NODE_ATTRS
            else:
                map_container_attrs = LOSS_MAP_NODE_ATTRS

            # Verify the <lossMap> attributes
            for key in map_container_attrs:
                self.assertEqual(
                    str(loss_map_data[key]),
                    xml_writer.map_container.attrib[key])

    def test_write_metadata_with_some_defaults(self):
        """
        Tests the :py:meth:`risk_output.LossMapXMLWriter.write_metadata`
        method using only partial metadata. For the metadata items not
        specified, this test ensures that defaults are used.
        """
        partial_meta = {
            'lossMapID': 'test_lm_id',
            'endBranchLabel': 'test_ebl',
            'lossCategory': 'economic_loss'}

        for xml_writer, loss_map_data in ((self.xml_writer, LOSS_MAP_METADATA),
                (self.xml_non_scn_writer, self.non_scenario_mdata)):

            xml_writer.write_metadata(loss_map_data)
            xml_writer.write_metadata(partial_meta)

            self.assertEqual(
                'undefined',
                xml_writer.root_node.attrib[GML_ID_KEY])

            self.assertEqual(
                'undefined',
                xml_writer.risk_result_node.attrib[GML_ID_KEY])

            self.assertEqual(
                'undefined',
                xml_writer.map_container.attrib['unit'])

    def test_serialize_with_no_meta(self):
        """
        This test serializes some sample data to an XML file. The sample data
        does not include metadata. This test will verify that defaults are used
        for all metadata.
        """
        for xml_writer, _ in ((self.xml_writer, LOSS_MAP_METADATA),
                (self.xml_non_scn_writer, self.non_scenario_mdata)):

            if isinstance(xml_writer,
                    risk_output.LossMapNonScenarioXMLWriter):
                map_container_attrs = LOSS_MAP_NON_SCN_NODE_ATTRS
                # everything but metadata
                test_data = self.non_scenario_data[1:]
            else:
                map_container_attrs = LOSS_MAP_NODE_ATTRS
                # everything but metadata
                test_data = SAMPLE_LOSS_MAP_DATA[1:]

            xml_writer.serialize(test_data)

            for key, node, in (
                ('nrmlID', xml_writer.root_node),
                ('riskResultID', xml_writer.risk_result_node),
                ('lossMapID', xml_writer.map_container)):
                self.assertEqual(
                    'undefined',
                    node.attrib[GML_ID_KEY])

            for key in map_container_attrs:
                self.assertEqual(
                    'undefined',
                    xml_writer.map_container.attrib[key])

    def test_loss_map_xml_content_is_correct(self):
        """
        This tests writes some sample data to an xml file, then verifies the
        contents.

        In particular, we want to make sure the site and loss data is correct.
        """

        def get_xml_elems_list(path, events=('start', 'end')):
            """
            Use :py:function:`lxml.etree.iterparse` to build a list of the
            elements in the given xml file.

            Note: This function will hold element data in a single list. This
            should work fine for small files, but may not perform well for
            large files.

            :param path: Path to an xml file, including file name
            :param events: Specify open tags ('start',), close tags ('end',),
                or both ('start', 'end'). Default is both.

            :returns: A list of 2-tuples.
                The first tuple item will be 'start' or 'end'.
                The second will be a :py:class:`lxml.etree._Element` object
            """
            elems = []

            for event, elem in etree.iterparse(path, events=events):
                elems.append((event, elem))

            return elems

        for xml_writer, loss_map_data in (
                (self.xml_writer, SAMPLE_LOSS_MAP_DATA),
                (self.xml_non_scn_writer, self.non_scenario_data)):
            xml_writer.serialize(loss_map_data)

            if isinstance(xml_writer, risk_output.LossMapNonScenarioXMLWriter):
                expected_loss_map = EXPECTED_TEST_NON_SCN_LOSS_MAP
                lossmap_xml_out_path = TEST_NON_SCN_LOSS_MAP_XML_OUTPUT_PATH
            else:
                expected_loss_map = EXPECTED_TEST_LOSS_MAP
                lossmap_xml_out_path = TEST_LOSS_MAP_XML_OUTPUT_PATH

            expected_elems = get_xml_elems_list(expected_loss_map)
            actual_elems = get_xml_elems_list(lossmap_xml_out_path)

            # first, make sure out elem list from the expected data
            # is _not_ empty (otherwise, this would be an epic test fail)
            self.assertTrue(len(expected_elems) > 0)

            # then verify the lengths of the elem lists are same
            self.assertEqual(len(expected_elems), len(actual_elems))

            # and finally, verify the actual contents are correct
            for i, (event, elem) in enumerate(expected_elems):
                actual_event, actual_elem = actual_elems[i]
                self.assertEqual(event, actual_event)
                self.assertEqual(elem.items(), actual_elem.items())
<|endoftext|>


<commit_before>from copy import copy
from collections import defaultdict
from itertools import chain
from nineml.xml import E, extract_xmlns, strip_xmlns
from nineml.base import DocumentLevelObject, BaseNineMLObject, ContainerObject
import re
from nineml.xml import ElementMaker, nineml_ns, etree
from nineml.exceptions import (
    NineMLXMLError, NineMLRuntimeError, NineMLNameError)

def read_annotations(from_xml):
    def annotate_from_xml(cls, element, *args, **kwargs):
        nineml_xmlns = extract_xmlns(element.tag)
        annot_elem = expect_none_or_single(
            element.findall(nineml_xmlns + Annotations.nineml_type))
        if annot_elem is not None:
            # Extract the annotations
            annotations = Annotations.from_xml(annot_elem, **kwargs)
            # Get a copy of the element with the annotations stripped
            element = copy(element)
            element.remove(element.find(nineml_xmlns +
                                        Annotations.nineml_type))
        else:
            annotations = Annotations()
        if cls.__class__.__name__ == 'DynamicsXMLLoader':
            # FIXME: Hack until I work out the best way to let other 9ML
            #        objects ignore this kwarg TGC 6/15
            valid_dims = annotations.get(
                (VALIDATION, PY9ML_NS), DIMENSIONALITY,
                default='True') == 'True'
            kwargs['validate_dimensions'] = valid_dims
        nineml_object = from_xml(cls, element, *args, **kwargs)
        # Extract saved indices from annotations and save them in container
        # object.
        if (INDEX_TAG, PY9ML_NS) in annotations:
            for ind in annotations.pop((INDEX_TAG, PY9ML_NS)):
                key = ind.get(INDEX_KEY_ATTR)
                name = ind.get(INDEX_NAME_ATTR)
                index = ind.get(INDEX_INDEX_ATTR)
                nineml_object._indices[
                    key][getattr(nineml_object, key)(name)] = int(index)
        # Set extracted annotations in nineml_object
        nineml_object._annotations = annotations
        return nineml_object
    return annotate_from_xml

def annotate_xml(to_xml):
    def annotate_to_xml(self, document_or_obj, E=E, **kwargs):
        """
        Parameters
        ----------
        save_indices : bool
            Whether to save the indices assigned to sub-elements or not
        """
        # If Abstraction Layer class
        if xml_visitor_module_re.match(type(self).__module__):
            obj = document_or_obj
            options = self.options
        # If User Layer class
        else:
            obj = self
            options = kwargs
        xml = to_xml(self, document_or_obj, E=E, **kwargs)
        annot_xml = None
        if not options.get('no_annotations', False):
            if obj.annotations:
                annot_xml = obj.annotations.to_xml(E=E, **kwargs)
            # Append sub-element indices if 'save_indices' is provided and true
            if (options.get('save_indices', False) and
                    isinstance(obj, ContainerObject)):
                # Create empty annot_elem if container element doesn't have any
                # other annotations
                indices_annot = Annotations()
                for key, elem, index in obj.all_indices():
                    index_annot = indices_annot.add((INDEX_TAG, PY9ML_NS))
                    index_annot.set(INDEX_KEY_ATTR, key)
                    index_annot.set(INDEX_NAME_ATTR, elem.name)
                    index_annot.set(INDEX_INDEX_ATTR, index)
                ind_annot_xml = indices_annot.to_xml(E=E, **kwargs)
                if annot_xml is None:
                    annot_xml = ind_annot_xml
                else:
                    for child in ind_annot_xml.getchildren():
                        annot_xml.append(child)
        if annot_xml is not None:
            xml.append(annot_xml)
        return xml
    return annotate_to_xml

class BaseAnnotations(BaseNineMLObject):

    def __init__(self, branches=None):
        self._branches = defaultdict(list)
        if branches is not None:
            assert all(isinstance(b, list) for b in branches)
            self._branches.update(branches)

    def __len__(self):
        return len(self._branches)

    def __repr__(self):
        return self._repr()

    def equals(self, other, **kwargs):  # @UnusedVariable
        try:
            if self.nineml_type != other.nineml_type:
                return False
        except AttributeError:
            return False
        return self._branches == other._branches

    def __iter__(self):
        return self._branches.iterkeys()

    def __contains__(self, key):
        return self._parse_key(key) in self._branches

    def __getitem__(self, key):
        """
        Returns the list of sub-branches for the given key

        Parameters
        ----------
        key : str
            The name of the annotations branch(es) to return
        """
        key = self._parse_key(key)
        if key in self._branches:
            key_branches = self._branches[key]
        else:
            raise NineMLNameError(
                "{} annotations branch does not have branch or attribute '{}'"
                .format(self._name, key))
        return key_branches

    def add(self, key, *args):
        """
        Adds a new branch for the given key. If the key exists already then
        then an additional branch is appended for that key.

        Parameters
        ----------
        key : str
            The name of the key to append a new branch for if no args provided
            otherwise the name of the branch to add a sub-branch to
        *args : list(str)
            A list of keys for the sub-branches ending in the key of the new
            sub-branch to add. Intermediate branches that are not present are
            added implicitly.
        """
        key = self._parse_key(key)
        key_branches = self._branches[key]
        if not key_branches or not args:
            branch = _AnnotationsBranch(*key)
            key_branches.append(branch)
        if args:
            if len(key_branches) > 1:
                raise NineMLNameError(
                    "Multiple branches found for key '{}' in annoations branch"
                    " '{}', cannot use 'add' method to add a sub-branch"
                    .format(key, self._name))
            branch = key_branches[0]
            branch.add(*args)
        return branch

    def pop(self, key):
        """
        Pops the list of sub-branches matching the given key

        Parameters
        ----------
        key : str | (str, str)
            A string containing the name of the branch to pop from the list or
            a tuple containing the name and namespace of the branch. If the
            namespace is not provided it is assumed to be the same as the
            branch above
        """
        try:
            return self._branches.pop(self._parse_key(key))
        except KeyError:
            return []

    def set(self, key, *args):
        """
        Sets the attribute of an annotations "leaf", creating intermediate
        branches if required

        Parameters
        ----------
        key : str
            Name of the first branch in the annotations tree
        *args : list(str) + (int|float|str)
            A list of subsequent branches to the leaf node followed by the
            attribute name and a value
        """
        key = self._parse_key(key)
        # Recurse into branches while there are remaining args
        key_branches = self._branches[key]
        if len(key_branches) == 1:
            branch = key_branches[0]
        elif not key_branches:
            branch = _AnnotationsBranch(*key)
            key_branches.append(branch)
        else:
            raise NineMLNameError(
                "Multiple branches found for key '{}' in annoations branch"
                " '{}', cannot use 'set' method".format(
                    key, self._name))
        branch.set(*args)  # recurse into branch

    def get(self, key, *args, **kwargs):
        """
        Gets the attribute of an annotations "leaf"

        Parameters
        ----------
        key : str
            Name of the first branch in the annotations tree
        *args : list(str) + (int|float|str)
            A list of subsequent branches to the leaf node followed by the
            attribute name to return the value of
        default: (int|float|str)
            The default value to return if the specified annotation has not
            been set

        Returns
        -------
        val : (int|float|str)
            The value of the annotation attribute
        """
        key = self._parse_key(key)
        if key in self._branches:
            key_branches = self._branches[key]
            if len(key_branches) == 1:
                # Recurse into branches while there are remaining args
                val = key_branches[0].get(*args, **kwargs)
            else:
                raise NineMLNameError(
                    "Multiple branches found for key '{}' in annoations "
                    "branch '{}', cannot use 'get' method".format(
                        key, self._name))
        else:
            if 'default' in kwargs:
                return kwargs['default']
            else:
                raise NineMLNameError(
                    "No annotation at path '{}'".format("', '".join(args)))
        return val

    @classmethod
    def _extract_key(cls, child):
        ns = extract_xmlns(child.tag)
        if not ns:
            raise NineMLXMLError(
                "All annotations must have a namespace: {}".format(
                    etree.tostring(child, pretty_print=True)))
        ns = ns[1:-1]  # strip braces
        name = strip_xmlns(child.tag)
        return (name, ns)

    def _sub_branches_to_xml(self, **kwargs):
        members = []
        for key_branches in self._branches.itervalues():
            for branch in key_branches:
                members.append(branch.to_xml(**kwargs))
        return members

    def _copy_to_clone(self, clone, memo, **kwargs):
        self._clone_defining_attr(clone, memo, **kwargs)

class Annotations(BaseAnnotations, DocumentLevelObject):
    """
    Is able to handle a basic hierarchical annotations format where the first
    level is the namespace of each sub element in the Annotations block
    """

    nineml_type = 'Annotations'
    defining_attributes = ('_branches',)

    def __init__(self, document=None):
        BaseAnnotations.__init__(self)
        DocumentLevelObject.__init__(self, document)

    def __repr__(self):
        rep = "Annotations:"
        for key_branch in self._branches.itervalues():
            for b in key_branch:
                rep += '\n' + b._repr(indent='  ')
        return rep

    def to_xml(self, E=E, **kwargs):  # @UnusedVariable
        return E(self.nineml_type, *self._sub_branches_to_xml(**kwargs))

    @classmethod
    def from_xml(cls, element, **kwargs):  # @UnusedVariable @IgnorePep8
        assert strip_xmlns(element.tag) == cls.nineml_type
        assert not element.attrib
        if element.text is not None:
            assert not element.text.strip()
        branches = defaultdict(list)
        for child in element.getchildren():
            branches[cls._extract_key(child)].append(
                _AnnotationsBranch.from_xml(child))
        return cls(branches, **kwargs)

    def _copy_to_clone(self, clone, memo, **kwargs):
        self._clone_defining_attr(clone, memo, **kwargs)
        clone._document = None

    def _parse_key(self, key):
        """
        Prepend current enclosing NS onto key if not provided explicitly

        Parameters
        ----------
        key : str
            Key of the annotations sub-branch
        """
        if not isinstance(key, tuple):
            raise NineMLXMLError(
                "All annotations under the root must have an explicit, "
                "'{}' branch does not".format(key))
        return key

    @property
    def _name(self):
        return 'Root'

class _AnnotationsBranch(BaseAnnotations):

    nineml_type = '_AnnotationsBranch'
    defining_attributes = ('_branches', '_attr', '_name', '_ns', '_text')

    def __init__(self, name, ns, attr=None, branches=None, text=None):
        super(_AnnotationsBranch, self).__init__(branches)
        if attr is None:
            attr = {}
        self._name = name
        self._ns = ns
        self._attr = attr
        self._text = text

    @property
    def name(self):
        return self._name

    @property
    def ns(self):
        return self._ns

    @property
    def text(self):
        return self._text

    @property
    def attr(self):
        return self._attr

    def equals(self, other, **kwargs):  # @UnusedVariable
        return (super(_AnnotationsBranch, self).equals(other) and
                self.name == other.name and
                self.attr == other.attr and
                self.text == other.text and
                self.ns == other.ns)

    def _repr(self, indent=''):
        rep = "{}{{{}}}{}:".format(indent, self.ns, self.name)
        for attr, val in self.attr_items():
            rep += '\n{}{}={}'.format(indent + '  ', attr, val)
        for key_branches in self._branches.itervalues():
            for branch in key_branches:
                rep += '\n' + branch._repr(indent=indent + '  ')
        if self.text is not None:
            rep += '\n{}__text__={}'.format(indent + '  ', self.text)
        return rep

    def attr_values(self):
        return self._attr.itervalues()

    def attr_keys(self):
        return self._attr.iterkeys()

    def attr_items(self):
        return self._attr.iteritems()

    def set(self, key, *args):
        """
        Sets the attribute of an annotations "leaf", creating intermediate
        branches if required

        Parameters
        ----------
        key : str
            Name of the first branch in the annotations tree
        *args : list(str) + (int|float|str)
            A list of subsequent branches to the leaf node followed by the
            attribute name and a value
        """
        if len(args) == 1:
            self._attr[key] = str(args[0])
        elif not args:
            raise NineMLRuntimeError("No value was provided to set of '{}' "
                                     "in annotations branch '{}'"
                                     .format(key, self.name))
        else:
            super(_AnnotationsBranch, self).set(key, *args)

    def get(self, key, *args, **kwargs):
        """
        Gets the attribute of an annotations "leaf"

        Parameters
        ----------
        key : str
            Name of the first branch in the annotations tree
        *args : list(str) + (int|float|str)
            A list of subsequent branches to the leaf node followed by the
            attribute name to return the value of
        default: (int|float|str)
            The default value to return if the specified annotation has not
            been set

        Returns
        -------
        val : (int|float|str)
            The value of the annotation attribute
        """
        if not args:
            if 'default' in kwargs:
                val = self._attr.get(key, kwargs['default'])
            else:
                val = self._attr[key]
        else:
            val = super(_AnnotationsBranch, self).get(key, *args, **kwargs)
        return val

    def to_xml(self, **kwargs):  # @UnusedVariable
        E = ElementMaker(namespace=self.ns)
        members = self._sub_branches_to_xml(**kwargs)
        if self.text is not None:
            members.append(self.text)
        return E(self.name, *members, **self._attr)

    @classmethod
    def from_xml(cls, element, **kwargs):  # @UnusedVariable
        name, ns = cls._extract_key(element)
        branches = defaultdict(list)
        for child in element.getchildren():
            branches[cls._extract_key(child)].append(
                _AnnotationsBranch.from_xml(child))
        attr = dict(element.attrib)
        if element.text is not None and element.text.strip():
            text = element.text.strip()
        else:
            text = None
        return cls(name, ns, attr=attr, branches=branches, text=text)

    def _copy_to_clone(self, clone, memo, **kwargs):
        self._clone_defining_attr(clone, memo, **kwargs)

    def _parse_key(self, key):
        """
        Prepend current enclosing NS onto key if not provided explicitly

        Parameters
        ----------
        key : str
            Key of the annotations sub-branch
        """
        if not isinstance(key, tuple):
            key = (key, self.ns)
        return key

# Python-9ML library specific annotations
PY9ML_NS = 'http://github.com/INCF/lib9ml'

# Annotation
INDICES_TAG = 'Indices'
INDEX_TAG = 'Index'
INDEX_KEY_ATTR = 'key'
INDEX_NAME_ATTR = 'name'
INDEX_INDEX_ATTR = 'index'

# Dimension validation
VALIDATION = 'Validation'
DIMENSIONALITY = 'dimensionality'

xml_visitor_module_re = re.compile(r'nineml\.abstraction\.\w+\.visitors\.xml')

from nineml.utils import expect_none_or_single  # @IgnorePep8
<commit_msg>fixed up Annotations.from_xml<commit_after>from copy import copy
from collections import defaultdict
from itertools import chain
from nineml.xml import E, extract_xmlns, strip_xmlns
from nineml.base import DocumentLevelObject, BaseNineMLObject, ContainerObject
import re
from nineml.xml import ElementMaker, nineml_ns, etree
from nineml.exceptions import (
    NineMLXMLError, NineMLRuntimeError, NineMLNameError)

def read_annotations(from_xml):
    def annotate_from_xml(cls, element, *args, **kwargs):
        nineml_xmlns = extract_xmlns(element.tag)
        annot_elem = expect_none_or_single(
            element.findall(nineml_xmlns + Annotations.nineml_type))
        if annot_elem is not None:
            # Extract the annotations
            annotations = Annotations.from_xml(annot_elem, **kwargs)
            # Get a copy of the element with the annotations stripped
            element = copy(element)
            element.remove(element.find(nineml_xmlns +
                                        Annotations.nineml_type))
        else:
            annotations = Annotations()
        if cls.__class__.__name__ == 'DynamicsXMLLoader':
            # FIXME: Hack until I work out the best way to let other 9ML
            #        objects ignore this kwarg TGC 6/15
            valid_dims = annotations.get(
                (VALIDATION, PY9ML_NS), DIMENSIONALITY,
                default='True') == 'True'
            kwargs['validate_dimensions'] = valid_dims
        nineml_object = from_xml(cls, element, *args, **kwargs)
        # Extract saved indices from annotations and save them in container
        # object.
        if (INDEX_TAG, PY9ML_NS) in annotations:
            for ind in annotations.pop((INDEX_TAG, PY9ML_NS)):
                key = ind.get(INDEX_KEY_ATTR)
                name = ind.get(INDEX_NAME_ATTR)
                index = ind.get(INDEX_INDEX_ATTR)
                nineml_object._indices[
                    key][getattr(nineml_object, key)(name)] = int(index)
        # Set extracted annotations in nineml_object
        nineml_object._annotations = annotations
        return nineml_object
    return annotate_from_xml

def annotate_xml(to_xml):
    def annotate_to_xml(self, document_or_obj, E=E, **kwargs):
        """
        Parameters
        ----------
        save_indices : bool
            Whether to save the indices assigned to sub-elements or not
        """
        # If Abstraction Layer class
        if xml_visitor_module_re.match(type(self).__module__):
            obj = document_or_obj
            options = self.options
        # If User Layer class
        else:
            obj = self
            options = kwargs
        xml = to_xml(self, document_or_obj, E=E, **kwargs)
        annot_xml = None
        if not options.get('no_annotations', False):
            if obj.annotations:
                annot_xml = obj.annotations.to_xml(E=E, **kwargs)
            # Append sub-element indices if 'save_indices' is provided and true
            if (options.get('save_indices', False) and
                    isinstance(obj, ContainerObject)):
                # Create empty annot_elem if container element doesn't have any
                # other annotations
                indices_annot = Annotations()
                for key, elem, index in obj.all_indices():
                    index_annot = indices_annot.add((INDEX_TAG, PY9ML_NS))
                    index_annot.set(INDEX_KEY_ATTR, key)
                    index_annot.set(INDEX_NAME_ATTR, elem.name)
                    index_annot.set(INDEX_INDEX_ATTR, index)
                ind_annot_xml = indices_annot.to_xml(E=E, **kwargs)
                if annot_xml is None:
                    annot_xml = ind_annot_xml
                else:
                    for child in ind_annot_xml.getchildren():
                        annot_xml.append(child)
        if annot_xml is not None:
            xml.append(annot_xml)
        return xml
    return annotate_to_xml

class BaseAnnotations(BaseNineMLObject):

    def __init__(self, branches=None):
        self._branches = defaultdict(list)
        if branches is not None:
            assert all(isinstance(b, list) for b in branches.itervalues())
            self._branches.update(branches)

    def __len__(self):
        return len(self._branches)

    def __repr__(self):
        return self._repr()

    def equals(self, other, **kwargs):  # @UnusedVariable
        try:
            if self.nineml_type != other.nineml_type:
                return False
        except AttributeError:
            return False
        return self._branches == other._branches

    def __iter__(self):
        return self._branches.iterkeys()

    def __contains__(self, key):
        return self._parse_key(key) in self._branches

    def __getitem__(self, key):
        """
        Returns the list of sub-branches for the given key

        Parameters
        ----------
        key : str
            The name of the annotations branch(es) to return
        """
        key = self._parse_key(key)
        if key in self._branches:
            key_branches = self._branches[key]
        else:
            raise NineMLNameError(
                "{} annotations branch does not have branch or attribute '{}'"
                .format(self._name, key))
        return key_branches

    def add(self, key, *args):
        """
        Adds a new branch for the given key. If the key exists already then
        then an additional branch is appended for that key.

        Parameters
        ----------
        key : str
            The name of the key to append a new branch for if no args provided
            otherwise the name of the branch to add a sub-branch to
        *args : list(str)
            A list of keys for the sub-branches ending in the key of the new
            sub-branch to add. Intermediate branches that are not present are
            added implicitly.
        """
        key = self._parse_key(key)
        key_branches = self._branches[key]
        if not key_branches or not args:
            branch = _AnnotationsBranch(*key)
            key_branches.append(branch)
        if args:
            if len(key_branches) > 1:
                raise NineMLNameError(
                    "Multiple branches found for key '{}' in annoations branch"
                    " '{}', cannot use 'add' method to add a sub-branch"
                    .format(key, self._name))
            branch = key_branches[0]
            branch.add(*args)
        return branch

    def pop(self, key):
        """
        Pops the list of sub-branches matching the given key

        Parameters
        ----------
        key : str | (str, str)
            A string containing the name of the branch to pop from the list or
            a tuple containing the name and namespace of the branch. If the
            namespace is not provided it is assumed to be the same as the
            branch above
        """
        try:
            return self._branches.pop(self._parse_key(key))
        except KeyError:
            return []

    def set(self, key, *args):
        """
        Sets the attribute of an annotations "leaf", creating intermediate
        branches if required

        Parameters
        ----------
        key : str
            Name of the first branch in the annotations tree
        *args : list(str) + (int|float|str)
            A list of subsequent branches to the leaf node followed by the
            attribute name and a value
        """
        key = self._parse_key(key)
        # Recurse into branches while there are remaining args
        key_branches = self._branches[key]
        if len(key_branches) == 1:
            branch = key_branches[0]
        elif not key_branches:
            branch = _AnnotationsBranch(*key)
            key_branches.append(branch)
        else:
            raise NineMLNameError(
                "Multiple branches found for key '{}' in annoations branch"
                " '{}', cannot use 'set' method".format(
                    key, self._name))
        branch.set(*args)  # recurse into branch

    def get(self, key, *args, **kwargs):
        """
        Gets the attribute of an annotations "leaf"

        Parameters
        ----------
        key : str
            Name of the first branch in the annotations tree
        *args : list(str) + (int|float|str)
            A list of subsequent branches to the leaf node followed by the
            attribute name to return the value of
        default: (int|float|str)
            The default value to return if the specified annotation has not
            been set

        Returns
        -------
        val : (int|float|str)
            The value of the annotation attribute
        """
        key = self._parse_key(key)
        if key in self._branches:
            key_branches = self._branches[key]
            if len(key_branches) == 1:
                # Recurse into branches while there are remaining args
                val = key_branches[0].get(*args, **kwargs)
            else:
                raise NineMLNameError(
                    "Multiple branches found for key '{}' in annoations "
                    "branch '{}', cannot use 'get' method".format(
                        key, self._name))
        else:
            if 'default' in kwargs:
                return kwargs['default']
            else:
                raise NineMLNameError(
                    "No annotation at path '{}'".format("', '".join(args)))
        return val

    @classmethod
    def _extract_key(cls, child):
        ns = extract_xmlns(child.tag)
        if not ns:
            raise NineMLXMLError(
                "All annotations must have a namespace: {}".format(
                    etree.tostring(child, pretty_print=True)))
        ns = ns[1:-1]  # strip braces
        name = strip_xmlns(child.tag)
        return (name, ns)

    def _sub_branches_to_xml(self, **kwargs):
        members = []
        for key_branches in self._branches.itervalues():
            for branch in key_branches:
                members.append(branch.to_xml(**kwargs))
        return members

    def _copy_to_clone(self, clone, memo, **kwargs):
        self._clone_defining_attr(clone, memo, **kwargs)

class Annotations(BaseAnnotations, DocumentLevelObject):
    """
    Is able to handle a basic hierarchical annotations format where the first
    level is the namespace of each sub element in the Annotations block
    """

    nineml_type = 'Annotations'
    defining_attributes = ('_branches',)

    def __init__(self, branches=None, document=None):
        BaseAnnotations.__init__(self, branches=branches)
        DocumentLevelObject.__init__(self, document)

    def __repr__(self):
        rep = "Annotations:"
        for key_branch in self._branches.itervalues():
            for b in key_branch:
                rep += '\n' + b._repr(indent='  ')
        return rep

    def to_xml(self, E=E, **kwargs):  # @UnusedVariable
        return E(self.nineml_type, *self._sub_branches_to_xml(**kwargs))

    @classmethod
    def from_xml(cls, element, **kwargs):  # @UnusedVariable @IgnorePep8
        assert strip_xmlns(element.tag) == cls.nineml_type
        assert not element.attrib
        if element.text is not None:
            assert not element.text.strip()
        branches = defaultdict(list)
        for child in element.getchildren():
            branches[cls._extract_key(child)].append(
                _AnnotationsBranch.from_xml(child))
        return cls(branches, **kwargs)

    def _copy_to_clone(self, clone, memo, **kwargs):
        self._clone_defining_attr(clone, memo, **kwargs)
        clone._document = None

    def _parse_key(self, key):
        """
        Prepend current enclosing NS onto key if not provided explicitly

        Parameters
        ----------
        key : str
            Key of the annotations sub-branch
        """
        if not isinstance(key, tuple):
            raise NineMLXMLError(
                "All annotations under the root must have an explicit, "
                "'{}' branch does not".format(key))
        return key

    @property
    def _name(self):
        return 'Root'

class _AnnotationsBranch(BaseAnnotations):

    nineml_type = '_AnnotationsBranch'
    defining_attributes = ('_branches', '_attr', '_name', '_ns', '_text')

    def __init__(self, name, ns, attr=None, branches=None, text=None):
        super(_AnnotationsBranch, self).__init__(branches)
        if attr is None:
            attr = {}
        self._name = name
        self._ns = ns
        self._attr = attr
        self._text = text

    @property
    def name(self):
        return self._name

    @property
    def ns(self):
        return self._ns

    @property
    def text(self):
        return self._text

    @property
    def attr(self):
        return self._attr

    def equals(self, other, **kwargs):  # @UnusedVariable
        return (super(_AnnotationsBranch, self).equals(other) and
                self.name == other.name and
                self.attr == other.attr and
                self.text == other.text and
                self.ns == other.ns)

    def _repr(self, indent=''):
        rep = "{}{{{}}}{}:".format(indent, self.ns, self.name)
        for attr, val in self.attr_items():
            rep += '\n{}{}={}'.format(indent + '  ', attr, val)
        for key_branches in self._branches.itervalues():
            for branch in key_branches:
                rep += '\n' + branch._repr(indent=indent + '  ')
        if self.text is not None:
            rep += '\n{}__text__={}'.format(indent + '  ', self.text)
        return rep

    def attr_values(self):
        return self._attr.itervalues()

    def attr_keys(self):
        return self._attr.iterkeys()

    def attr_items(self):
        return self._attr.iteritems()

    def set(self, key, *args):
        """
        Sets the attribute of an annotations "leaf", creating intermediate
        branches if required

        Parameters
        ----------
        key : str
            Name of the first branch in the annotations tree
        *args : list(str) + (int|float|str)
            A list of subsequent branches to the leaf node followed by the
            attribute name and a value
        """
        if len(args) == 1:
            self._attr[key] = str(args[0])
        elif not args:
            raise NineMLRuntimeError("No value was provided to set of '{}' "
                                     "in annotations branch '{}'"
                                     .format(key, self.name))
        else:
            super(_AnnotationsBranch, self).set(key, *args)

    def get(self, key, *args, **kwargs):
        """
        Gets the attribute of an annotations "leaf"

        Parameters
        ----------
        key : str
            Name of the first branch in the annotations tree
        *args : list(str) + (int|float|str)
            A list of subsequent branches to the leaf node followed by the
            attribute name to return the value of
        default: (int|float|str)
            The default value to return if the specified annotation has not
            been set

        Returns
        -------
        val : (int|float|str)
            The value of the annotation attribute
        """
        if not args:
            if 'default' in kwargs:
                val = self._attr.get(key, kwargs['default'])
            else:
                val = self._attr[key]
        else:
            val = super(_AnnotationsBranch, self).get(key, *args, **kwargs)
        return val

    def to_xml(self, **kwargs):  # @UnusedVariable
        E = ElementMaker(namespace=self.ns)
        members = self._sub_branches_to_xml(**kwargs)
        if self.text is not None:
            members.append(self.text)
        return E(self.name, *members, **self._attr)

    @classmethod
    def from_xml(cls, element, **kwargs):  # @UnusedVariable
        name, ns = cls._extract_key(element)
        branches = defaultdict(list)
        for child in element.getchildren():
            branches[cls._extract_key(child)].append(
                _AnnotationsBranch.from_xml(child))
        attr = dict(element.attrib)
        if element.text is not None and element.text.strip():
            text = element.text.strip()
        else:
            text = None
        return cls(name, ns, attr=attr, branches=branches, text=text)

    def _copy_to_clone(self, clone, memo, **kwargs):
        self._clone_defining_attr(clone, memo, **kwargs)

    def _parse_key(self, key):
        """
        Prepend current enclosing NS onto key if not provided explicitly

        Parameters
        ----------
        key : str
            Key of the annotations sub-branch
        """
        if not isinstance(key, tuple):
            key = (key, self.ns)
        return key

# Python-9ML library specific annotations
PY9ML_NS = 'http://github.com/INCF/lib9ml'

# Annotation
INDICES_TAG = 'Indices'
INDEX_TAG = 'Index'
INDEX_KEY_ATTR = 'key'
INDEX_NAME_ATTR = 'name'
INDEX_INDEX_ATTR = 'index'

# Dimension validation
VALIDATION = 'Validation'
DIMENSIONALITY = 'dimensionality'

xml_visitor_module_re = re.compile(r'nineml\.abstraction\.\w+\.visitors\.xml')

from nineml.utils import expect_none_or_single  # @IgnorePep8
<|endoftext|>


<commit_before>#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import models
from django.template.defaultfilters import slugify
import datetime
from django.core.validators import RegexValidator

class Genero(models.Model):
    nombre = models.CharField(blank=False, max_length=100,primary_key=True,unique=True)

    def get(self):
        return nombre

    def __unicode__(self):
            return self.nombre

class Usuario(models.Model):
    nombre = models.CharField(blank=False, max_length=15)
    apellidos = models.CharField(blank=False, max_length=30)
    phone_regex = RegexValidator(regex=r'^\d{9,15}$', message="Telefono debe ser: 123456789.")
    telefono = models.CharField(validators=[phone_regex], blank=True,max_length=15) # validators should be a list
    direccion = models.CharField(blank=True,max_length=100)

    def __unicode__(self):
            return self.nombre+" "+self.apellidos

class Recurso(models.Model):
    titulo = models.CharField(blank=False, max_length=100)
    autor = models.CharField(blank=False, max_length=100)
    descripcion = models.CharField(blank=True, max_length=250)
    orden = models.CharField(blank=True, max_length=100)
    estanteria = models.CharField(blank=True, max_length=100)
    genero = models.ForeignKey(Genero, on_delete=models.CASCADE)
    edicion = models.IntegerField(blank=True, null=True)
    editorial = models.CharField(blank=True, max_length=250)
    dvd = models.BooleanField(default=False)

    def __unicode__(self):
            return self.titulo

class Prestamo(models.Model):
    """( Prestamo Recursos)"""
    fechaInicio = models.DateField(default=datetime.datetime.today)
    fechaFin = models.DateField(default=datetime.datetime.today()+datetime.timedelta(days=7)) #por defecto 7 dias despues de la creacion
    Recurso = models.OneToOneField(Recurso,unique=True)
    usuarioPrestamo = models.OneToOneField(Usuario,unique=True)

    def __unicode__(self):
        return self.fechaFin.strftime("%Y-%m-%d")+" "+self.Recurso.__unicode__()+" "+self.usuarioPrestamo.__unicode__()
<commit_msg>cambio modelo<commit_after>#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import models
from django.template.defaultfilters import slugify
import datetime
from django.core.validators import RegexValidator

class Genero(models.Model):
    nombre = models.CharField(blank=False, max_length=100,primary_key=True,unique=True)

    def get(self):
        return nombre

    def __unicode__(self):
            return self.nombre

class Usuario(models.Model):
    nombre = models.CharField(blank=False, max_length=15)
    apellidos = models.CharField(blank=False, max_length=30)
    phone_regex = RegexValidator(regex=r'^\d{9,15}$', message="Telefono debe ser: 123456789.")
    telefono = models.CharField(validators=[phone_regex], blank=True,max_length=15) # validators should be a list
    direccion = models.CharField(blank=True,max_length=100)

    def __unicode__(self):
            return self.nombre+" "+self.apellidos

class Recurso(models.Model):
    titulo = models.CharField(blank=False, max_length=100)
    autor = models.CharField(blank=False, max_length=100)
    descripcion = models.CharField(blank=True, max_length=250)
    codigo = models.CharField(blank=True, max_length=100)
    estanteria = models.CharField(blank=True, max_length=100)
    genero = models.ForeignKey(Genero, on_delete=models.CASCADE)
    edicion = models.IntegerField(blank=True, null=True)
    editorial = models.CharField(blank=True, max_length=250)
    dvd = models.BooleanField(default=False)

    def __unicode__(self):
            return self.titulo

class Prestamo(models.Model):
    """( Prestamo Recursos)"""
    fechaInicio = models.DateField(default=datetime.datetime.today)
    fechaFin = models.DateField(default=datetime.datetime.today()+datetime.timedelta(days=7)) #por defecto 7 dias despues de la creacion
    Recurso = models.OneToOneField(Recurso,unique=True)
    usuarioPrestamo = models.OneToOneField(Usuario,unique=True)

    def __unicode__(self):
        return self.fechaFin.strftime("%Y-%m-%d")+" "+self.Recurso.__unicode__()+" "+self.usuarioPrestamo.__unicode__()
<|endoftext|>