Example #1
0
def create_user_state_config(class_config):
    """
        Creates the UserStateType based on specific class_config
    :param class_config: A dict containing class configurations. The default is:
    dict(
        settings=dict(
            model_class=Settings,
            graphene_class=SettingsType,
            graphene_fields=settings_fields,
            query=SettingsQuery,
            mutation=SettingsMutation
        ),
        region=dict(
            model_class=Region,
            graphene_class=RegionType,
            graphene_fields=region_fields,
            query=RegionQuery,
            mutation=RegionMutation
        ),
        project=dict(
            model_class=Project,
            graphene_class=ProjectType,
            graphene_fields=project_fields,
            query=ProjectQuery,
            mutation=ProjectMutation
        ),
        resource=dict(
            model_class=Resource,
            graphene_class=ResourceType,
            graphene_fields=resource_fields,
            query=ResourceQuery,
            mutation=ResourceMutation
        ),
        location=get_location_schema(),
        user_search=get_user_search_data_schema(),
        search_location=get_search_location_schema()
        # additional_user_scope_schemas and additional_user_scopes
        # are passed in from a calling app
        # these are a dict of properties that need to go on user_regions and user_projects
        # at the same level as userSearch. For instance, a user's saved app selections could go here
        # additional_user_scope_schemas = dict(
        # userDesignFeatureLayers=dict(
        #    graphene_class=UserDesignFeatureDataType,
        #    graphene_fields=user_design_feature_data_fields
        # )
        # additional_user_scopes explains the path to Django models within additional_user_scope_schemas
        # additional_django_model_user_scopes = dict(
        # userDesignFeatureLayers=dict(
        #   designFeature=True
        # )
        # Would match the list of some django DesignFeature model instances
    )
    :return:
    """

    class UserStateType(DjangoObjectType, DjangoObjectTypeRevisionedMixin):
        """
            UserStateType models UserState, which represents the settings both imposed upon and chosen by the user
        """
        id = graphene.Int(source='pk')

        class Meta:
            model = UserState

    # Modify data field to use the resolver.
    # I guess there's no way to specify a resolver upon field creation, since graphene just reads the underlying
    # Django model to generate the fields
    UserStateType._meta.fields['data'] = Field(
        UserStateDataType(class_config),
        resolver=resolver_for_data_field
    )

    user_state_fields = merge_with_django_properties(UserStateType, dict(
        id=dict(create=DENY, update=REQUIRE),
        # 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 use id, and a query doesn't need other
        # details of the User--it can query separately for that
        user=dict(graphene_type=UserType, fields=user_fields),
        # This refers to the UserState, which is a representation of all the json fields of UserState.data
        data=dict(graphene_type=UserStateDataType(class_config), fields=user_state_data_fields(class_config),
                  default=lambda: dict()),
        **reversion_and_safe_delete_types
    ))

    user_state_mutation_config = dict(
        class_name='UserState',
        crud={
            CREATE: 'createUserState',
            UPDATE: 'updateUserState'
        },
        resolve=guess_update_or_create
    )

    additional_django_model_user_scopes = R.prop('additional_django_model_user_scopes', class_config) \
        if R.prop_or(None, 'additional_django_model_user_scopes', class_config) else {}
    additional_user_scope_schemas = R.prop('additional_user_scope_schemas', class_config) \
        if R.prop_or(None, 'additional_user_scope_schemas', class_config) else {}

    # The scope instance types expected in user_state.data
    user_state_scope_instances_config = R.concat([
        # dict(region=True) means search all userRegions for that dict
        dict(pick=dict(userRegions=dict(region=True)),
             key='region',
             model=get_region_model()
             ),
        # dict(project=True) means search all userProjects for that dict
        dict(
            pick=dict(userProjects=dict(project=True)),
            key='project',
            model=get_project_model(),
            # This is currently just needed for the field key's unique_with function
            field_config=project_fields,
            # Projects can be modified when userState is mutated
            can_mutate_related=True
        ),
        dict(
            pick=dict(
                userRegions=[
                    dict(
                        userSearch=dict(
                            # dict(searchLocation=True) means search all userSearchLocations for that dict
                            userSearchLocations=dict(
                                searchLocation=True,
                            )
                        )
                    )
                ],
                userProjects=[
                    dict(
                        userSearch=dict(
                            # dict(searchLocation=True) means search all userSearchLocations for that dict
                            userSearchLocations=dict(searchLocation=True)
                        )
                    )
                ]
            ),
            key='searchLocation',
            model=get_search_location_schema()['model_class'],
            # These can be modified when userState is mutated
            can_mutate_related=True
        ),
    ],
        # Map each additional_django_model_user_scopes to a scope config
        R.map_with_obj_to_values(
            lambda field_name, additional_django_model_user_scope: dict(
                pick=dict(
                    userRegions=[
                        {field_name: additional_django_model_user_scope}
                    ],
                    userProjects=[
                        {field_name: additional_django_model_user_scope}
                    ]
                ),
                # Assume the scope object is the deepest field
                key=list(R.keys(R.flatten_dct(additional_django_model_user_scope, '.')))[0].split('.')[-1],
                # model isn't needed unless can_mutate_related is true
                model=additional_user_scope_schemas[field_name]['model'],
                # These can be modified when userState is mutated
                can_mutate_related=R.prop_or(False, 'can_mutate_related', additional_django_model_user_scope)
            ),
            additional_django_model_user_scopes
        )
    )

    class UpsertUserState(Mutation):
        """
            Abstract base class for mutation
        """
        user_state = Field(UserStateType)

        def mutate(self, info, user_state_data=None):
            """
                Update or create the user state
            :param info:
            :param user_state_data:
            :return:
            """

            # Check that all the scope instances in user_state.data exist. We permit deleted instances for now.
            new_data = R.prop_or({}, 'data', user_state_data)
            # Copy since Graphene reuses this data
            copied_new_data = copy.deepcopy(new_data)
            old_user_state_data = UserState.objects.get(
                id=user_state_data['id']
            ).data if R.prop_or(None, 'id', user_state_data) else None

            # Inspect the data and find all scope instances within UserState.data
            # This includes userRegions[*].region, userProject[*].project and within userRegions and userProjects
            # userSearch.userSearchLocations[*].search_location and whatever the implementing libraries define
            # in addition
            updated_new_data = validate_and_mutate_scope_instances(
                user_state_scope_instances_config,
                copied_new_data
            )

            # If either userProjects or userRegions are null, it means those scope instances aren't part
            # of the update, so merge in the old values
            if R.prop_or(None, 'id', user_state_data) and R.any_satisfy(
                    lambda user_scope_key: not R.prop_or(None, user_scope_key, updated_new_data),
                    ['userProjects', 'userRegions']
            ):
                # The special update case where one userScope collection is null,
                # indicates that we are only updating one userScope object. The rest
                # should remain the same and not be removed
                for user_scope_key in ['userProjects', 'userRegions']:
                    # Database values
                    old_user_scopes_by_id = user_scope_instances_by_id(
                        user_scope_key,
                        old_user_state_data
                    )
                    # New values with updates applied
                    new_user_scopes_by_id = user_scope_instances_by_id(
                        user_scope_key,
                        updated_new_data
                    )
                    # Prefer the old over the new, merging all objects but overriding lists
                    # We override lists because a non-null list always replaces the old list in the database
                    updated_new_data[user_scope_key] = R.values(R.merge_deep(
                        old_user_scopes_by_id,
                        new_user_scopes_by_id,
                        MyMerger(
                            # pass in a list of tuples,with the
                            # strategies you are looking to apply
                            # to each type.
                            [
                                (list, ["override_non_null"]),
                                (dict, ["merge"])
                            ],
                            # next, choose the fallback strategies,
                            # applied to all other types:
                            ["override"],
                            # finally, choose the strategies in
                            # the case where the types conflict:
                            ["override"]
                        )
                    ))

            # Update user_state_data the updated data
            modified_user_state_data = R.merge(user_state_data, dict(data=updated_new_data))

            # id or user.id can be used to identify the existing instance
            id_props = R.compact_dict(
                dict(
                    id=R.prop_or(None, 'id', modified_user_state_data),
                    user_id=R.item_str_path_or(None, 'user.id', modified_user_state_data)
                )
            )

            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))
                )

            modified_data = R.if_else(
                R.compose(R.length, R.keys),
                lambda props: fetch_and_merge(modified_user_state_data, props),
                lambda _: modified_user_state_data
            )(id_props)

            update_or_create_values = input_type_parameters_for_update_or_create(
                user_state_fields,
                # Make sure that all props are unique that must be, either by modifying values or erring.
                enforce_unique_props(
                    user_state_fields,
                    modified_data)
            )

            user_state, created = update_or_create_with_revision(UserState, update_or_create_values)
            return UpsertUserState(user_state=user_state)

    class CreateUserState(UpsertUserState):
        """
            Create UserState mutation class
        """

        class Arguments:
            user_state_data = type('CreateUserStateInputType', (InputObjectType,),
                                   input_type_fields(user_state_fields, CREATE, UserStateType)
                                   )(required=True)

    class UpdateUserState(UpsertUserState):
        """
            Update UserState mutation class
        """

        class Arguments:
            user_state_data = type('UpdateUserStateInputType', (InputObjectType,),
                                   input_type_fields(user_state_fields, UPDATE, UserStateType))(required=True)

    graphql_update_or_create_user_state = graphql_update_or_create(user_state_mutation_config, user_state_fields)
    graphql_query_user_states = graphql_query(UserStateType, user_state_fields, 'userStates')

    return dict(
        model_class=UserState,
        graphene_class=UserStateType,
        graphene_fields=user_state_fields,
        create_mutation_class=CreateUserState,
        update_mutation_class=UpdateUserState,
        graphql_mutation=graphql_update_or_create_user_state,
        graphql_query=graphql_query_user_states
    )
def create_group_state_config(class_config):
    """
        Creates the GroupStateType based on specific class_config
    :param class_config: A dict containing class configurations. Right now it's only region in the form
    dict(
        region=dict(
            model_class=...,
            graphene_class=...,
            fields=...
        )
    )
    :return:
    """

    class GroupStateType(DjangoObjectType, DjangoObjectTypeRevisionedMixin):
        """
            GroupStateType models GroupState, which represents the settings both imposed upon and chosen by the group
        """
        id = graphene.Int(source='pk')

        class Meta:
            model = GroupState

    # Modify data field to use the resolver.
    # I guess there's no way to specify a resolver upon field creation, since graphene just reads the underlying
    # Django model to generate the fields
    GroupStateType._meta.fields['data'] = Field(
        GroupStateDataType(class_config),
        resolver=resolver_for_dict_field
    )

    group_state_fields = merge_with_django_properties(GroupStateType, dict(
        id=dict(create=DENY, update=REQUIRE),
        data=dict(graphene_type=GroupStateDataType(class_config), fields=group_state_data_fields(class_config),
                  default=lambda: dict()),
        **reversion_and_safe_delete_types
    ))

    group_state_mutation_config = dict(
        class_name='GroupState',
        crud={
            CREATE: 'createGroupState',
            UPDATE: 'updateGroupState'
        },
        resolve=guess_update_or_create
    )

    class UpsertGroupState(Mutation):
        """
            Abstract base class for mutation
        """
        group_state = Field(GroupStateType)

        def mutate(self, info, group_state_data=None):
            """
                Update or create the group state
            :param info:
            :param group_state_data:
            :return:
            """
            update_or_create_values = input_type_parameters_for_update_or_create(group_state_fields, group_state_data)
            # We can do update_or_create since we have a unique group_id in addition to the unique id
            group_state, created = update_or_create_with_revision(GroupState, update_or_create_values)
            return UpsertGroupState(group_state=group_state)

    class CreateGroupState(UpsertGroupState):
        """
            Create GroupState mutation class
        """

        class Arguments:
            group_state_data = type('CreateGroupStateInputType', (InputObjectType,),
                                    input_type_fields(group_state_fields, CREATE, GroupStateType))(required=True)

    class UpdateGroupState(UpsertGroupState):
        """
            Update GroupState mutation class
        """

        class Arguments:
            group_state_data = type('UpdateGroupStateInputType', (InputObjectType,),
                                    input_type_fields(group_state_fields, UPDATE, GroupStateType))(required=True)

    graphql_update_or_create_group_state = graphql_update_or_create(group_state_mutation_config, group_state_fields)
    graphql_query_group_states = graphql_query(GroupStateType, group_state_fields, 'groupStates')

    return dict(
        model_class=GroupState,
        graphene_class=GroupStateType,
        graphene_fields=group_state_fields,
        create_mutation_class=CreateGroupState,
        update_mutation_class=UpdateGroupState,
        graphql_mutation=graphql_update_or_create_group_state,
        graphql_query=graphql_query_group_states
    )
Example #3
0
    """
        Create Settings mutation class
    """
    class Arguments:
        settings_data = type(
            'CreateSettingsInputType', (InputObjectType, ),
            input_type_fields(settings_fields, CREATE,
                              SettingsType))(required=True)


class UpdateSettings(UpsertSettings):
    """
        Update Settings mutation class
    """
    class Arguments:
        settings_data = type(
            'UpdateSettingsInputType', (InputObjectType, ),
            input_type_fields(settings_fields, UPDATE,
                              SettingsType))(required=True)


class SettingsMutation(graphene.ObjectType):
    create_settings = CreateSettings.Field()
    update_settings = UpdateSettings.Field()


graphql_update_or_create_settings = graphql_update_or_create(
    settings_mutation_config, settings_fields)
graphql_query_settings = graphql_query(SettingsType, settings_fields,
                                       'settings')
        search_location_data = type(
            'UpdateSearchLocationInputType', (InputObjectType, ),
            input_type_fields(search_location_fields, UPDATE,
                              SearchLocationType))(required=True)


class SearchLocationMutation(graphene.ObjectType):
    create_search_location = CreateSearchLocation.Field()
    update_search_location = UpdateSearchLocation.Field()


graphql_update_or_create_search_location = graphql_update_or_create(
    search_location_mutation_config, search_location_fields)
# Just use location fields here, the search fields will be added automatically
graphql_query_search_locations = graphql_query(SearchLocationType,
                                               search_location_fields,
                                               'searchLocations')


def graphql_query_locations_limited(search_location_fields):
    return graphql_query(SearchLocationType, search_location_fields,
                         'searchLocations')


# This must be referenced in settings.py
search_location_schema_config = dict(model_class=SearchLocation,
                                     graphene_class=SearchLocationType,
                                     graphene_fields=search_location_fields,
                                     query=SearchLocationQuery,
                                     mutation=SearchLocationMutation)
def graphql_query_locations_limited(search_location_fields):
    return graphql_query(SearchLocationType, search_location_fields,
                         'searchLocations')
Example #6
0

class UpdateRegion(UpsertRegion):
    """
        Update Region mutation class
    """
    class Arguments:
        region_data = type(
            'UpdateRegionInputType', (InputObjectType, ),
            input_type_fields(region_fields, UPDATE,
                              RegionType))(required=True)


class RegionMutation(graphene.ObjectType):
    create_region = CreateRegion.Field()
    update_region = UpdateRegion.Field()


graphql_update_or_create_region = graphql_update_or_create(
    region_mutation_config, region_fields)
graphql_query_regions = graphql_query(RegionType, region_fields, 'regions')


def graphql_query_regions_limited(region_fields):
    return graphql_query(RegionType, region_fields, 'regions')


graphql_query_regions_paginated = graphql_query(RegionPaginatedType,
                                                region_paginated_fields,
                                                'regionsPaginated')
Example #7
0
def graphql_query_regions_limited(region_fields):
    return graphql_query(RegionType, region_fields, 'regions')
class UpdateSearchJurisdiction(UpsertSearchJurisdiction):
    """
        Update SearchJurisdiction mutation class
    """
    class Arguments:
        search_jurisdiction_data = type(
            'UpdateSearchJurisdictionInputType', (InputObjectType, ),
            input_type_fields(search_jurisdiction_fields,
                              UPDATE,
                              SearchJurisdictionType,
                              with_filter_fields=False))(required=True)


class SearchJurisdictionMutation(graphene.ObjectType):
    create_search_jurisdiction = CreateSearchJurisdiction.Field()
    update_search_jurisdiction = UpdateSearchJurisdiction.Field()


graphql_update_or_create_jurisdiction = graphql_update_or_create(
    search_jurisdiction_mutation_config, search_jurisdiction_fields)
# Just use jurisdiction_fields, the search fields will be added automatically
graphql_query_jurisdictions = graphql_query(SearchJurisdictionType,
                                            search_jurisdiction_fields,
                                            'searchJurisdictions')


def graphql_query_jurisdictions_limited(search_jurisdiction_fields):
    return graphql_query(SearchJurisdictionType, search_jurisdiction_fields,
                         'searchJurisdictions')
def graphql_query_jurisdictions_limited(search_jurisdiction_fields):
    return graphql_query(SearchJurisdictionType, search_jurisdiction_fields,
                         'searchJurisdictions')
Example #10
0
    """
        Update Location mutation class
    """

    class Arguments:
        location_data = type('UpdateLocationInputType', (InputObjectType,),
                             input_type_fields(location_fields, UPDATE, LocationType))(required=True)


class LocationMutation(graphene.ObjectType):
    create_location = CreateLocation.Field()
    update_location = UpdateLocation.Field()


graphql_update_or_create_location = graphql_update_or_create(location_mutation_config, location_fields)
graphql_query_locations = graphql_query(LocationType, location_fields, 'locations')

# This must be referenced in settings.py
location_schema_config = dict(
    model_class=Location,
    graphene_class=LocationType,
    graphene_fields=location_fields,
    query=LocationQuery,
    mutation=LocationMutation
)

graphql_query_locations_paginated = graphql_query(
    LocationPaginatedType,
    location_paginated_fields,
    'locationsPaginated'
)
Example #11
0
def graphql_query_jurisdictions_limited(jurisdiction_fields):
    return graphql_query(JurisdictionType, jurisdiction_fields,
                         'jurisdictions')
Example #12
0
    class Arguments:
        jurisdiction_data = type(
            'UpdateJurisdictionInputType', (InputObjectType, ),
            input_type_fields(jurisdiction_fields, UPDATE,
                              JurisdictionType))(required=True)


class JurisdictionMutation(graphene.ObjectType):
    create_jurisdiction = CreateJurisdiction.Field()
    update_jurisdiction = UpdateJurisdiction.Field()


graphql_update_or_create_jurisdiction = graphql_update_or_create(
    jurisdiction_mutation_config, jurisdiction_fields)
graphql_query_jurisdictions = graphql_query(JurisdictionType,
                                            jurisdiction_fields,
                                            'jurisdictions')


def graphql_query_jurisdictions_limited(jurisdiction_fields):
    return graphql_query(JurisdictionType, jurisdiction_fields,
                         'jurisdictions')


graphql_query_jurisdictions_paginated = graphql_query(
    JurisdictionPaginatedType, jurisdiction_paginated_fields,
    'jurisdictionsPaginated')

graphql_query_jurisdictions_versioned = graphql_query(
    JurisdictionVersionedType, jurisdiction_versioned_fields,
    'jurisdictionsVersioned')
        if R.prop_or(False, 'id', update_or_create_values):
            resource, created = Resource.objects.update_or_create(**update_or_create_values)
        else:
            resource = Resource(**update_or_create_values['defaults'])
            resource.save()
        return UpsertResource(resource=resource)


class CreateResource(UpsertResource):
    """
        Create Resource mutation class
    """

    class Arguments:
        resource_data = type('CreateResourceInputType', (InputObjectType,),
                             input_type_fields(resource_fields, CREATE, ResourceType))(required=True)


class UpdateResource(UpsertResource):
    """
        Update Resource mutation class
    """

    class Arguments:
        resource_data = type('UpdateResourceInputType', (InputObjectType,),
                             input_type_fields(resource_fields, UPDATE, ResourceType))(required=True)


graphql_update_or_create_resource = graphql_update_or_create(resource_mutation_config, resource_fields)
graphql_query_resources = graphql_query('resources', resource_fields)
Example #14
0
    """
        Create Resource mutation class
    """
    class Arguments:
        resource_data = type(
            'CreateResourceInputType', (InputObjectType, ),
            input_type_fields(resource_fields, CREATE,
                              ResourceType))(required=True)


class UpdateResource(UpsertResource):
    """
        Update Resource mutation class
    """
    class Arguments:
        resource_data = type(
            'UpdateResourceInputType', (InputObjectType, ),
            input_type_fields(resource_fields, UPDATE,
                              ResourceType))(required=True)


class ResourceMutation(graphene.ObjectType):
    create_resource = CreateResource.Field()
    update_resource = UpdateResource.Field()


graphql_update_or_create_resource = graphql_update_or_create(
    resource_mutation_config, resource_fields)
graphql_query_resources = graphql_query(ResourceType, resource_fields,
                                        'resources')