def sort_query(self, query, sort_info): """Sort query according to jsonapi 1.0 :param Query query: sqlalchemy query to sort :param list sort_info: sort information :return Query: the sorted query """ for sort_opt in sort_info: field = sort_opt['field'] if sort_opt.get('relationship'): current_schema = self.resource.schema for obj in sort_opt['relationship'].split('.'): current_schema = get_related_schema(current_schema, obj) if not hasattr(current_schema.Meta, 'model'): raise InvalidSort("In order to sort on a relationship, meta model must be defined") relationship_model = current_schema.Meta.model query = query.join(relationship_model) relationship_model = current_schema.Meta.model model_field = get_model_field(current_schema, sort_opt['field']) query = query.order_by(getattr(getattr(relationship_model, model_field), sort_opt['order'])()) elif not hasattr(self.model, field): raise InvalidSort("{} has no attribute {}".format(self.model.__name__, field)) else: query = query.order_by(getattr(getattr(self.model, field), sort_opt['order'])()) return query
def sorting(self): """Return fields to sort by including sort name for SQLAlchemy and row sort parameter for other ORMs :return list: a list of sorting information Example of return value:: [ {'field': 'created_at', 'order': 'desc'}, ] """ if self.qs.get('sort'): sorting_results = [] for sort_field in self.qs['sort'].split(','): field = sort_field.replace('-', '') if field not in self.schema._declared_fields: raise InvalidSort("{} has no attribute {}".format( self.schema.__name__, field)) if field in get_relationships(self.schema): raise InvalidSort( "You can't sort on {} because it is a relationship field" .format(field)) field = get_model_field(self.schema, field) order = 'desc' if sort_field.startswith('-') else 'asc' sorting_results.append({'field': field, 'order': order}) return sorting_results return []
def sorting(self): """Return fields to sort by including sort name for SQLAlchemy and row sort parameter for other ORMs :return list: a list of sorting information Example of return value:: [ {'field': 'created_at', 'order': 'desc'}, ] """ if self.qs.get('sort'): sorting_results = [] for sort_field in self.qs['sort'].split(','): field = sort_field.replace('-', '') order = 'desc' if sort_field.startswith('-') else 'asc' if '.' in field: # attempting to sort on relationship relationship_path = '.'.join(field.split('.')[0:-1]) target_attribute = field.split('.')[-1] relationship_schema = get_related_schema_path( self.schema, relationship_path) if not get_model_field(relationship_schema, target_attribute): raise InvalidSort("Invalid sort path {}".format(field)) sorting_results.append({ 'field': target_attribute, 'order': order, 'relationship': relationship_path }) elif field not in self.schema._declared_fields: raise InvalidSort("{} has no attribute {}".format( self.schema.__name__, field)) elif field in get_relationships(self.schema): raise InvalidSort( "You can't sort on {} because it is a relationship field" .format(field)) else: field = get_model_field(self.schema, field) sorting_results.append({'field': field, 'order': order}) return sorting_results return []
def eagerload_includes(self, query, qs): """Use eagerload feature of sqlalchemy to optimize data retrieval for include querystring parameter :param Query query: sqlalchemy queryset :param QueryStringManager qs: a querystring manager to retrieve information from url :return Query: the query with includes eagerloaded """ for include in qs.include: joinload_object = None if '.' in include: current_schema = self.resource.schema for obj in include.split('.'): try: field = get_model_field(current_schema, obj) except Exception as e: raise InvalidInclude(str(e)) if joinload_object is None: joinload_object = joinedload(field, innerjoin=True) else: joinload_object = joinload_object.joinedload( field, innerjoin=True) related_schema_cls = get_related_schema( current_schema, obj) if isinstance(related_schema_cls, SchemaABC): related_schema_cls = related_schema_cls.__class__ else: related_schema_cls = class_registry.get_class( related_schema_cls) current_schema = related_schema_cls else: try: field = get_model_field(self.resource.schema, include) except Exception as e: raise InvalidInclude(str(e)) joinload_object = joinedload(field, innerjoin=True) query = query.options(joinload_object) return query
def _get_relationship_data(self): """Get useful data for relationship management""" relationship_field = request.path.split('/')[-1] if relationship_field not in get_relationships(self.schema): raise RelationNotFound("{} has no attribute {}".format(self.schema.__name__, relationship_field)) related_type_ = self.schema._declared_fields[relationship_field].type_ related_id_field = self.schema._declared_fields[relationship_field].id_field model_relationship_field = get_model_field(self.schema, relationship_field) return relationship_field, model_relationship_field, related_type_, related_id_field
def column(self): """Get the column object :param DeclarativeMeta model: the model :param str field: the field :return InstrumentedAttribute: the column to filter on """ field = self.name model_field = get_model_field(self.schema, field) try: attr = getattr(self.model, model_field) except AttributeError: raise InvalidFilters("{} has no attribute {}".format( self.model.__name__, model_field)) name = self.filter_.get('name') if '[' in name: if not isinstance(attr.type, Indexable): raise InvalidFilters( "{} is not an indexable column".format(model_field)) name = name[name.index('['):] while name.startswith('['): item, name = name[1:].split(']', 1) try: item = json.loads(item) except json.decoder.JSONDecodeError: raise InvalidFilters( "{} is not a valid index".format(item)) try: attr = attr[item] except Exception: raise InvalidFilters( "{} is not a valid indexing expression".format( self.filter_.get('name'))) if name == ".as_string()": attr = attr.as_string() elif name == ".as_integer()": attr = attr.as_integer() elif name == ".as_float()": attr = attr.as_float() elif name == ".as_boolean()": attr = attr.as_boolean() elif len(name) > 0: raise InvalidFilters( "{} is not a valid indexing expression".format( self.filter_.get('name'))) return attr
def related_model(self): """Get the related model of a relationship field :return DeclarativeMeta: the related model """ relationship_field = self.name if relationship_field not in get_relationships(self.schema): raise InvalidFilters("{} has no relationship attribute {}".format( self.schema.__name__, relationship_field)) return getattr(self.model, get_model_field( self.schema, relationship_field)).property.mapper.class_
def column(self): """Get the column object :param DeclarativeMeta model: the model :param str field: the field :return InstrumentedAttribute: the column to filter on """ field = self.name model_field = get_model_field(self.schema, field) try: return getattr(self.model, model_field) except AttributeError: raise InvalidFilters("{} has no attribute {}".format( self.model.__name__, model_field))
def related_model(self): """Get the related model of a related (relationship or nested) field :return DeclarativeMeta: the related model """ related_field_name = self.name related_fields = get_relationships(self.schema) + get_nested_fields( self.schema) if related_field_name not in related_fields: raise InvalidFilters( "{} has no relationship or nested attribute {}".format( self.schema.__name__, related_field_name)) return getattr(self.model, get_model_field( self.schema, related_field_name)).property.mapper.class_