models.py 3.24 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113
from sqlalchemy.ext.declarative import as_declarative
from sqlalchemy import Column, Integer, String, Float, JSON, ForeignKey, Enum
from sqlalchemy.orm import relationship, validates

from . import FieldType

@as_declarative()
class Base(object):
    pass


class Point(Base):
    """
    Represents actual instances of any and all points on the map.
    """
    __tablename__ = 'point'
    id = Column(Integer, primary_key=True, autoincrement=True)
    name = Column(String, nullable=True)
    lat = Column(Float, nullable=False)
    lon = Column(Float, nullable=False)
    attributes = Column(JSON, nullable=False)

    # Relationships
    category_id = Column(Integer, ForeignKey('category.id'), nullable=False)
    category = relationship('Category')
    parent_id = Column(Integer, ForeignKey('point.id'), nullable=True)
    parent = relationship('Point', remote_side=[id])
    children = relationship('Point')

    def __init__(self, **kwargs):
        # Need to load category first or else attribute validation will fail
        if 'category' in kwargs:
            self.category = kwargs.pop('category')
        
        super(Point, self).__init__(**kwargs)

    @validates('attributes')
    def validate_data(self, _, data):
        if data is None:
            return

        fields = self.category.fields

        for key in data:
            # Find Field object that corresponds to this key
            for field in fields:
                if field.slug == key:
                    break
            else:
                raise ValueError(f'extra data "{key}" must be a registered field')
            field.validate_data(data[key])
        return data

    def as_json(self, children=True):
        if children:
            children = [child.as_json(children=False) for child in self.children]
        return {
            "name": self.name,
            "lat": self.lat,
            "lon": self.lon,
            "category": self.category.id,
            "attributes": self.attributes,
            "children": children
        }


class Category(Base):
    """
    Represent a schema for a single category of objects (e.g. water fountain or bathroom)
    """
    __tablename__ = 'category'

    id = Column(Integer, primary_key=True, autoincrement=True)
    name = Column(String, nullable=False, unique=True)
    icon = Column(String, nullable=True)

    fields = relationship("Field")

    def as_json(self):
        return {
            "id": self.id,
            "name": self.name,
            "icon": self.icon,
            "attributes": {attr.slug: attr.as_json() for attr in self.fields}
        }


class Field(Base):
    """
    Represents a single field in the Category schema.
    """
    __tablename__ = 'field'

    id = Column(Integer, primary_key=True, autoincrement=True)
    slug = Column(String, nullable=False)
    name = Column(String, nullable=False)
    type = Column(Enum(FieldType), nullable=False)

    # Relationship
    category_id = Column(Integer, ForeignKey('category.id'))

    def validate_data(self, data):
        """
        Verify that data is the correct type for this Field.
        """
        self.type.validate(data)

    def as_json(self):
        return {
            "slug": self.slug,
            "name": self.name,
            "type": self.type.name
        }