Beispiel #1
0
class Geography(ApiModel):
    """Geography model."""

    __tablename__ = 'geography'
    id = db.Column(db.Integer, primary_key=True)
    label_id = db.Column(db.Integer, db.ForeignKey('english_string.id'))
    order = db.Column(db.Integer, unique=True)
    subheading_id = db.Column(db.Integer, db.ForeignKey('english_string.id'))
    type = db.Column(db.String)
    code = db.Column(db.String, unique=True)

    label = db.relationship('EnglishString', foreign_keys=label_id)
    subheading = db.relationship('EnglishString', foreign_keys=subheading_id)

    # TODO (2017-08-29 jkp): Include country backref? Maybe? Delete if a lot of
    # time has passed and no need for this feature

    def __init__(self, **kwargs):
        """Initialize instance of model.

        Does a few things: (1) Updates instance based on mapping from API query
        parameter names to model field names, and (2) calls super init.
        """
        self.update_kwargs_english(kwargs, 'label', 'label_id')
        self.update_kwargs_english(kwargs, 'subheading', 'subheading_id')
        super(Geography, self).__init__(**kwargs)

    @staticmethod
    def none_json(jns=False):
        """Return dictionary ready to convert to JSON as response.

        All values in this dictionary are set to none, serving the cases where
        no data is found or needs to be supplied.

        Args:
            jns (bool): If true, namespaces all dictionary keys with prefixed
            table name.

        Returns:
            dict: API response ready to be JSONified.
        """
        result = {'id': None, 'label': None}
        if jns:
            result = ApiModel.namespace(result, 'geography')
        return result

    def __repr__(self):
        """Return a representation of this object."""
        return '<Geography "{}">'.format(self.label.english)
Beispiel #2
0
class User(db.Model, UserMixin):
    """User

    Regarding collation: The collation='NOCASE' is required to search case
    insensitively when USER_IFIND_MODE is 'nocase_collation'.

    Example:
        username = db.Column(db.String(100, collation='NOCASE'), ...)

    Warnings:
        PostgreSQL does not allow for (utf-8) 'NOCASE' collation.
    """

    __tablename__ = 'user'
    id = db.Column(db.Integer, primary_key=True)
    active = db.Column('is_active',
                       db.Boolean(),
                       nullable=False,
                       server_default='1')
    username = db.Column(db.String(100), nullable=False, unique=True)
    password = db.Column(db.String(255), nullable=False, server_default='')
    email_confirmed_at = db.Column(db.DateTime())
    first_name = db.Column(db.String(100), nullable=False, server_default='')
    last_name = db.Column(db.String(100), nullable=False, server_default='')
Beispiel #3
0
class Task(db.Model):
    """Tasks

    Attribute 'id' is not auto-generated / auto-incremented, but is actually
    a unique task ID assigned by celery.
    """
    __tablename__ = 'task'

    id = db.Column(db.String, primary_key=True)
    is_active = db.Column('is_active', db.Boolean(), nullable=False)

    def __init__(self, task_id: str, is_active: bool = False):
        """Task init"""
        self.id = task_id
        self.is_active = is_active

    @classmethod
    def register_active(cls, task_id: str):
        """Register task as active

        Side effects:
            - Adds record to DB if doesn't exist
            - Modifies record
        """
        with app.app_context():
            task: Task = cls.query.filter_by(id=task_id).first()
            if not task:
                task = cls(task_id=task_id, is_active=True)
                db.session.add(task)
            else:
                task.is_active = True
            db.session.commit()

    @classmethod
    def register_inactive(cls, task_id: str):
        """Register task as inactive

        Side effects:
            - Modifies record
        """
        with app.app_context():
            task: Task = cls.query.filter_by(id=task_id).first()
            task.is_active = False
            db.session.commit()

    @classmethod
    def get_present_tasks(cls, validate: bool = True, update: bool = True) \
            -> List[str]:
        """Get list of IDs for active tasks

        Side effects:
            - Modifies records if update arg is True

        Args:
            validate (bool): If True, will query task queue message broker to
             see if tasks marked as active in the PMA API db are in fact
             correctly marked as such. If update arg is True, validation will
             also run validation even if the arg validate is False.
            update (bool): If True, will: (1) also set validation to True, (2)
             also modify records to correctly mark them as inactive if they
             fail to validate as active tasks.

        TODO 2019.04.15-jef: Ideally, we want to use a more standard way to get
         a list of present (active/scheduled/reserved tasks). Unfortunately,
         there are some issues making this difficult in Celery 4. Presently,
         best solution seems to be either: a. downgrade to Celery 3, or b. use
         rabbitmq-admin available on pip. Useful link: https://stackoverflow.
         com/questions/5544629/retrieve-list-of-tasks-in-a-queue-in-celery

        Returns:
            list(str): Present tasks
        """
        from pma_api.task_utils import validate_active_task_status

        validation: bool = True if update or validate else False
        with app.app_context():
            all_tasks: List[Task] = cls.query.all()
            tasks: List[Task] = [x for x in all_tasks if x.is_active]
        actually_inactive_tasks: List[Task] = [] if not validation else \
            [x for x in tasks if not validate_active_task_status(x.id)]

        if update:
            for x in actually_inactive_tasks:
                x.is_active = False
            with app.app_context():
                db.session.commit()

        task_ids: List[str] = [x.id for x in tasks] if not validation else \
            [x.id for x in tasks if x not in actually_inactive_tasks]

        return task_ids
Beispiel #4
0
class ApiMetadata(db.Model):
    """Metadata."""

    __tablename__ = 'api_metadata'
    id = db.Column(db.Integer, autoincrement=True, primary_key=True)
    name = db.Column(db.String)
    type = db.Column(db.String, index=True)
    md5_checksum = db.Column(db.String)
    blob = db.Column(db.LargeBinary)
    created_on = db.Column(db.DateTime,
                           default=db.func.now(),
                           onupdate=db.func.now(),
                           index=True)

    def __init__(self, path):
        """Metadata init."""
        filename = os.path.splitext(os.path.basename(path))[0]
        self.name = filename
        if filename.startswith('api'):
            self.type = 'api'
        elif filename.startswith('ui'):
            self.type = 'ui'
        with open(path, 'rb') as file:
            self.blob = file.read()
        self.md5_checksum = md5(self.blob).hexdigest()

    @classmethod
    def get_record(cls, ui_or_api: str, as_json: bool = False) \
            -> Union[Dict, Any]:
        """Return the record for the most recent API/UI data.

        Args:
            as_json (bool): Return as dictionary to be serialized into JSON?
            ui_or_api (str): Dataset type; valid values: ('ui', 'api')

        Returns:
            Union[Dict, db.Model]: Record
        """
        record = cls.query.filter_by(type=ui_or_api).first()  # db.Model

        result: Union[Dict, Any] = record if not as_json else {
            'id': str(record.id),
            'name': str(record.name),
            'type': str(record.type),
            'created_on': str(record.created_on),
            'md5_checksum': str(record.md5_checksum)
        }

        return result

    @classmethod
    def get_current_api_data(cls, as_json: bool = False) -> Union[Dict, Any]:
        """Return the record for the most recent API data.

        Args:
            as_json (bool): Return as dictionary to be serialized into JSON?

        Returns:
            Union[Dict, db.Model]: Record
        """
        return cls.get_record(ui_or_api='api', as_json=as_json)

    @classmethod
    def get_current_ui_data(cls, as_json: bool = False) -> Union[Dict, Any]:
        """Return the record for the most recent UI data.

        Args:
            as_json (bool): Return as dictionary to be serialized into JSON?

        Returns:
            Union[Dict, db.Model]: Record
        """
        return cls.get_record(ui_or_api='ui', as_json=as_json)

    def to_json(self):
        """Return dictionary ready to convert to JSON as response.

        Returns:
            dict: API response ready to be JSONified.
        """
        result = {
            'name': self.name,
            'hash': self.md5_checksum,
            'type': self.type,
            'createdOn': self.created_on
        }
        return result
Beispiel #5
0
class Cache(db.Model):
    """Cache for API responses."""

    __tablename__ = 'cache'
    key = db.Column(db.String, primary_key=True)
    value = db.Column(db.String, nullable=False)
    mimetype = db.Column(db.String)
    source_data_md5 = db.Column(db.String)

    # TODO: Should be generalized function for all routes
    @staticmethod
    def cache_route(route: str, app: Flask = current_app):
        """Add route to the server cache

        This method checks the cache. If there is nothing cached or if the md5s
        do not match, then a new cached response is generated and saved.

        Args:
            route (str): route to cache
            app (Flask): The Flask app. There must be a current app context.
        """
        source_data_md5 = ApiMetadata.get_current_api_data().md5_checksum
        # example route: 'datalab_init': 'v1/datalab/init'
        # TODO: use 'cache_datalab_init' as template
        print(route, source_data_md5, app)

    @staticmethod
    def cache_datalab_init(app: Flask = current_app):
        """Add /v1/datalab/init to the server cache.

        This method checks the cache. If there is nothing cached or if the md5s
        do not match, then a new cached response is generated and saved.

        Args:
            app (Flask): The Flask app. There must be a current app context.
        """
        source_data_md5 = ApiMetadata.get_current_api_data().md5_checksum
        current_cache = Cache.get(REFERENCES['routes']['datalab_init'])

        if not current_cache \
                or current_cache.source_data_md5 != source_data_md5:
            url = url_for('api.get_datalab_init')
            headers = {'X-Requested-With': 'XMLHttpRequest'}
            with app.test_request_context(url, headers=headers):
                api_result = \
                    app.view_functions['api.get_datalab_init'](cached=False)
                response = api_result.to_response()
                value = response.get_data(as_text=True).strip()
                mimetype = response.mimetype
                if current_cache:
                    current_cache.value = value
                    current_cache.mimetype = mimetype
                    current_cache.source_data_md5 = source_data_md5
                else:
                    new_cache = Cache(key=REFERENCES['routes']['datalab_init'],
                                      value=value,
                                      mimetype=mimetype,
                                      source_data_md5=source_data_md5)
                    db.session.add(new_cache)
                db.session.commit()

    @classmethod
    def get(cls, key):
        """Return a record by key."""
        return cls.query.filter_by(key=key).first()

    def __repr__(self):
        """Give a representation of this record."""
        return "<Cache key='{}'>".format(self.key)
Beispiel #6
0
class EnglishString(db.Model):
    """EnglishString model."""

    __tablename__ = 'english_string'
    id = db.Column(db.Integer, primary_key=True)
    code = db.Column(db.String, unique=True)
    english = db.Column(db.String, nullable=False)
    translations = db.relationship('Translation')

    def to_string(self, lang=None):
        """Return string in specified language if supplied, else English.

        Args:
            lang (str): The language, if specified.

        Returns:
            str: The text.
        """
        result = self.english
        if lang is not None and lang.lower() != 'en':
            lang = lang.lower()
            gen = iter(t for t in self.translations if t.language_code == lang)
            found = next(gen, None)
            if found is not None:
                result = found.translation
        return result

    def to_json(self):
        """Return dictionary ready to convert to JSON as response.

        Contains URL for resource entity.

        Returns:
            dict: API response ready to be JSONified.
        """
        json_obj = {
            # 'url': url_for('api.get_text', code=self.code, _external=True),
            'id': self.code,
            'text': self.english,
            'langCode': 'en'
        }
        return json_obj

    @staticmethod
    def insert_or_update(english, code):
        """Insert or update an English record.

        Args:
            english (str): The string in English to insert.
            code (str): The code for the string.

        Returns:
            The EnglishString record inserted or updated.
        """
        record = EnglishString.query.filter_by(code=code).first()
        if record and record.english != english:
            record.english = english
            # TODO: Resolve - Committing causes slow, but remove causes error
            db.session.add(record)
            db.session.commit()
        elif not record:
            record = EnglishString.insert_unique(english, code)
        return record

    @staticmethod
    def insert_unique(english, code=None):
        """Insert a unique record into the database.

        Creates a code and combines with English text to as the parameters for
        new record.

        Args:
            english (str): The string in English to insert.
            code (str): The code for the string. None if it should be random.

        Returns:
            The new EnglishString record.
        """
        # TODO: (jkp 2017-08-29) This is not necessary because next64 now
        # returns unique. Needs: Nothing.
        if code is None:
            code = next64()
        record = EnglishString(code=code, english=english)
        db.session.add(record)
        # TODO: Resolve - Committing causes slow, but remove causes error
        db.session.commit()
        return record

    def datalab_init_json(self):
        """Datalab init json: EnglishString."""
        this_dict = {'en': self.english}
        for translation in self.translations:
            this_dict[translation.language_code] = translation.translation
        to_return = {self.code: this_dict}
        return to_return

    def __repr__(self):
        """Return a representation of this object."""
        if len(self.english) < 20:
            preview = '{}...'.format(self.english[:17])
        else:
            preview = self.english
        return '<EnglishString {} "{}">'.format(self.code, preview)
Beispiel #7
0
class Translation(db.Model):
    """Translation model."""

    __tablename__ = 'translation'
    id = db.Column(db.Integer, primary_key=True)
    english_id = db.Column(db.Integer, db.ForeignKey('english_string.id'))
    language_code = db.Column(db.String, nullable=False)
    translation = db.Column(db.String, nullable=False)
    languages_info = {
        'english': {
            'code': 'en',
            'label': 'English',
            'active': True,
            'string_records': 'english'
        },
        'french': {
            'code': 'fr',
            'label': 'French',
            'active': True,
            'string_records': 'To be implemented.'
        }
    }

    def __init__(self, **kwargs):
        """Initialize instance of model.

        Does a few things: (1) Gets english code if it is already supplied and
        creates a record in EnglishString. This happens when inserting a
        record for UI data. Otherwise, gets the english code and (2) Calls
        super init.
        """
        self.prune_ignored_fields(kwargs)
        if kwargs.get('english_code'):
            english = EnglishString.insert_or_update(
                kwargs['english'], kwargs['english_code'].lower())
            kwargs.pop('english_code')
        else:
            english = EnglishString.query.filter_by(english=kwargs['english'])\
                .first()
        try:
            kwargs['english_id'] = english.id
        except AttributeError:
            new_record = EnglishString.insert_unique(kwargs['english'])
            kwargs['english_id'] = new_record.id

        kwargs.pop('english')
        super(Translation, self).__init__(**kwargs)

    @staticmethod
    def prune_ignored_fields(kwargs):
        """Prune ignored fields.

        Args:
            kwargs (dict): Keyword arguments.
        """
        from pma_api.models.api_base import prune_ignored_fields
        prune_ignored_fields(kwargs)

    @staticmethod
    def languages():
        """Languages list."""
        languages = {
            v['code']: v['label']
            for _, v in Translation.languages_info.items()
        }
        return languages

    def __repr__(self):
        """Return a representation of this object."""
        if len(self.translation) < 20:
            preview = '{}...'.format(self.translation[:17])
        else:
            preview = self.translation
        return '<Translation ({}) "{}">'.format(self.language_code, preview)
Beispiel #8
0
class Indicator(ApiModel):
    """Indicator model."""

    __tablename__ = 'indicator'
    id = db.Column(db.Integer, primary_key=True)
    code = db.Column(db.String, unique=True)
    label_id = db.Column(db.Integer,
                         db.ForeignKey('english_string.id'),
                         nullable=False)
    order = db.Column(db.Integer, unique=True)
    type = db.Column(db.String)
    definition_id = db.Column(db.Integer,
                              db.ForeignKey('english_string.id'),
                              nullable=False)
    level1_id = db.Column(db.Integer,
                          db.ForeignKey('english_string.id'),
                          nullable=False)
    level2_id = db.Column(db.Integer,
                          db.ForeignKey('english_string.id'),
                          nullable=False)
    domain_id = db.Column(db.Integer,
                          db.ForeignKey('english_string.id'),
                          nullable=False)
    # TODO: (jkp 2017-08-29) Should this be a translated string?
    # Needs: Nothing?
    denominator = db.Column(db.String)
    measurement_type = db.Column(db.String)
    is_favorite = db.Column(db.Boolean)
    favorite_order = db.Column(db.Integer, unique=True)

    label = db.relationship('EnglishString', foreign_keys=label_id)
    definition = db.relationship('EnglishString', foreign_keys=definition_id)
    level1 = db.relationship('EnglishString', foreign_keys=level1_id)
    level2 = db.relationship('EnglishString', foreign_keys=level2_id)
    domain = db.relationship('EnglishString', foreign_keys=domain_id)

    def __init__(self, **kwargs):
        """Initialize instance of model.

        Does a few things: (1) Updates instance based on mapping from API query
        parameter names to model field names, (2) Reformats any empty strings,
        and (3) Calls super init.
        """
        kwargs['is_favorite'] = bool(kwargs['is_favorite'])
        self.update_kwargs_english(kwargs, 'level1', 'level1_id')
        self.update_kwargs_english(kwargs, 'level2', 'level2_id')
        self.update_kwargs_english(kwargs, 'domain', 'domain_id')
        self.update_kwargs_english(kwargs, 'definition', 'definition_id')
        self.update_kwargs_english(kwargs, 'label', 'label_id')
        super(Indicator, self).__init__(**kwargs)

    def full_json(self, lang=None, jns=False, endpoint=None):
        """Return dictionary ready to convert to JSON as response.

        This response contains fields of 1 or more related
        model(s) which are included along with fields for this model.

        Args:
            lang (str): The language, if specified.
            jns (bool): If true, namespaces all dictionary keys with prefixed
            table name, e.g. indicator.id.
            endpoint (str): If supplied, provides URL for entity in response.

        Returns:
            dict: API response ready to be JSONified.
        """
        result = {
            'id': self.code,
            'order': self.order,
            'type': self.type,
            'denominator': self.denominator,
            'measurementType': self.measurement_type,
            'isFavorite': self.is_favorite,
            'favoriteOrder': self.favorite_order
        }

        label_str = self.label.to_string(lang)
        defn_str = self.definition.to_string(lang)
        level1_str = self.level1.to_string(lang)
        level2_str = self.level2.to_string(lang)
        domain_str = self.domain.to_string(lang)

        result['label'] = label_str
        result['definition'] = defn_str
        result['level1'] = level1_str
        result['level2'] = level2_str
        result['domain'] = domain_str

        if endpoint is not None:
            result['url'] = url_for(endpoint, code=self.code, _external=True)

        if jns:
            result = self.namespace(result, 'indicator')

        return result

    def __repr__(self):
        """Return a representation of this object."""
        return '<Indicator "{}">'.format(self.code)

    def datalab_init_json(self):
        """Datalab init json: Indicator."""
        to_return = {
            'id': self.code,
            'label.id': self.label.code,
            'definition.id': self.definition.code,
            'type': self.type
        }
        return to_return
Beispiel #9
0
class Country(ApiModel):
    """Country model."""

    __tablename__ = 'country'
    id = db.Column(db.Integer, primary_key=True)
    label_id = db.Column(db.Integer, db.ForeignKey('english_string.id'))
    order = db.Column(db.Integer, unique=True)
    subregion = db.Column(db.String)
    region = db.Column(db.String)
    code = db.Column(db.String, unique=True)

    label = db.relationship('EnglishString', foreign_keys=label_id)

    api_schema = {  # size_min & size_max currently unused.
        'fields': {
            'id': {
                'restrictions': {
                    'type': str,
                    'size_min': 1,
                    'size_max': None,
                    'queryable': True
                },
            },
            'label': {
                'restrictions': {
                    'type': str,
                    'size_min': 1,
                    'size_max': None,
                    'queryable': True
                }
            },
            'order': {
                'restrictions': {
                    'type': int,
                    'size_min': 1,
                    'size_max': None,
                    'queryable': False
                }
            },
            'region': {
                'restrictions': {
                    'type': str,
                    'size_min': 1,
                    'size_max': None,
                    'queryable': True
                }
            },
            'subregion': {
                'restrictions': {
                    'type': str,
                    'size_min': 1,
                    'size_max': None,
                    'queryable': True
                }
            }
        }
    }

    # def __init__(self, label_id, order, ):
    def __init__(self, **kwargs):
        """Initialize instance of model.

        Does a few things: (1) Updates instance based on mapping from API query
        parameter names to model field names, and (2) calls super init.

        Example Usage:
            new_country = Country(
                label_id='Burkina Faso',
                order='1',
                subregion='West Africa',
                region='Africa',
                code='BF'
            )
        """
        self.update_kwargs_english(kwargs, 'label', 'label_id')
        super(Country, self).__init__(**kwargs)

    @staticmethod
    def validate_param_types(request_args):
        """Validate query parameter types.

        Args:
            request_args (ImmutableMultiDict): API query parameters.

        Returns
            bool: True if valid param types, else false.
        """
        # TODO: (jef 2017-08-29) Support other types?: lists, dict.
        # Needs: Nothing?
        flds = Country.api_schema['fields']
        typed_params = {
            key: {
                'value':
                val,
                'type':
                None if val == '' else int if val.isdigit() else
                float if '.' in val and val.replace('.', '', 1).isdigit() else
                bool if val.lower() in ('false', 'true') else str
            }
            for key, val in request_args.items()
        }
        if False in [
                val['type'] == flds[key]['restrictions']['type']
                for key, val in typed_params.items() if key in flds
                if flds[key]['restrictions']['queryable']
        ]:
            return False
        return True

    # TODO: (jef 2017-08-29) Insert violation in error message for methods:
    # validate_keys(), validate_queryable(), validate_types().
    # Needs: Validation to be decided on.
    @staticmethod
    def validate_keys(request_args):
        """Validate whether query parameters passed even exist to be queried.

        Args:
            request_args (ImmutableMultiDict): API query parameters.

        Returns:
            tuple: (bool: Validity, str: Error message)
        """
        msg = 'One or more invalid query parameter was passed.'
        flds = Country.api_schema['fields']
        if True in [key not in flds for key in request_args]:
            return False, msg
        return True, ''

    @staticmethod
    def validate_queryable(request_args):
        """Validate whether query parameters are allowed to be queried.

        Args:
            request_args (ImmutableMultiDict): API query parameters.

        Returns:
            tuple: (bool: Validity, str: Error message)
        """
        flds = Country.api_schema['fields']
        if True in [
                not flds[key]['restrictions']['queryable']
                for key in request_args if key in flds
        ]:
            return False, 'One or more query params passed is not queryable.'
        return True, ''

    @staticmethod
    def validate_types(request_args):
        """Validate whether query parameter types are correct.

        Args:
            request_args (ImmutableMultiDict): API query parameters.

        Returns:
            tuple: (bool: Validity, str: Error message)
        """
        if not Country.validate_param_types(request_args):
            return False, 'One or more types for query parameters was invalid.'
        return True, ''

    @staticmethod
    def validate_query(request_args):
        """Validate query.

        Args:
            request_args (ImmutableMultiDict): API query parameters.

        Returns:
            bool: True if valid query, else false.
            lit: List of error message strings.
        """
        # TODO: (jef 2017-08.29) Decide on letting the user know if the query
        # was invalid, either in its own response or at the top along with
        # results if we choose to return results when part of the query was
        # invalid. Needs: Validation to be decided on.
        validation_funcs = [
            Country.validate_keys, Country.validate_queryable,
            Country.validate_types
        ]
        validities = [func(request_args) for func in validation_funcs]

        return \
            False if False in [status for status, _ in validities] else True, \
            list(filter(None, [messages for _, messages in validities]))

    def url_for(self):
        """Supply URL for resource entity.

        Returns:
            dict: Dict of key 'url' and value of URL for resource entity.
        """
        return {
            'url': url_for('api.get_country', code=self.code, _external=True)
        }

    def full_json(self, lang=None, jns=False):
        """Return dictionary ready to convert to JSON as response.

        Args:
            lang (str): The language, if specified.
            jns (bool): If true, namespaces all dictionary keys with prefixed
            table name.

        Returns:
            dict: API response ready to be JSONified.
        """
        result = {
            'id': self.code,
            'order': self.order,
            'subregion': self.subregion,
            'region': self.region,
        }
        # TODO: (jkp 2017-08-29) is it possble that label is null?
        # Needs: Nothing.
        label = self.label.to_string(lang)
        result['label'] = label

        if jns:
            result = self.namespace(result, 'country')

        return result

    def to_json(self, lang=None):
        """Return dictionary ready to convert to JSON as response.

        Contains URL for resource entity.

        Args:
            lang (str): The language, if specified.

        Returns:
            dict: API response ready to be JSONified.
        """
        json_obj = {
            'url': url_for('api.get_country', code=self.code, _external=True),
            'order': self.order,
            'subregion': self.subregion,
            'region': self.region,
            'countryCode': self.code
        }
        if lang is None or lang.lower() == 'en':
            json_obj['label'] = self.label.english
        else:
            lang = lang.lower()
            translations = self.label.translations
            gen = iter(t for t in translations if t.language_code == lang)
            translation = next(gen, None)
            if translation:
                json_obj['label'] = translation.translation
            else:
                json_obj['label'] = url_for('api.get_text',
                                            code=self.label.code,
                                            _external=True)
        return json_obj

    def __repr__(self):
        """Return a representation of this object."""
        return '<Country "{}">'.format(self.code)
Beispiel #10
0
class Survey(ApiModel):
    """Survey model."""

    __tablename__ = 'survey'
    id = db.Column(db.Integer, primary_key=True)
    label_id = db.Column(db.Integer,
                         db.ForeignKey('english_string.id'),
                         nullable=False)
    order = db.Column(db.Integer, unique=True)
    type = db.Column(db.String)
    year = db.Column(db.Integer)
    round = db.Column(db.Integer)
    start_date = db.Column(db.DateTime)
    end_date = db.Column(db.DateTime)
    code = db.Column(db.String, unique=True)
    pma_code = db.Column(db.String)
    country_id = db.Column(db.Integer, db.ForeignKey('country.id'))
    geography_id = db.Column(db.Integer, db.ForeignKey('geography.id'))
    partner_id = db.Column(db.Integer, db.ForeignKey('english_string.id'))

    label = db.relationship('EnglishString', foreign_keys=label_id)
    country = db.relationship('Country')
    geography = db.relationship('Geography')
    partner = db.relationship('EnglishString', foreign_keys=partner_id)

    def url_for(self):
        """Supply URL for resource entity.

        Returns:
            dict: Dict of key 'url' and value of URL for resource entity.
        """
        return {
            'url': url_for('api.get_survey',
                           code=self.pma_code,
                           _external=True)
        }

    def full_json(self, lang=None, jns=False):
        """Return dictionary ready to convert to JSON as response.

        This response contains fields of 1 or more related
        model(s) which are included along with fields for this model.

        Args:
            lang (str): The language, if specified.
            jns (bool): If true, namespaces all dictionary keys with prefixed
            table name.

        Returns:
            dict: API response ready to be JSONified.
        """
        result = {
            'order': self.order,
            'type': self.type,
            'year': self.year,
            'round': self.round,
            'start_date': self.start_date.date().isoformat(),
            'end_date': self.end_date.date().isoformat(),
            'id': self.code,
            'pma_code': self.pma_code,
        }

        if jns:
            result = self.namespace(result, 'survey')

        country_json = self.country.full_json(lang=lang, jns=True)

        result.update(country_json)
        return result

    def datalab_init_json(self, reduced: bool = True):
        """Datalab init json: Survey

        Args:
            reduced (bool): Return more information if true
        """
        to_return = {
            'id': self.code,
            'partner.label.id': self.partner.code,
            'label.id': self.label.code,
        }
        if not reduced:
            to_return.update({
                'geography.label.id': self.geography.subheading.code,
                'country.label.id': self.country.label.code
            })
        return to_return

    def __init__(self, **kwargs):
        """Initialize instance of model.

        Does a few things: (1) Removes unnecessary fields, (2) Converts API
        query parameters to model field name equivalents, (3) Inserts new
        values into the EnglishString translation table if not present, and
        (4) calls super init.

        Raises:
            AttributeError: If valid ID is not found for Country.
        """
        self.update_kwargs_english(kwargs, 'label', 'label_id')
        self.update_kwargs_english(kwargs, 'partner', 'partner_id')
        self.update_kwargs_date(kwargs, 'start_date', '%m-%Y')
        self.update_kwargs_date(kwargs, 'end_date', '%m-%Y')
        self.set_kwargs_id(kwargs,
                           'country_code',
                           'country_id',
                           Country,
                           required=True)
        self.set_kwargs_id(kwargs,
                           'geography_code',
                           'geography_id',
                           Geography,
                           required=False)
        super(Survey, self).__init__(**kwargs)

    def __repr__(self):
        """Return a representation of this object."""
        return '<Survey "{}">'.format(self.code)
Beispiel #11
0
class Data(ApiModel):
    """Data model."""

    __tablename__ = 'datum'
    id = db.Column(db.Integer, primary_key=True)
    code = db.Column(db.String, unique=True)
    value = db.Column(db.Float, nullable=False)
    lower_ci = db.Column(db.Float)
    upper_ci = db.Column(db.Float)
    level_ci = db.Column(db.Float)
    precision = db.Column(db.Integer)
    is_total = db.Column(db.Boolean)
    denom_w = db.Column(db.Float)
    denom_uw = db.Column(db.Float)

    survey_id = db.Column(db.Integer, db.ForeignKey('survey.id'))
    indicator_id = db.Column(db.Integer, db.ForeignKey('indicator.id'))
    char1_id = db.Column(db.Integer, db.ForeignKey('characteristic.id'))
    char2_id = db.Column(db.Integer, db.ForeignKey('characteristic.id'))
    geo_id = db.Column(db.Integer, db.ForeignKey('geography.id'))

    survey = db.relationship('Survey', foreign_keys=survey_id)
    indicator = db.relationship('Indicator', foreign_keys=indicator_id)
    char1 = db.relationship('Characteristic', foreign_keys=char1_id)
    char2 = db.relationship('Characteristic', foreign_keys=char2_id)
    geo = db.relationship('Geography', foreign_keys=geo_id)

    def __init__(self, **kwargs):
        """Initialize instance of model.

        Does a few things: (1) Updates instance based on mapping from API query
        parameter names to model field names, (2) Reformats any empty strings,
        (3) Sets a randomly generated code string, and (4) Calls super init.
        """
        kwargs_copy = copy(kwargs)
        if kwargs:
            kwargs['is_total'] = bool(kwargs['is_total'])
            self.set_kwargs_id(kwargs, 'survey_code', 'survey_id', Survey)
            try:
                self.set_kwargs_id(kwargs, 'indicator_code', 'indicator_id',
                                   Indicator)
                self.set_kwargs_id(kwargs, 'char1_code', 'char1_id',
                                   Characteristic, False)
                self.set_kwargs_id(kwargs, 'char2_code', 'char2_id',
                                   Characteristic, False)
                self.empty_to_none(kwargs)
            except KeyError as err:
                msg = """
                {}

                Error occured while trying to store the following data:
                {}
                """.format(
                    str(err), ', '.join([
                        '{}: {}'.format(k, v) for k, v in kwargs_copy.items()
                    ]))
                raise KeyError(msg)
            kwargs['code'] = next64()
            super(Data, self).__init__(**kwargs)

    def full_json(self, lang=None, jns=False):
        """Return dictionary ready to convert to JSON as response.

        This response contains fields of 1 or more related
        model(s) which are included along with fields for this model.

        Args:
            lang (str): The language, if specified.
            jns (bool): If true, namespaces all dictionary keys with prefixed
            table name.

        Returns:
            dict: API response ready to be JSONified.
        """
        result = {
            'id': self.code,
            'value': self.value,
            'lowerCi': self.lower_ci,
            'upperCi': self.upper_ci,
            'levelCi': self.level_ci,
            'precision': self.precision,
            'isTotal': self.is_total,
            'denominatorWeighted': self.denom_w,
            'denominatorUnweighted': self.denom_uw,
        }

        if jns:
            result = self.namespace(result, 'data')

        survey_json = self.survey.full_json(lang=lang, jns=True)
        indicator_json = self.indicator.full_json(lang=lang, jns=True)
        if self.char1 is not None:
            char1_json = self.char1.full_json(lang, jns=True, index=1)
        else:
            char1_json = Characteristic.none_json(jns=True, index=1)
        if self.char2 is not None:
            char2_json = self.char2.full_json(lang, jns=True, index=2)
        else:
            char2_json = Characteristic.none_json(jns=True, index=2)
        if self.geo is not None:
            geo_json = self.geo.full_json(lang, jns=True)
        else:
            geo_json = Geography.none_json(jns=True)

        result.update(survey_json)
        result.update(indicator_json)
        result.update(char1_json)
        result.update(char2_json)
        result.update(geo_json)

        return result

    def __repr__(self):
        """Return a representation of this object."""
        return '<Data "{}">'.format(self.code)
Beispiel #12
0
class Characteristic(ApiModel):
    """Characteristic model."""

    __tablename__ = 'characteristic'
    id = db.Column(db.Integer, primary_key=True)
    code = db.Column(db.String, unique=True)
    label_id = db.Column(db.Integer, db.ForeignKey('english_string.id'))
    order = db.Column(db.Integer, unique=True)
    char_grp_id = \
        db.Column(db.Integer, db.ForeignKey('characteristic_group.id'))

    char_grp = db.relationship('CharacteristicGroup')
    label = db.relationship('EnglishString', foreign_keys=label_id)

    def __init__(self, **kwargs):
        """Initialize instance of model.

        Does a few things: (1) Removes unnecessary fields, (2) Converts API
        query parameters to model field name equivalents, (3) Inserts new
        values into the EnglishString translation table if not present, and
        (4) calls super init.

        Raises:
            AttributeError: If valid ID is not found for CharacteristicGroup.
        """
        self.update_kwargs_english(kwargs, 'label', 'label_id')
        self.set_kwargs_id(kwargs, 'char_grp_code', 'char_grp_id',
                           CharacteristicGroup)
        super(Characteristic, self).__init__(**kwargs)

    def full_json(self, lang=None, jns=False, index=None):
        """Return dictionary ready to convert to JSON as response.

        This response contains fields of 1 or more related
        model(s) which are included along with fields for this model.

        Args:
            lang (str): The language, if specified.
            jns (bool): If true, namespaces all dictionary keys with prefixed
            table name.
            index (int): Field index for fields that have multiple instances of
            itself, e.g. "characteristic1", "characteristic2".

        Returns:
            dict: API response ready to be JSONified.
        """
        result = {
            'id': self.code,
            'order': self.order,
            'label': self.label.to_string(lang)
        }

        if jns:
            result = self.namespace(result, 'char', index=index)

        char_grp_json = \
            self.char_grp.full_json(lang=lang, jns=True, index=index)

        result.update(char_grp_json)
        return result

    def __repr__(self):
        """Return a representation of this object."""
        return '<Characteristic "{}">'.format(self.code)

    def datalab_init_json(self):
        """Datalab init json: Characteristic."""
        to_return = {
            'id': self.code,
            'label.id': self.label.code,
            'order': self.order,
        }
        return to_return

    @staticmethod
    def none_json(jns=False, index=None):
        """Return dictionary ready to convert to JSON as response.

        All values in this dictionary are set to none, serving the cases where
        no data is found or needs to be supplied.

        This response contains fields of 1 or more related
        model(s) which are included along with fields for this model.

        Args:
            jns (bool): If true, namespaces all dictionary keys with prefixed
            table name.
            index (int): Field index for fields that have multiple instances of
            itself, e.g. "characteristic1", "characteristic2".

        Returns:
            dict: API response ready to be JSONified.
        """
        result = {
            'id': None,
            'order': None,
            'label': None,
        }
        if jns:
            result = ApiModel.namespace(result, 'char', index=index)
        char_grp_json = \
            CharacteristicGroup.none_json(jns=True, index=index)
        result.update(char_grp_json)
        return result
Beispiel #13
0
class CharacteristicGroup(ApiModel):
    """CharacteristicGroup model."""

    __tablename__ = 'characteristic_group'
    id = db.Column(db.Integer, primary_key=True)
    code = db.Column(db.String, unique=True)
    label_id = db.Column(db.Integer, db.ForeignKey('english_string.id'))
    definition_id = db.Column(db.Integer, db.ForeignKey('english_string.id'))
    order = db.Column(db.Integer, unique=True)
    category_id = db.Column(db.Integer, db.ForeignKey('english_string.id'))

    label = db.relationship('EnglishString', foreign_keys=label_id)
    definition = db.relationship('EnglishString', foreign_keys=definition_id)
    category = db.relationship('EnglishString', foreign_keys=category_id)

    def __init__(self, **kwargs):
        """Initialize instance of model.

        Does a few things: (1) Removes unnecessary fields, (2) Converts API
        query parameters to model field name equivalents, (3) Inserts new
        values into the EnglishString translation table if not present, and
        (4) calls super init.
        """
        self.update_kwargs_english(kwargs, 'label', 'label_id')
        self.update_kwargs_english(kwargs, 'definition', 'definition_id')
        self.update_kwargs_english(kwargs, 'category', 'category_id')
        super(CharacteristicGroup, self).__init__(**kwargs)

    def full_json(self, lang=None, jns=False, index=None):
        """Return dictionary ready to convert to JSON as response.

        Args:
            lang (str): The language, if specified.
            jns (bool): If true, namespaces all dictionary keys with prefixed
            table name.
            index (int): Field index for fields that have multiple instances of
            itself, e.g. "characteristic1", "characteristic2".

        Returns:
            dict: API response ready to be JSONified.
        """
        result = {
            'id': self.code,
            'label': self.label.to_string(lang),
            'definition': self.definition.to_string(lang)
        }
        if jns:
            result = self.namespace(result, 'charGrp', index=index)
        return result

    def __repr__(self):
        """Return a representation of this object."""
        return '<CharacteristicGroup "{}">'.format(self.code)

    @staticmethod
    def none_json(jns=False, index=None):
        """Return dictionary ready to convert to JSON as response.

        All values in this dictionary are set to none, serving the cases where
        no data is found or needs to be supplied.

        Args:
            jns (bool): If true, namespaces all dictionary keys with prefixed
            table name.
            index (int): Field index for fields that have multiple instances of
            itself, e.g. "characteristic1", "characteristic2".

        Returns:
            dict: API response ready to be JSONified.
        """
        result = {'id': None, 'label': None, 'definition': None}
        if jns:
            result = ApiModel.namespace(result, 'charGrp', index=index)
        return result

    def datalab_init_json(self):
        """Datalab init json: CharacteristicGroup."""
        to_return = {
            'id': self.code,
            'label.id': self.label.code,
            'definition.id': self.definition.code,
        }
        return to_return