Source code for pyramid_jsonapi.workflow.loop.collection_post

import pyramid_jsonapi.workflow as wf
import sqlalchemy

from collections.abc import Sequence

from .item_get import (
    get_doc,
)

from sqlalchemy.orm.interfaces import (
    ONETOMANY,
    MANYTOMANY,
    MANYTOONE
)

from pyramid.httpexceptions import (
    HTTPBadRequest,
    HTTPForbidden,
    HTTPConflict,
    HTTPNotFound,
)
from . import stages


[docs]def workflow(view, stages): try: data = view.request.json_body['data'] except KeyError: raise HTTPBadRequest('data attribute required in POSTs.') if not isinstance(data, dict): raise HTTPBadRequest('data attribute must contain a single resource object.') # Check to see if we're allowing client ids if not view.api.settings.allow_client_ids and 'id' in data: raise HTTPForbidden('Client generated ids are not supported.') # Type should be correct or raise 409 Conflict datatype = data.get('type') if datatype != view.collection_name: raise HTTPConflict("Unsupported type '{}'".format(datatype)) try: atts = data['attributes'] except KeyError: atts = {} if 'id' in data: atts[view.model.__pyramid_jsonapi__['id_col_name']] = data['id'] item = view.model(**atts) with view.dbsession.no_autoflush: for relname, reldict in data.get('relationships', {}).items(): try: reldata = reldict['data'] except KeyError: raise HTTPBadRequest( 'relationships within POST must have data member' ) try: rel = view.relationships[relname] except KeyError: raise HTTPNotFound( 'No relationship {} in collection {}'.format( relname, view.collection_name ) ) rel_type = view.api.view_classes[rel.tgt_class].collection_name if rel.direction is ONETOMANY or rel.direction is MANYTOMANY: # reldata should be a list/array if not isinstance(reldata, Sequence) or isinstance(reldata, str): raise HTTPBadRequest( 'Relationship data should be an array for TOMANY relationships.' ) rel_items = [] for rel_identifier in reldata: if rel_identifier.get('type') != rel_type: raise HTTPConflict( 'Relationship identifier has type {} and should be {}'.format( rel_identifier.get('type'), rel_type ) ) try: rel_items.append(view.dbsession.query(rel.tgt_class).get(rel_identifier['id'])) except KeyError: raise HTTPBadRequest( 'Relationship identifier must have an id member' ) setattr(item, relname, rel_items) else: if (not isinstance(reldata, dict)) and (reldata is not None): raise HTTPBadRequest( 'Relationship data should be a resource identifier object or null.' ) if reldata.get('type') != rel_type: raise HTTPConflict( 'Relationship identifier has type {} and should be {}'.format( reldata.get('type'), rel_type ) ) try: setattr( item, relname, view.dbsession.query(rel.tgt_class).get(reldata['id']) ) except KeyError: raise HTTPBadRequest( 'No id member in relationship data.' ) item = wf.execute_stage( view, stages, 'before_write_item', item ) try: view.dbsession.add(item) view.dbsession.flush() except sqlalchemy.exc.IntegrityError as exc: raise HTTPConflict(exc.args[0]) view.request.response.status_code = 201 item_id = view.id_col(item) view.request.response.headers['Location'] = view.request.route_url( view.api.endpoint_data.make_route_name(view.collection_name, suffix='item'), **{'id': item_id} ) # The rest of this is more or less a get. return get_doc( view, getattr(view, 'item_get').stages, view.single_item_query(item_id) )