diff --git a/where/app.py b/where/app.py index 6aac4d5a9e61621b67521fc95b2bc9d7b6518b25..9c50e22a99e3922fc0671d3ea6b7fba4d50a0516 100644 --- a/where/app.py +++ b/where/app.py @@ -247,6 +247,8 @@ def search_resource(schema, data): :param data: A dictionary containing search parameters :return: a Flask Response object ''' + + # TODO: returns 404 when accessing children - i think it should just return an empty array query = g.db_session.query(schema.Meta.model).filter_by(**data) resp = (None, 404) if query.first() is None else \ (schema.dump(query.all(), many=True), 200) diff --git a/where/model/__init__.py b/where/model/__init__.py index 61cf13c95b3acc436d5f8c598b5bb32699885822..b88ef73428bc32a6af566d5ef67c998b3b61a05c 100644 --- a/where/model/__init__.py +++ b/where/model/__init__.py @@ -1,117 +1,5 @@ from contextlib import contextmanager -from sqlalchemy import Column, Integer, String, Float, JSON, ForeignKey, Enum -from sqlalchemy.ext.declarative import as_declarative -from sqlalchemy.orm import relationship, validates - +from .meta import Session from .field_types import FieldType -from .meta import Session, engine - - -@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 - } +from .models import Base, Point, Category, Field \ No newline at end of file diff --git a/where/model/models.py b/where/model/models.py new file mode 100644 index 0000000000000000000000000000000000000000..c906a9a9d78acc132d60d621c196fe2b07c73aea --- /dev/null +++ b/where/model/models.py @@ -0,0 +1,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 + }