Source code for sortinghat.core.schema

# -*- coding: utf-8 -*-
#
# Copyright (C) 2014-2020 Bitergia
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# Authors:
#     Santiago Dueñas <sduenas@bitergia.com>
#     Miguel Ángel Fernández <mafesan@bitergia.com>
#

import json
import re

import graphene
import graphql_jwt

from django.conf import settings
from django.core.paginator import Paginator
from django.db.models import Q, Subquery

from django.db.models import JSONField

from django_rq import enqueue

from graphene.types.generic import GenericScalar
from graphene.utils.str_converters import to_snake_case

from graphene_django.converter import convert_django_field
from graphene_django.types import DjangoObjectType

from grimoirelab_toolkit.datetime import (str_to_datetime,
                                          InvalidDateError)

from .api import (add_identity,
                  delete_identity,
                  update_profile,
                  move_identity,
                  lock,
                  unlock,
                  merge,
                  unmerge_identities,
                  add_organization,
                  add_domain,
                  delete_organization,
                  delete_domain,
                  enroll,
                  withdraw,
                  update_enrollment)
from .context import SortingHatContext
from .decorators import check_auth
from .errors import InvalidFilterError
from .jobs import (affiliate,
                   unify,
                   find_job,
                   get_jobs,
                   recommend_affiliations,
                   recommend_matches,
                   recommend_gender,
                   genderize)
from .models import (Organization,
                     Domain,
                     Country,
                     Individual,
                     Identity,
                     Profile,
                     Enrollment,
                     Transaction,
                     Operation)


[docs]@convert_django_field.register(JSONField) def convert_json_field_to_generic_scalar(field, registry=None): """Convert the content of a `JSONField` loading it as an object""" return OperationArgsType(description=field.help_text, required=not field.null)
[docs]def parse_date_filter(filter_value): """Extract the filter terms from a date filter The accepted formats are controlled by regular expressions matching two patterns: a comparison operator (>, >=, <, <=) and a date OR a range operator (..) between two dates. The accepted date format is ISO 8601, YYYY-MM-DDTHH:MM:SSZ, also accepting microseconds and time zone offset (YYYY-MM-DDTHH:MM:SS.ms+HH:HH). :param filter_value: String containing the filter value :returns: A dictionary including an operator and the datetime values """ # Accepted date format is ISO 8601, YYYY-MM-DDTHH:MM:SSZ (no `Z` is accepted too) filter_data = { "operator": None, "date1": None, "date2": None } iso_date_group = r"(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|\+\d{2}:\d{2})?)" # Filter with a comparison operator (>, >=, <, <=) and a date (e.g. `>=YYYY-MM-DDTHH:MM:SS`) oper_comparison = r"^(<=?|>=?)%s$" % iso_date_group # Filter with a range operator (..) between two dates # (e.g. YYYY-MM-DDTHH:MM:SSZ..YYYY-MM-DDTHH:MM:SSZ) range_comparison = r"^%s\.{2}%s$" % (iso_date_group, iso_date_group) oper_result = re.match(oper_comparison, filter_value) range_result = re.match(range_comparison, filter_value) if not oper_result and not range_result: raise ValueError('Filter format is not valid') if oper_result: filter_data['operator'] = oper_result.group(1) filter_data['date1'] = str_to_datetime(oper_result.group(2)) if range_result: filter_data['operator'] = '..' filter_data['date1'] = str_to_datetime(range_result.group(1)) filter_data['date2'] = str_to_datetime(range_result.group(4)) if filter_data['date1'] > filter_data['date2']: range_msg = 'Date range is invalid. Upper bound must be greater than the lower bound' raise ValueError(range_msg) return filter_data
[docs]class PaginationType(graphene.ObjectType): page = graphene.Int(description='Current page.') page_size = graphene.Int(description='Number of items per page.') num_pages = graphene.Int(description='Total number of pages.') has_next = graphene.Boolean(description='Whether there is a page after the current one.') has_prev = graphene.Boolean(description='Whether there is a page before the current one.') start_index = graphene.Int(description='Index of the first item on the page.') end_index = graphene.Int(description='Index of the last item on the page.') total_results = graphene.Int(description='Total number of items.')
[docs]class OperationArgsType(GenericScalar):
[docs] @classmethod def serialize(cls, value): value = super().serialize(value) value = json.loads(value) return value
[docs]class OperationType(DjangoObjectType): class Meta: model = Operation
[docs]class TransactionType(DjangoObjectType): class Meta: model = Transaction
[docs]class OrganizationType(DjangoObjectType): class Meta: model = Organization
[docs]class DomainType(DjangoObjectType): class Meta: model = Domain
[docs]class CountryType(DjangoObjectType): class Meta: model = Country
[docs]class IndividualType(DjangoObjectType): class Meta: model = Individual
[docs]class IdentityType(DjangoObjectType): class Meta: model = Identity
[docs]class ProfileType(DjangoObjectType): class Meta: model = Profile
[docs]class EnrollmentType(DjangoObjectType): class Meta: model = Enrollment
[docs]class AffiliationRecommendationType(graphene.ObjectType): uuid = graphene.String(description='The unique identifier of an individual.') organizations = graphene.List(graphene.String, description='List of recommended organizations.')
[docs]class MatchesRecommendationType(graphene.ObjectType): uuid = graphene.String(description='The unique identifier of an individual.') matches = graphene.List(graphene.String, description='List of recommended matches.')
[docs]class GenderRecommendationType(graphene.ObjectType): uuid = graphene.String(description='The unique identifier of an individual.') gender = graphene.String(description='The suggested gender of an individual') accuracy = graphene.Int(description='The probability of the gender to be accurate')
[docs]class AffiliationResultType(graphene.ObjectType): uuid = graphene.String(description='The unique identifier of an individual.') organizations = graphene.List( graphene.String, description='List of organizations an individual was affilated to using matching recommendations.' )
[docs]class UnifyResultType(graphene.ObjectType): merged = graphene.List( graphene.String, description='List of individuals that were merged using matching recommendations.' )
[docs]class GenderizeResultType(graphene.ObjectType): uuid = graphene.String(description='The unique identifier of an individual.') gender = graphene.String(description='The suggested gender of an individual') accuracy = graphene.Int(description='The probability of the gender to be accurate')
[docs]class JobResultType(graphene.Union): class Meta: types = (AffiliationResultType, AffiliationRecommendationType, MatchesRecommendationType, UnifyResultType, GenderRecommendationType, GenderizeResultType)
[docs]class JobType(graphene.ObjectType): job_id = graphene.String(description='Job identifier.') job_type = graphene.String(description='Type of job.') status = graphene.String(description='Job status (`started`, `deferred`, `finished`, `failed` or `scheduled`).') result = graphene.List(JobResultType, description='List of job results.') errors = graphene.List(graphene.String, description='List of errors.') enqueued_at = graphene.DateTime(description='Time the job was enqueued at.')
[docs]class ProfileInputType(graphene.InputObjectType): name = graphene.String(required=False, description='Name of the individual.') email = graphene.String(required=False, description='Email address of the individual.') gender = graphene.String(required=False, description='Gender of the individual.') gender_acc = graphene.Int( required=False, description='Gender accuracy (range of 1 to 100; by default, set to 100).' ) is_bot = graphene.Boolean(required=False, description='Whether an individual is a bot or not.') country_code = graphene.String( required=False, description='ISO-3166 country code. Examples: `DK` for Denmark, `IT` for Italy.' )
[docs]class CountryFilterType(graphene.InputObjectType): code = graphene.String( required=False, description='Filter countries with an ISO Alpha 2 country code. Examples: `DK` for Denmark, `IT` for Italy.' ) term = graphene.String( required=False, description='Filter countries whose name contains the term.' )
[docs]class OrganizationFilterType(graphene.InputObjectType): name = graphene.String( required=False, description='Filter organizations with an exact name match.' ) term = graphene.String( required=False, description='Filter organizations whose name or domains include the term.' )
[docs]class IdentityFilterType(graphene.InputObjectType): uuid = graphene.String( required=False, description='Find an identity by its unique identifier.' ) term = graphene.String( required=False, description='Filter individuals whose name, email or username contain the term.' ) is_locked = graphene.Boolean( required=False, description='Filters individuals by whether their profiles are locked and cannot be edited.' ) is_bot = graphene.Boolean( required=False, description='Filters individuals by whether they have been marked as bots.' ) gender = graphene.String( required=False, description='Filters individuals by their gender.' ) country = graphene.String( required=False, description='Filters individuals using an ISO Alpha 3 or Alpha 2 country code, or with a country name.\ Examples:\n * `GB`\n * `GBR`\n * `United Kingdom`' ) source = graphene.String( required=False, description='Filters individuals by the data source of their identities.' ) enrollment = graphene.String( required=False, description='Filters individuals affiliated to an organization.' ) enrollment_date = graphene.String( required=False, description='Filter with a comparison operator (>, >=, <, <=) and a date OR with a range operator (..) between\ two dates, following ISO-8601 format. Examples:\n* `>=2020-10-12T09:35:06.13045+01:00` \ \n * `2020-10-12T00:00:00..2020-11-22T00:00:00`.' ) is_enrolled = graphene.Boolean( required=False, description='Filter individuals by whether they are affiliated to any organization.' ) last_updated = graphene.String( required=False, description='Filter with a comparison operator (>, >=, <, <=) and a date OR with a range operator (..) between\ two dates, following ISO-8601 format. Examples:\n* `>=2020-10-12T09:35:06.13045+01:00` \ \n * `2020-10-12T00:00:00..2020-11-22T00:00:00`.' )
[docs]class TransactionFilterType(graphene.InputObjectType): tuid = graphene.String( required=False, description='Find a transaction using its unique id.' ) name = graphene.String( required=False, description='Find a transaction using its name.' ) is_closed = graphene.Boolean( required=False, description='Filter transactions by whether they are closed.' ) from_date = graphene.DateTime( required=False, description='Find transactions created after a date, following ISO-8601 format. For example, `2020-04-22T00:00:00Z`.' ) to_date = graphene.DateTime( required=False, description='Find transactions created before a date, following ISO-8601 format. For example, `2020-04-22T00:00:00Z`.' ) authored_by = graphene.String( required=False, description='Filter transactions using the username of their author.' )
[docs]class OperationFilterType(graphene.InputObjectType): ouid = graphene.String( required=False, description='Find an operation using its unique id.' ) op_type = graphene.String( required=False, description='Filter operations by their type: `ADD`, `DELETE` or `UPDATE`.' ) entity_type = graphene.String( required=False, description='Filter by the type of entity involved in the operations, eg. `individual`, `profile`, `enrollment`.' ) target = graphene.String( required=False, description='Filter by the argument which the operation is directed to.' ) from_date = graphene.DateTime( required=False, description='Find operations created after a date, following ISO-8601 format. For example, `2020-04-22T00:00:00Z`.' ) to_date = graphene.DateTime( required=False, description='Find operations created before a date, following ISO-8601 format. For example, `2020-04-22T00:00:00Z`.' )
[docs]class AbstractPaginatedType(graphene.ObjectType):
[docs] @classmethod def create_paginated_result(cls, query, page=1, page_size=settings.DEFAULT_GRAPHQL_PAGE_SIZE): paginator = Paginator(query, page_size) result = paginator.page(page) entities = result.object_list page_info = PaginationType( page=result.number, page_size=page_size, num_pages=paginator.num_pages, has_next=result.has_next(), has_prev=result.has_previous(), start_index=result.start_index(), end_index=result.end_index(), total_results=len(query) ) return cls(entities=entities, page_info=page_info)
[docs]class CountryPaginatedType(AbstractPaginatedType): entities = graphene.List(CountryType, description='A list of countries.') page_info = graphene.Field(PaginationType, description='Information to aid in pagination.')
[docs]class OrganizationPaginatedType(AbstractPaginatedType): entities = graphene.List(OrganizationType, description='A list of organizations.') page_info = graphene.Field(PaginationType, description='Information to aid in pagination.')
[docs]class IdentityPaginatedType(AbstractPaginatedType): entities = graphene.List(IndividualType, description='A list of identities.') page_info = graphene.Field(PaginationType, description='Information to aid in pagination.')
[docs]class TransactionPaginatedType(AbstractPaginatedType): entities = graphene.List(TransactionType, description='A list of transactions.') page_info = graphene.Field(PaginationType, description='Information to aid in pagination.')
[docs]class OperationPaginatedType(AbstractPaginatedType): entities = graphene.List(OperationType, description='A list of operations.') page_info = graphene.Field(PaginationType, description='Information to aid in pagination.')
[docs]class JobPaginatedType(AbstractPaginatedType): entities = graphene.List(JobType, description='A list of jobs.') page_info = graphene.Field(PaginationType, description='Information to aid in pagination.')
[docs]class AddOrganization(graphene.Mutation):
[docs] class Arguments: name = graphene.String()
organization = graphene.Field(lambda: OrganizationType)
[docs] @check_auth def mutate(self, info, name): user = info.context.user ctx = SortingHatContext(user) org = add_organization(ctx, name) return AddOrganization( organization=org )
[docs]class DeleteOrganization(graphene.Mutation):
[docs] class Arguments: name = graphene.String()
organization = graphene.Field(lambda: OrganizationType)
[docs] @check_auth def mutate(self, info, name): user = info.context.user ctx = SortingHatContext(user) org = delete_organization(ctx, name) return DeleteOrganization( organization=org )
[docs]class AddDomain(graphene.Mutation):
[docs] class Arguments: organization = graphene.String() domain = graphene.String() is_top_domain = graphene.Boolean()
domain = graphene.Field(lambda: DomainType)
[docs] @check_auth def mutate(self, info, organization, domain, is_top_domain=False): user = info.context.user ctx = SortingHatContext(user) dom = add_domain(ctx, organization, domain, is_top_domain=is_top_domain) return AddDomain( domain=dom )
[docs]class DeleteDomain(graphene.Mutation):
[docs] class Arguments: domain = graphene.String()
domain = graphene.Field(lambda: DomainType)
[docs] @check_auth def mutate(self, info, domain): user = info.context.user ctx = SortingHatContext(user) dom = delete_domain(ctx, domain) return DeleteDomain( domain=dom )
[docs]class AddIdentity(graphene.Mutation):
[docs] class Arguments: source = graphene.String() name = graphene.String() email = graphene.String() username = graphene.String() uuid = graphene.String()
uuid = graphene.Field(lambda: graphene.String) individual = graphene.Field(lambda: IndividualType)
[docs] @check_auth def mutate(self, info, source, name=None, email=None, username=None, uuid=None): user = info.context.user ctx = SortingHatContext(user) identity = add_identity(ctx, source, name=name, email=email, username=username, uuid=uuid) individual = identity.individual return AddIdentity( uuid=identity.uuid, individual=individual )
[docs]class DeleteIdentity(graphene.Mutation):
[docs] class Arguments: uuid = graphene.String()
uuid = graphene.Field(lambda: graphene.String) individual = graphene.Field(lambda: IndividualType)
[docs] @check_auth def mutate(self, info, uuid): user = info.context.user ctx = SortingHatContext(user) individual = delete_identity(ctx, uuid) return DeleteIdentity( uuid=uuid, individual=individual )
[docs]class Lock(graphene.Mutation):
[docs] class Arguments: uuid = graphene.String()
uuid = graphene.Field(lambda: graphene.String) individual = graphene.Field(lambda: IndividualType)
[docs] @check_auth def mutate(self, info, uuid): user = info.context.user ctx = SortingHatContext(user) individual = lock(ctx, uuid) return Lock( uuid=uuid, individual=individual )
[docs]class Unlock(graphene.Mutation):
[docs] class Arguments: uuid = graphene.String()
uuid = graphene.Field(lambda: graphene.String) individual = graphene.Field(lambda: IndividualType)
[docs] @check_auth def mutate(self, info, uuid): user = info.context.user ctx = SortingHatContext(user) individual = unlock(ctx, uuid) return Unlock( uuid=uuid, individual=individual )
[docs]class UpdateProfile(graphene.Mutation):
[docs] class Arguments: uuid = graphene.String() data = ProfileInputType()
uuid = graphene.Field(lambda: graphene.String) individual = graphene.Field(lambda: IndividualType)
[docs] @check_auth def mutate(self, info, uuid, data): user = info.context.user ctx = SortingHatContext(user) individual = update_profile(ctx, uuid, **data) return UpdateProfile( uuid=individual.mk, individual=individual )
[docs]class MoveIdentity(graphene.Mutation):
[docs] class Arguments: from_uuid = graphene.String() to_uuid = graphene.String()
uuid = graphene.Field(lambda: graphene.String) individual = graphene.Field(lambda: IndividualType)
[docs] @check_auth def mutate(self, info, from_uuid, to_uuid): user = info.context.user ctx = SortingHatContext(user) individual = move_identity(ctx, from_uuid, to_uuid) return MoveIdentity( uuid=individual.mk, individual=individual )
[docs]class Merge(graphene.Mutation):
[docs] class Arguments: from_uuids = graphene.List(graphene.String) to_uuid = graphene.String()
uuid = graphene.Field(lambda: graphene.String) individual = graphene.Field(lambda: IndividualType)
[docs] @check_auth def mutate(self, info, from_uuids, to_uuid): user = info.context.user ctx = SortingHatContext(user) individual = merge(ctx, from_uuids, to_uuid) return Merge( uuid=individual.mk, individual=individual )
[docs]class UnmergeIdentities(graphene.Mutation):
[docs] class Arguments: uuids = graphene.List(graphene.String)
uuids = graphene.Field(lambda: graphene.List(graphene.String)) individuals = graphene.Field(lambda: graphene.List(IndividualType))
[docs] @check_auth def mutate(self, info, uuids): user = info.context.user ctx = SortingHatContext(user) individuals = unmerge_identities(ctx, uuids) uuids = [individual.mk for individual in individuals] return UnmergeIdentities( uuids=uuids, individuals=individuals )
[docs]class Enroll(graphene.Mutation):
[docs] class Arguments: uuid = graphene.String() organization = graphene.String() from_date = graphene.DateTime(required=False) to_date = graphene.DateTime(required=False) force = graphene.Boolean(required=False)
uuid = graphene.Field(lambda: graphene.String) individual = graphene.Field(lambda: IndividualType)
[docs] @check_auth def mutate(self, info, uuid, organization, from_date=None, to_date=None, force=False): user = info.context.user ctx = SortingHatContext(user) individual = enroll(ctx, uuid, organization, from_date=from_date, to_date=to_date, force=force) return Enroll( uuid=individual.mk, individual=individual )
[docs]class Withdraw(graphene.Mutation):
[docs] class Arguments: uuid = graphene.String() organization = graphene.String() from_date = graphene.DateTime(required=False) to_date = graphene.DateTime(required=False)
uuid = graphene.Field(lambda: graphene.String) individual = graphene.Field(lambda: IndividualType)
[docs] @check_auth def mutate(self, info, uuid, organization, from_date=None, to_date=None): user = info.context.user ctx = SortingHatContext(user) individual = withdraw(ctx, uuid, organization, from_date=from_date, to_date=to_date) return Withdraw( uuid=individual.mk, individual=individual )
[docs]class UpdateEnrollment(graphene.Mutation):
[docs] class Arguments: uuid = graphene.String() organization = graphene.String() from_date = graphene.DateTime() to_date = graphene.DateTime() new_from_date = graphene.DateTime(required=False) new_to_date = graphene.DateTime(required=False) force = graphene.Boolean(required=False)
uuid = graphene.Field(lambda: graphene.String) individual = graphene.Field(lambda: IndividualType)
[docs] @check_auth def mutate(self, info, uuid, organization, from_date, to_date, new_from_date=None, new_to_date=None, force=True): user = info.context.user ctx = SortingHatContext(user) individual = update_enrollment(ctx, uuid, organization, from_date=from_date, to_date=to_date, new_from_date=new_from_date, new_to_date=new_to_date, force=force) return UpdateEnrollment( uuid=individual.mk, individual=individual )
[docs]class RecommendAffiliations(graphene.Mutation):
[docs] class Arguments: uuids = graphene.List(graphene.String, required=False)
job_id = graphene.Field(lambda: graphene.String)
[docs] @check_auth def mutate(self, info, uuids=None): user = info.context.user ctx = SortingHatContext(user) job = enqueue(recommend_affiliations, ctx, uuids) return RecommendAffiliations( job_id=job.id )
[docs]class RecommendMatches(graphene.Mutation):
[docs] class Arguments: source_uuids = graphene.List(graphene.String) target_uuids = graphene.List(graphene.String, required=False) criteria = graphene.List(graphene.String) verbose = graphene.Boolean(required=False)
job_id = graphene.Field(lambda: graphene.String)
[docs] @check_auth def mutate(self, info, source_uuids, criteria, target_uuids=None, verbose=False): user = info.context.user ctx = SortingHatContext(user) job = enqueue(recommend_matches, ctx, source_uuids, target_uuids, criteria, verbose) return RecommendMatches( job_id=job.id )
[docs]class RecommendGender(graphene.Mutation):
[docs] class Arguments: uuids = graphene.List(graphene.String)
job_id = graphene.Field(lambda: graphene.String)
[docs] @check_auth def mutate(self, info, uuids=None): user = info.context.user ctx = SortingHatContext(user) job = enqueue(recommend_gender, ctx, uuids) return RecommendGender( job_id=job.id )
[docs]class Affiliate(graphene.Mutation):
[docs] class Arguments: uuids = graphene.List(graphene.String, required=False)
job_id = graphene.Field(lambda: graphene.String)
[docs] @check_auth def mutate(self, info, uuids=None): user = info.context.user ctx = SortingHatContext(user) job = enqueue(affiliate, ctx, uuids) return Affiliate( job_id=job.id )
[docs]class Unify(graphene.Mutation):
[docs] class Arguments: source_uuids = graphene.List(graphene.String) target_uuids = graphene.List(graphene.String, required=False) criteria = graphene.List(graphene.String)
job_id = graphene.Field(lambda: graphene.String)
[docs] @check_auth def mutate(self, info, source_uuids, criteria, target_uuids=None): user = info.context.user ctx = SortingHatContext(user) job = enqueue(unify, ctx, source_uuids, target_uuids, criteria) return Unify( job_id=job.id )
[docs]class Genderize(graphene.Mutation):
[docs] class Arguments: uuids = graphene.List(graphene.String)
job_id = graphene.Field(lambda: graphene.String)
[docs] @check_auth def mutate(self, info, uuids=None): user = info.context.user ctx = SortingHatContext(user) job = enqueue(genderize, ctx, uuids) return Genderize( job_id=job.id )
[docs]class SortingHatQuery: countries = graphene.Field( CountryPaginatedType, page_size=graphene.Int(), page=graphene.Int(), filters=CountryFilterType(required=False), description='Find countries.' ) organizations = graphene.Field( OrganizationPaginatedType, page_size=graphene.Int(), page=graphene.Int(), filters=OrganizationFilterType(required=False), description='Find organizations.' ) individuals = graphene.Field( IdentityPaginatedType, page_size=graphene.Int(), page=graphene.Int(), filters=IdentityFilterType(required=False), order_by=graphene.String(required=False), description='Find individuals.' ) transactions = graphene.Field( TransactionPaginatedType, page_size=graphene.Int(), page=graphene.Int(), filters=TransactionFilterType(required=False), description='Find transactions.' ) operations = graphene.Field( OperationPaginatedType, page_size=graphene.Int(), page=graphene.Int(), filters=OperationFilterType(required=False), description='Find operations.' ) job = graphene.Field( JobType, job_id=graphene.String(), description='Find a single job by its id.' ) jobs = graphene.Field( JobPaginatedType, page_size=graphene.Int(), page=graphene.Int(), description='Get all jobs.' )
[docs] @check_auth def resolve_countries(self, info, filters=None, page=1, page_size=settings.DEFAULT_GRAPHQL_PAGE_SIZE): query = Country.objects.order_by('code') if filters and 'code' in filters: query = query.filter(code=filters['code']) if filters and 'term' in filters: query = query.filter(name__icontains=filters['term']) return CountryPaginatedType.create_paginated_result(query, page, page_size=page_size)
[docs] @check_auth def resolve_organizations(self, info, filters=None, page=1, page_size=settings.DEFAULT_GRAPHQL_PAGE_SIZE, **kwargs): query = Organization.objects.order_by('name') if filters and 'name' in filters: query = query.filter(name=filters['name']) if filters and 'term' in filters: search_term = filters['term'] query = query.filter(Q(name__icontains=search_term) | Q(name__in=Subquery(Domain.objects .filter(domain__icontains=search_term) .values_list('organization__name')))) return OrganizationPaginatedType.create_paginated_result(query, page, page_size=page_size)
[docs] @check_auth def resolve_individuals(self, info, filters=None, page=1, page_size=settings.DEFAULT_GRAPHQL_PAGE_SIZE, order_by='mk', **kwargs): query = Individual.objects.order_by(to_snake_case(order_by)) if filters and 'uuid' in filters: indv_uuid = filters['uuid'] # Search among all the individuals and their identities query = query.filter(mk__in=Subquery(Identity.objects .filter(Q(uuid=indv_uuid) | Q(individual__mk=indv_uuid)) .values_list('individual__mk'))) if filters and 'term' in filters: search_term = filters['term'] # Filter matching individuals by their mk and their identities query = query.filter(mk__in=Subquery(Identity.objects .filter(Q(name__icontains=search_term) | Q(email__icontains=search_term) | Q(username__icontains=search_term) | Q(individual__profile__name__icontains=search_term) | Q(individual__profile__email__icontains=search_term)) .values_list('individual__mk'))) if filters and 'is_locked' in filters: query = query.filter(is_locked=filters['is_locked']) if filters and 'is_bot' in filters: query = query.filter(mk__in=Subquery(Profile.objects .filter(is_bot=filters['is_bot']) .values_list('individual__mk'))) if filters and 'gender' in filters: query = query.filter(profile__gender=filters['gender']) if filters and 'country' in filters: country = filters['country'] query = query.filter(mk__in=Subquery(Profile.objects .filter(Q(country__name__icontains=country) | Q(country__code=country) | Q(country__alpha3=country)) .values_list('individual__mk'))) if filters and 'source' in filters: query = query.filter(identities__source=filters['source']) if filters and 'enrollment' in filters: query = query.filter(enrollments__organization__name__icontains=filters['enrollment']) if filters and 'enrollment_date' in filters: # Accepted date format is ISO 8601, YYYY-MM-DDTHH:MM:SS try: filter_data = parse_date_filter(filters['enrollment_date']) except ValueError as e: raise InvalidFilterError(filter_name='enrollment_date', msg=e) except InvalidDateError as e: raise InvalidFilterError(filter_name='enrollment_date', msg=e) date1 = filter_data['date1'] date2 = filter_data['date2'] if filter_data['operator']: operator = filter_data['operator'] if operator == '<': query = query.filter(mk__in=Subquery(Enrollment.objects .filter(start__lt=date1) .values_list('individual__mk'))) elif operator == '<=': query = query.filter(mk__in=Subquery(Enrollment.objects .filter(start__lte=date1) .values_list('individual__mk'))) elif operator == '>': query = query.filter(mk__in=Subquery(Enrollment.objects .filter(end__gt=date1) .values_list('individual__mk'))) elif operator == '>=': query = query.filter(mk__in=Subquery(Enrollment.objects .filter(end__gte=date1) .values_list('individual__mk'))) elif operator == '..': query = query.filter(mk__in=Subquery(Enrollment.objects .filter(start__lte=date2, end__gte=date1) .values_list('individual__mk'))) if filters and 'is_enrolled' in filters: query = query.filter(enrollments__isnull=not filters['is_enrolled']) if filters and 'last_updated' in filters: # Accepted date format is ISO 8601, YYYY-MM-DDTHH:MM:SS try: filter_data = parse_date_filter(filters['last_updated']) except ValueError as e: raise InvalidFilterError(filter_name='last_updated', msg=e) except InvalidDateError as e: raise InvalidFilterError(filter_name='last_updated', msg=e) date1 = filter_data['date1'] date2 = filter_data['date2'] if filter_data['operator']: operator = filter_data['operator'] if operator == '<': query = query.filter(last_modified__lt=date1) elif operator == '<=': query = query.filter(last_modified__lte=date1) elif operator == '>': query = query.filter(last_modified__gt=date1) elif operator == '>=': query = query.filter(last_modified__gte=date1) elif operator == '..': query = query.filter(last_modified__range=(date1, date2)) return IdentityPaginatedType.create_paginated_result(query, page, page_size=page_size)
[docs] @check_auth def resolve_job(self, info, job_id): job = find_job(job_id) status = job.get_status() job_type = job.func_name.split('.')[-1] enqueued_at = job.enqueued_at result = None errors = None if (job.result) and (job_type == 'affiliate'): errors = job.result['errors'] result = [ AffiliationResultType(uuid=uuid, organizations=orgs) for uuid, orgs in job.result['results'].items() ] elif (job.result) and (job_type == 'recommend_affiliations'): result = [ AffiliationRecommendationType(uuid=uuid, organizations=orgs) for uuid, orgs in job.result['results'].items() ] elif (job.result) and (job_type == 'recommend_matches'): result = [ MatchesRecommendationType(uuid=uuid, matches=matches) for uuid, matches in job.result['results'].items() ] elif (job.result) and (job_type == 'recommend_gender'): result = [ GenderRecommendationType(uuid=uuid, gender=rec['gender'], accuracy=rec['accuracy']) for uuid, rec in job.result['results'].items() ] elif (job.result) and (job_type == 'unify'): errors = job.result['errors'] result = [ UnifyResultType(merged=job.result['results']) ] elif (job.result) and (job_type == 'genderize'): errors = job.result['errors'] result = [ GenderizeResultType(uuid=uuid, gender=rec[0], accuracy=rec[1]) for uuid, rec in job.result['results'].items() ] elif status == 'failed': errors = [job.exc_info] return JobType(job_id=job_id, job_type=job_type, status=status, result=result, errors=errors, enqueued_at=enqueued_at)
[docs] @check_auth def resolve_jobs(self, info, page=1, page_size=settings.DEFAULT_GRAPHQL_PAGE_SIZE): jobs = get_jobs() result = [] for job in jobs: job_id = job.get_id() status = job.get_status() job_type = job.func_name.split('.')[-1] enqueued_at = job.enqueued_at result.append(JobType(job_id=job_id, job_type=job_type, status=status, result=[], errors=[], enqueued_at=enqueued_at)) return JobPaginatedType.create_paginated_result(result, page, page_size=page_size)
[docs] @check_auth def resolve_transactions(self, info, filters=None, page=1, page_size=settings.DEFAULT_GRAPHQL_PAGE_SIZE, **kwargs): query = Transaction.objects.order_by('created_at') if filters and 'tuid' in filters: query = query.filter(tuid=filters['tuid']) if filters and 'name' in filters: query = query.filter(name=filters['name']) if filters and 'is_closed' in filters: query = query.filter(is_closed=filters['isClosed']) if filters and 'from_date' in filters: query = query.filter(created_at__gte=filters['from_date']) if filters and 'to_date' in filters: query = query.filter(created_at__lte=filters['to_date']) if filters and 'authored_by' in filters: query = query.filter(authored_by=filters['authored_by']) return TransactionPaginatedType.create_paginated_result(query, page, page_size=page_size)
[docs] @check_auth def resolve_operations(self, info, filters=None, page=1, page_size=settings.DEFAULT_GRAPHQL_PAGE_SIZE, **kwargs): query = Operation.objects.order_by('timestamp') if filters and 'ouid' in filters: query = query.filter(ouid=filters['ouid']) if filters and 'op_type' in filters: query = query.filter(op_type=filters['op_type']) if filters and 'entity_type' in filters: query = query.filter(entity_type=filters['entity_type']) if filters and 'target' in filters: query = query.filter(target=filters['target']) if filters and 'from_date' in filters: query = query.filter(timestamp__gte=filters['from_date']) if filters and 'to_date' in filters: query = query.filter(timestamp__lte=filters['to_date']) return OperationPaginatedType.create_paginated_result(query, page, page_size=page_size)
[docs]class SortingHatMutation(graphene.ObjectType): add_organization = AddOrganization.Field( description='Add an organization to the registry.' ) delete_organization = DeleteOrganization.Field( description='Remove an organization from the registry. Related information\ such as domains or enrollments is also removed.' ) add_domain = AddDomain.Field( description='Add a new domain to an organization. The new domain is set\ as a top domain by default. A domain can only be assigned to one organization.' ) delete_domain = DeleteDomain.Field( description='Remove a domain from the registry.' ) add_identity = AddIdentity.Field( description='Add a new identity to the registry. A new individual will be\ also added and associated to the new identity unless an `uuid` is provided.\ When `uuid` is set, it creates a new identity associated to the individual\ defined by this identifier.' ) delete_identity = DeleteIdentity.Field( description='Remove an identity from the registry. If the `uuid` also\ belongs to an individual, this entry and those identities linked to it\ will be removed too.' ) update_profile = UpdateProfile.Field( description='Update an individual profile.' ) move_identity = MoveIdentity.Field( description='Shift the identity identified by `from_uuid` to the individual\ identified by `to_uuid`.' ) lock = Lock.Field( description='Lock an individual so it cannot be modified.' ) unlock = Unlock.Field( description='Unlock an individual so it can be modified.' ) merge = Merge.Field( description='Join a list of individuals, defined in `from_uuid` by any of\ their valid identities ids, into `to_uuid` individual. Identities and enrollments\ related to each `from_uuid` will be assigned to `to_uuid`. In addition, each\ `from_uuid` will be removed from the registry.' ) unmerge_identities = UnmergeIdentities.Field( description='Separate a list of `uuid` identities, creating an individual for each one.' ) enroll = Enroll.Field( description='Enroll an individual in an organization. Existing enrollments\ for the same individual and organization which overlap with the new period\ will be merged into a single enrollment.' ) withdraw = Withdraw.Field( description='Withdraw an individual identified by `uuid` from the given\ `organization` during the given period of time.' ) update_enrollment = UpdateEnrollment.Field( description='Update one or more enrollments from an individual given a new\ date range. By default, `force` is set to `true`. In case any of the new\ dates are missing, the former value for that date will be preserved.' ) recommend_affiliations = RecommendAffiliations.Field( description='Recommend organizations for a list of individuals based on their emails.' ) recommend_matches = RecommendMatches.Field( description='Recommend identity matches for a list of individuals based\ on a list of criteria composed by `email`, `name` and/or `username`.' ) recommend_gender = RecommendGender.Field( description='Recommend genders for a list of individuals based on their names\ using the genderize.io API.' ) affiliate = Affiliate.Field( description='Affiliate a set of individuals using recommendations.' ) unify = Unify.Field( description='Unify a set of individuals by merging them using matching recommendations.' ) genderize = Genderize.Field( description='Autocomplete the gender information of a set of individuals\ using genderize.io recommendations.' ) # JWT authentication token_auth = graphql_jwt.ObtainJSONWebToken.Field() verify_token = graphql_jwt.Verify.Field(description='Verify a JSON Web Token.') refresh_token = graphql_jwt.Refresh.Field(description='Refresh a JSON Web Token.')