def test_update(self): (result, update_result) = quiz_model_mutation_update( self.client, graphql_update_or_create_foo, 'createFoo.foo', 'updateFoo.foo', dict(name='Luxembourg', key='luxembourg', user=R.pick(['id'], self.admin), geojson=geojson, data=dict(example=1.1, friend=R.pick(['id'], self.user))), # Update the coords dict( geojson={ 'features': [{ "type": "Feature", "geometry": { "type": "Polygon", "coordinates": [[[49.5294835476, 2.51357303225], [51.4750237087, 2.51357303225], [51.4750237087, 6.15665815596], [49.5294835476, 6.15665815596], [49.5294835476, 2.51357303225]]] } }] })) versions = Version.objects.get_for_object( Foo.objects.get( id=R.item_str_path('data.updateFoo.foo.id', update_result))) assert len(versions) == 2
def create_sample_foos(user, friend): bar = Bar(key='bar') with reversion.create_revision(): bar.save() bar_barr = Bar(key='bar_barr') with reversion.create_revision(): bar_barr.save() foo = Foo(key='foo', name="Foo", user=user, data=dict(example=2.2, friend=R.pick(['id'], friend)), geojson=geojson, geo_collection=ewkt_from_feature_collection(geojson)) with reversion.create_revision(): foo.save() foo.bars.add(bar) foo.bars.add(bar_barr) boo = Foo(key='boo', name="Boo", user=user, data=dict(example=2.2, friend=R.pick(['id'], friend)), geojson=geojson, geo_collection=ewkt_from_feature_collection(geojson)) with reversion.create_revision(): boo.save() boo.bars.add(bar) return [foo, boo]
def test_create(self): result, new_result = quiz_model_mutation_create( self.client, graphql_update_or_create_project, 'createProject.project', dict( name='Carre', key='carre', geojson={ 'type': 'FeatureCollection', 'features': [{ "type": "Feature", "geometry": { "type": "Polygon", "coordinates": [ [[49.5294835476, 2.51357303225], [51.4750237087, 2.51357303225], [51.4750237087, 6.15665815596], [49.5294835476, 6.15665815596], [49.5294835476, 2.51357303225]]] } }] }, data=dict(), locations=R.map(R.compose(R.pick(['id']), lambda l: l.__dict__), self.locations), user=R.pick(['id'], R.head(self.users).__dict__), ), dict(key=r'carre.+')) versions = Version.objects.get_for_object(get_project_model().objects.get( id=R.item_str_path('data.createProject.project.id', result) )) assert len(versions) == 1
def test_create(self): (result, new_result) = quiz_model_mutation_create( self.client, graphql_update_or_create_foo, 'createFoo.foo', dict(name='Luxembourg', key='luxembourg', user=R.pick(['id'], self.admin), geojson=geojson, data=dict(example=1.1, friend=R.pick(['id'], self.user))), dict(key=r'luxembourg.+')) versions = Version.objects.get_for_object( Foo.objects.get( id=R.item_str_path('data.createFoo.foo.id', result))) assert len(versions) == 1
def test_update(self): result, update_result = quiz_model_mutation_update( self.client, graphql_update_or_create_project, 'createProject.project', 'updateProject.project', dict( name='Carre', key='carre', geojson={ 'type': 'FeatureCollection', 'features': [{ "type": "Feature", "geometry": { "type": "Polygon", "coordinates": [ [[49.4426671413, 5.67405195478], [50.1280516628, 5.67405195478], [50.1280516628, 6.24275109216], [49.4426671413, 6.24275109216], [49.4426671413, 5.67405195478]]] } }] }, data=dict(), locations=R.map(R.compose(R.pick(['id']), lambda l: l.__dict__), self.locations), user=R.pick(['id'], R.head(self.users).__dict__), ), # Update the coords and limit to one location dict( geojson={ 'features': [{ "type": "Feature", "geometry": { "type": "Polygon", "coordinates": [ [[49.5294835476, 2.51357303225], [51.4750237087, 2.51357303225], [51.4750237087, 6.15665815596], [49.5294835476, 6.15665815596], [49.5294835476, 2.51357303225]]] } }] }, locations=R.map(R.compose(R.pick(['id']), lambda l: l.__dict__), [R.head(self.locations)]) ) ) versions = Version.objects.get_for_object(get_project_model().objects.get( id=R.item_str_path('data.updateProject.project.id', update_result) )) assert len(versions) == 2
def pick_selections(selections, data): """ Pick the selections from the current data :param {[Sting]} selections: The field names to that are in the query :param {dict} data: Data to pick from :return: {DataTuple} data with limited to selections """ dct = R.pick(selections, data) return namedtuple('DataTuple', R.keys(dct))(*R.values(dct))
def test_query_with_project_reference(self): quiz_model_query( self.client, graphql_query_locations, 'locations', dict( name='Grand Place', projects=[R.pick(['id'], self.projects[0])] ) )
def fetch_and_merge(modified_user_state_data, props): existing = UserState.objects.filter(**props) # If the user doesn't have a user state yet if not R.length(existing): return modified_user_state_data return merge_data_fields_on_update( ['data'], R.head(existing), # Merge existing's id in case it wasn't in user_state_data R.merge(modified_user_state_data, R.pick(['id'], existing)) )
def execute_graphql_request(self, *args, **kwargs): result = super().execute_graphql_request(*args, **kwargs) if result.errors: log.error( json.dumps(R.pick(['operationName', 'variables'], args[1]), indent=4)) for error in result.errors: if hasattr(error, 'source'): log.error(error.source.body) # NO way to get stack trace of the original error grrrr log.exception(error) return result
def test_update(self): # First add a new User margay = dict(username="******", first_name='Upa', last_name='Tree', password=make_password("merowgir", salt='not_random')) user = create_sample_user(margay) # Now assign regions and persist the UserState sample_user_state_data = dict( user=dict(id=user.id), data=form_sample_user_state_data( self.regions, self.projects, dict( userRegions=[ dict( # Assign the first region region=dict( key=R.prop('key', R.head(self.regions))), mapbox=dict(viewport=dict( latitude=50.5915, longitude=2.0165, zoom=7))) ], userProjects=[ dict( # Assign the first prjoect project=dict( key=R.prop('key', R.head(self.projects))), mapbox=dict(viewport=dict( latitude=50.5915, longitude=2.0165, zoom=7))) ]))) # Update the zoom of the first userRegion update_data = deepcopy(R.pick(['data'], sample_user_state_data)) R.item_str_path( 'mapbox.viewport', R.head(R.item_str_path('data.userRegions', (update_data))))['zoom'] = 15 result, update_result = quiz_model_mutation_update( self.client, R.prop('graphql_mutation', self.user_state_schema), 'createUserState.userState', 'updateUserState.userState', sample_user_state_data, update_data) versions = Version.objects.get_for_object( UserState.objects.get(id=R.item_str_path( 'data.updateUserState.userState.id', update_result))) assert len(versions) == 2
def test_query(self): user_results = graphql_query_users(self.client) format_error(R.prop('errors', user_results)[0]) assert not R.prop('errors', user_results), R.dump_json(R.map(lambda e: format_error(e), R.prop('errors', user_results))) assert 2 == R.length(R.map(R.omit_deep(omit_props), R.item_path(['data', 'users'], user_results))) # Query using for foos based on the related User foo_results = graphql_query_foos( self.client, variables=dict( user=R.pick(['id'], self.lion.__dict__), # Test filters name_contains='oo', name_contains_not='jaberwaki' ) ) assert not R.prop('errors', foo_results), R.dump_json(R.map(lambda e: format_error(e), R.prop('errors', foo_results))) assert 1 == R.length(R.map(R.omit_deep(omit_props), R.item_path(['data', 'foos'], foo_results))) # Make sure the Django instance in the json blob was resolved assert self.cat.id == R.item_path(['data', 'foos', 0, 'data', 'friend', 'id'], foo_results)
def resolver_for_feature_collection(resource, context, **kwargs): """ Like resolver but takes care of converting the geos value stored in the field to a dict that has the values we want to resolve, namely type and features. :param {string} resource: The instance whose json field data is being resolved :param {ResolveInfo} context: Graphene context which contains the fields queried in field_asts :return: {DataTuple} Standard resolver return value """ # Take the camelized keys. We don't store data fields slugified. We leave them camelized selections = R.map(lambda sel: sel.name.value, context.field_asts[0].selection_set.selections) # Recover the json by parsing the string provided by GeometryCollection and mapping the geometries property to features json = R.compose( # Map the value GeometryCollection to FeatureCollection for the type property R.map_with_obj(lambda k, v: R.if_else( R.equals('type'), R.always('FeatureCollection'), R.always(v) )(k)), # Map geometries to features: [{type: Feature, geometry: geometry}] lambda dct: R.merge( # Remove geometries R.omit(['geometries'], dct), # Add features containing the geometries dict(features=R.map( lambda geometry: dict(type='Feature', geometry=geometry), R.prop_or([], 'geometries', dct)) ) ), )(ast.literal_eval(R.prop(context.field_name, resource).json)) # Identify the keys that are actually in resource[json_field_name] all_selections = R.filter( lambda key: key in json, selections ) # Pick out the values that we want result = R.pick(all_selections, json) # Return in the standard Graphene DataTuple return namedtuple('DataTuple', R.keys(result))(*R.values(result))
def sample_user_state_with_search_locations_and_additional_scope_instances( user_scope_name, sample_user_state): return R.fake_lens_path_set( f'data.{user_scope_name}'.split('.'), R.map( lambda user_scope: R.compose( # Gives applications a chance to add the needed additional scope instances, # e.g. userDesignFeatures lambda user_scope: create_additional_scope_instance_properties(user_scope), lambda user_scope: R.merge( user_scope, dict(userSearch=dict(userSearchLocations=R.map( lambda i_search_location: dict( # Just return with the id since the full data is in the database searchLocation=R.pick(['id'], i_search_location[1]), # Set the first search_location to active activity=dict(isActive=i_search_location[0] == 0)), enumerate(search_locations))))))(user_scope), R.item_str_path(f'data.{user_scope_name}', sample_user_state)), sample_user_state)
def handle_can_mutate_related(model, related_model_scope_config, data, validated_scope_objs_instances_and_ids): """ Mutates the given related models of an instance if permitted See rescape-region's UserState for a working usage :param model: The related model :param related_model_scope_config: Configuration of the related model relative to the referencing instance :param data: The data containing thphee related models dicts to possibly mutate with :param validated_scope_objs_instances_and_ids: Config of the related objects that have been validated as existing in the database for objects not being created :return: Possibly mutates instances, returns data with newly created ids set """ def make_fields_unique_if_needed(scope_obj): # If a field needs to be unique, like a key, call it's unique_with method return R.map_with_obj( lambda key, value: R.item_str_path_or( R.identity, f'field_config.{key}.unique_with', related_model_scope_config)(scope_obj), scope_obj) def convert_foreign_key_to_id(scope_obj): # Find ForeignKey attributes and map the class field name to the foreign key id field # E.g. region to region_id, user to user_id, etc converters = R.compose( R.from_pairs, R.map(lambda field: [field.name, field.attname]), R.filter(lambda field: R.isinstance(ForeignKey, field)))( model._meta.fields) # Convert scopo_obj[related_field] = {id: x} to scope_obj[related_field_id] = x return R.from_pairs( R.map_with_obj_to_values( lambda key, value: [converters[key], R.prop('id', value)] if R.has(key, converters) else [key, value], scope_obj)) def omit_to_many(scope_obj): return R.omit(R.map(R.prop('attname'), model._meta.many_to_many), scope_obj) # This indicates that scope_objs were submitted that didn't have ids # This is allowed if those scope_objs can be created/updated when the userState is mutated if R.prop_or(False, 'can_mutate_related', related_model_scope_config): for scope_obj_key_value in validated_scope_objs_instances_and_ids[ 'scope_objs']: scope_obj = scope_obj_key_value['value'] scope_obj_path = scope_obj_key_value['key'] if R.length(R.keys(R.omit(['id'], scope_obj))): modified_scope_obj = R.compose( convert_foreign_key_to_id, omit_to_many, make_fields_unique_if_needed)(scope_obj) if R.prop_or(False, 'id', scope_obj): # Update, we don't need the result since it's already in user_state.data instance, created = model.objects.update_or_create( defaults=R.omit(['id'], modified_scope_obj), **R.pick(['id'], scope_obj)) else: # Create instance = model(**modified_scope_obj) instance.save() # We need to replace the object # passed in with an object containing the id of the instance data = R.fake_lens_path_set(scope_obj_path.split('.'), R.pick(['id'], instance), data) for to_many in model._meta.many_to_many: if to_many.attname in R.keys(scope_obj): # Set existing related values to the created/updated instances getattr(instance, to_many.attname).set( R.map(R.prop('id'), scope_obj[to_many.attname])) return data
class ResourceType(DjangoObjectType, DjangoObjectTypeRevisionedMixin): id = graphene.Int(source='pk') class Meta: model = Resource raw_resource_fields = merge_with_django_properties( ResourceType, dict( id=dict(create=DENY, update=REQUIRE), key=dict(create=REQUIRE, unique_with=increment_prop_until_unique( Resource, None, 'key', R.pick(['deleted']))), name=dict(create=REQUIRE), # This refers to the Resource, which is a representation of all the json fields of Resource.data data=dict(graphene_type=ResourceDataType, fields=resource_data_fields, default=lambda: dict()), # This is a Foreign Key. Graphene generates these relationships for us, but we need it here to # support our Mutation subclasses and query_argument generation # For simplicity we limit fields to id. Mutations can only us id, and a query doesn't need other # details of the resource--it can query separately for that region=dict(graphene_type=RegionType, fields=merge_with_django_properties( RegionType, dict(id=dict(create=REQUIRE)))), **reversion_and_safe_delete_types)) # Modify data field to use the resolver.
def test_create(self): # First add a new User margay = dict(username="******", first_name='Upa', last_name='Tree', password=make_password("merowgir", salt='not_random')) user = create_sample_user(margay) # Now assign regions and persist the UserState sample_user_state_data = dict( user=dict(id=user.id), data=form_sample_user_state_data( self.regions, self.projects, dict( userGlobal=dict(mapbox=dict(viewport=dict( latitude=50.5915, longitude=2.0165, zoom=7))), userRegions=[ dict( # Assign the first region region=dict( key=R.prop('key', R.head(self.regions))), mapbox=dict(viewport=dict( latitude=50.5915, longitude=2.0165, zoom=7)), userSearch=dict(userSearchLocations=R.concat( R.map( lambda search_location: dict( searchLocation=R.pick(['id'], search_location), activity=dict(isActive=True)), self.search_locations), # Search locations can be created on the fly [ dict(searchLocation=dict( name="I am a new search"), activity=dict(isActive=True)) ])), **self.additional_user_scope_data) ], userProjects=[ dict( # Assign the first project project=dict( key=R.prop('key', R.head(self.projects))), mapbox=dict(viewport=dict( latitude=50.5915, longitude=2.0165, zoom=7)), userSearch=dict(userSearchLocations=R.map( lambda search_location: dict( searchLocation=R.pick(['id'], search_location), activity=dict(isActive=True)), self.search_locations)), **self.additional_user_scope_data), dict( # Create a new project when creating the userProject project=dict(user=R.pick(['id'], user), region=R.pick(['id'], R.head(self.regions)), key='newProject', name='New Project', locations=R.map( R.pick(['id']), self.locations)), mapbox=dict(viewport=dict( latitude=50.5915, longitude=2.0165, zoom=7)), userSearch=dict(userSearchLocations=R.map( lambda search_location: dict( searchLocation=R.pick(['id'], search_location), activity=dict(isActive=True)), self.search_locations)), **self.additional_user_scope_data) ]))) result, _ = quiz_model_mutation_create( self.client, R.prop('graphql_mutation', self.user_state_schema), 'createUserState.userState', sample_user_state_data, # The second create should update, since we can only have one userState per user dict(), True) versions = Version.objects.get_for_object( UserState.objects.get(id=R.item_str_path( 'data.createUserState.userState.id', result))) assert len(versions) == 1
from rescape_graphene.graphql_helpers.schema_helpers import process_filter_kwargs, delete_if_marked_for_delete, \ update_or_create_with_revision, top_level_allowed_filter_arguments, allowed_filter_arguments from rescape_graphene.schema_models.django_object_type_revisioned_mixin import reversion_and_safe_delete_types, \ DjangoObjectTypeRevisionedMixin from rescape_graphene.schema_models.geojson.types.feature_collection import feature_collection_data_type_fields from rescape_python_helpers import ramda as R from rescape_region.model_helpers import get_region_model from rescape_region.models.region import Region from .region_data_schema import RegionDataType, region_data_fields raw_region_fields = dict( id=dict(create=DENY, update=REQUIRE), key=dict(create=REQUIRE, unique_with=increment_prop_until_unique(Region, None, 'key', R.pick(['deleted']))), name=dict(create=REQUIRE), # This refers to the RegionDataType, which is a representation of all the json fields of Region.data data=dict(graphene_type=RegionDataType, fields=region_data_fields, default=lambda: dict()), # This is the OSM geojson geojson=dict(graphene_type=FeatureCollectionDataType, fields=feature_collection_data_type_fields), **reversion_and_safe_delete_types) class RegionType(DjangoObjectType, DjangoObjectTypeRevisionedMixin): id = graphene.Int(source='pk') class Meta: