def test__one_to_one_field__is_properly_converted(self): class MockSuperModel(models.Model): name = models.CharField(max_length=128, default="Heidi Klum") class MockModel(models.Model): super_model = models.OneToOneField(MockSuperModel, on_delete=models.CASCADE, related_name='modelling') # Model-ling... Get it? xD # Convert primary relation registry = get_global_registry() field = MockModel._meta.get_field("super_model") result = convert_django_field_with_choices( field, registry=registry ) self.assertIsInstance(result, graphene.types.ID) self.assertEqual(result.kwargs.get("required"), True) # Convert backward relation field = MockSuperModel._meta.get_field("modelling") result = convert_django_field_with_choices( field, registry=registry ) self.assertIsInstance(result, graphene.types.ID)
def test_choices_field__choice_field_in_different_locations__accepts_different_parameters(self): class MockModel(models.Model): field_with_choices = models.CharField( max_length=16, choices=( ("A", "Choice a"), ("B", "Choice b"), ("C", "Choice c"), ) ) registry = get_global_registry() field = MockModel._meta.get_field("field_with_choices") result = convert_django_field_with_choices( field, registry=registry ) self.assertIsInstance(result, graphene.types.Enum) self.assertEqual(result.kwargs.get("required"), True) result = convert_django_field_with_choices( field, registry, required=False ) self.assertIsInstance(result, graphene.types.Enum) self.assertEqual(result.kwargs.get("required"), False)
def _convert_filter_field(filter_field, model): filter_field_split = filter_field.split("__") field_name = filter_field_split[0] model_field = model._meta.get_field(field_name) filter_field_is_list = False # In this case, we have a deeply nested field. To find the correct field, we recurse into the string if len(filter_field_split) > 2: return _convert_filter_field( "__".join(filter_field_split[1:]), model_field.related_model, # This fails only on bad input ) if len(filter_field_split) == 2: # If we have an "__in" final part of the filter, we are now dealing with # a list of things. Note that all other variants can be coerced directly # on the filter-call, so we don't really have to deal with other cases here. if filter_field_split[1] == "in": filter_field_is_list = True elif model_field.related_model is not None: # Check if the field has a related model. If it does, we recurse one last time, otherwise # we are dealing with the final field and some filter, e.g. fieldname__contains. return _convert_filter_field( "__".join(filter_field_split[1:]), model_field.related_model, # This fails only on bad input ) field_type = convert_django_field_with_choices(model_field, required=False) # Handle this case by "deconstructing" the field type class, and pass it as an argument to # graphene.List if filter_field_is_list: field_type = graphene.List(type(field_type), required=False) return field_type
def test__field_has_default__required_is_set_appropriately(self): class MockModel(models.Model): field_with_choices = models.CharField(max_length=16, choices=( ("A", "Choice a"), ("B", "Choice b"), ("C", "Choice c"), ), default="A") field = MockModel._meta.get_field("field_with_choices") result = convert_django_field_with_choices(field, ) self.assertIsInstance(result, graphene.types.Enum) self.assertEqual(result.kwargs.get("required"), False)
def get_input_fields_for_model( model, only_fields, exclude_fields, optional_fields=(), required_fields=(), many_to_many_extras=None, foreign_key_extras=None, many_to_one_extras=None, parent_type_name="", field_types=None, ignore_primary_key=True, ) -> OrderedDict: registry = get_global_registry() meta_registry = get_type_meta_registry() model_fields = get_model_fields(model) many_to_many_extras = many_to_many_extras or {} foreign_key_extras = foreign_key_extras or {} many_to_one_extras = many_to_one_extras or {} field_types = field_types or {} fields = OrderedDict() fields_lookup = {} for name, field in model_fields: # We ignore the primary key if getattr(field, "primary_key", False) and ignore_primary_key: continue # If the field has an override, use that if name in field_types: fields[name] = field_types[name] continue # Save for later fields_lookup[name] = field is_not_in_only = only_fields and name not in only_fields # is_already_created = name in options.fields is_excluded = name in exclude_fields # or is_already_created # https://docs.djangoproject.com/en/1.10/ref/models/fields/#django.db.models.ForeignKey.related_query_name is_no_backref = str(name).endswith("+") if is_not_in_only or is_excluded or is_no_backref: # We skip this field if we specify only_fields and is not # in there. Or when we exclude this field in exclude_fields. # Or when there is no back reference. continue required = None if name in optional_fields: required = False elif name in required_fields: required = True converted = convert_django_field_with_choices( field, registry, required, many_to_many_extras.get(name, {}).get("exact"), foreign_key_extras.get(name, {}), ) fields[name] = converted # Create extra many_to_many_fields for name, extras in many_to_many_extras.items(): field = fields_lookup.get(name) if field is None: raise GraphQLError( f"Error adding extras for {name} in model f{model}. Field {name} does not exist." ) for extra_name, data in extras.items(): # This is handled above if extra_name == "exact": continue if isinstance(data, bool): data = {} _type_name = data.get("type") _field = convert_many_to_many_field(field, registry, False, data, None) # Default to the same as the "exact" version if not _field: _field = fields[name] # operation = data.get('operation') or get_likely_operation_from_name(extra_name) fields[name + "_" + extra_name] = _field for name, extras in many_to_one_extras.items(): field = fields_lookup.get(name) if field is None: raise GraphQLError( f"Error adding extras for {name} in model f{model}. Field {name} does not exist." ) for extra_name, data in extras.items(): argument_name = data.get("name", name + "_" + extra_name) # Override default if extra_name == "exact": argument_name = name if isinstance(data, bool): data = {"type": "ID"} _type = data.get("type") if not _type or _type == "auto": # Create new type. operation_name = data.get( "operation", get_likely_operation_from_name(extra_name)) _type_name = data.get( "type_name", f"{parent_type_name or ''}{operation_name.capitalize()}{model.__name__}{name.capitalize()}", ) converted_fields = get_input_fields_for_model( field.related_model, data.get("only_fields", ()), data.get( "exclude_fields", (field.field.name, ) ), # Exclude the field referring back to the foreign key data.get("optional_fields", ()), data.get("required_fields", ()), data.get("many_to_many_extras"), data.get("foreign_key_extras"), data.get("many_to_one_extras"), parent_type_name=_type_name, field_types=data.get("field_types"), # Don't ignore the primary key on updates ignore_primary_key=operation_name != "update") InputType = type(_type_name, (InputObjectType, ), converted_fields) meta_registry.register( _type_name, { "auto_context_fields": data.get( "auto_context_fields", {}), "optional_fields": data.get("optional_fields", ()), "required_fields": data.get("required_fields", ()), "many_to_many_extras": data.get( "many_to_many_extras", {}), "many_to_one_extras": data.get("many_to_one_extras", {}), "foreign_key_extras": data.get("auto_context_fields", {}), "field_types": data.get("field_types", {}), }, ) _field = graphene.List( type(_type_name, (InputObjectType, ), converted_fields), required=False, ) else: _field = convert_many_to_many_field(field, registry, False, data, None) fields[argument_name] = _field return fields
def get_input_fields_for_model( model, only_fields, exclude_fields, optional_fields=(), required_fields=(), many_to_many_extras=None, foreign_key_extras=None, many_to_one_extras=None, one_to_one_extras=None, parent_type_name="", field_types=None, ignore_primary_key=True, ) -> OrderedDict: registry = get_global_registry() meta_registry = get_type_meta_registry() model_fields = get_model_fields(model) many_to_many_extras = resolve_many_to_many_extra_auto_field_names( many_to_many_extras or {}, model, parent_type_name) many_to_one_extras = resolve_many_to_one_extra_auto_field_names( many_to_one_extras or {}, model, parent_type_name) foreign_key_extras = resolve_foreign_key_extra_auto_field_names( foreign_key_extras or {}, model, parent_type_name) one_to_one_extras = resolve_one_to_one_extra_auto_field_names( one_to_one_extras or {}, model, parent_type_name) field_types = field_types or {} one_to_one_fields: List[Union[models.OneToOneRel, models.OneToOneField]] = [] fields = OrderedDict() fields_lookup = {} for name, field in model_fields: # We ignore the primary key if getattr(field, "primary_key", False) and ignore_primary_key: continue # If the field has an override, use that if name in field_types: fields[name] = field_types[name] continue # Save for later fields_lookup[name] = field is_not_in_only = only_fields and name not in only_fields # is_already_created = name in options.fields is_excluded = name in exclude_fields # or is_already_created # https://docs.djangoproject.com/en/1.10/ref/models/fields/#django.db.models.ForeignKey.related_query_name is_no_backref = str(name).endswith("+") if is_not_in_only or is_excluded or is_no_backref: # We skip this field if we specify only_fields and is not # in there. Or when we exclude this field in exclude_fields. # Or when there is no back reference. continue required = None if name in optional_fields: required = False elif name in required_fields: required = True converted = convert_django_field_with_choices( field, registry, required, many_to_many_extras.get(name, {}).get("exact"), foreign_key_extras.get(name, {}), one_to_one_extras.get(name, {}), ) fields[name] = converted if type(field) in (models.OneToOneRel, models.OneToOneField): one_to_one_fields.append(field) # Create the one to one field types here. for name, data in one_to_one_extras.items(): field: Union[models.OneToOneRel, models.OneToOneField] = fields_lookup.get(name) if field is None: raise ValueError( f"Error adding extras for {name} in model f{model}. Field {name} does not exist." ) type_name = data.get("type") if type_name == "ID": continue # On OneToOnerels we can get the reverse field name from "field.field.name", as we have a direct # reference to the reverse field that way. For OneToOneFields we need to go through "field.target_field". reverse_field_name = (field.field.name if isinstance( field, models.OneToOneRel) else field.remote_field.name) converted_fields = get_input_fields_for_model( field.related_model, data.get("only_fields", ()), data.get( "exclude_fields", (reverse_field_name, )), # Exclude the field referring back to the foreign key data.get("optional_fields", ()), data.get("required_fields", ()), data.get("many_to_many_extras"), data.get("foreign_key_extras"), data.get("many_to_one_extras"), parent_type_name=type_name, field_types=data.get("field_types"), ) InputType = type(type_name, (InputObjectType, ), converted_fields) registry.register_converted_field(type_name, InputType) meta_registry.register(type_name, data) # Create extra many_to_many_fields for name, extras in many_to_many_extras.items(): field: Union[models.ManyToManyField, models.ManyToManyRel] = fields_lookup.get(name) if field is None: raise GraphQLError( f"Error adding extras for {name} in model f{model}. Field {name} does not exist." ) for extra_name, data in extras.items(): # This is handled above if extra_name == "exact": continue if isinstance(data, bool): data = {} _type_name = data.get("type") _field = convert_many_to_many_field(field, registry, False, data, None) # Default to the same as the "exact" version if not _field: _field = fields[name] # operation = data.get('operation') or get_likely_operation_from_name(extra_name) fields[name + "_" + extra_name] = _field for name, extras in many_to_one_extras.items(): field: models.ManyToOneRel = fields_lookup.get(name) if field is None: raise GraphQLError( f"Error adding extras for {name} in model f{model}. Field {name} does not exist." ) for extra_name, data in extras.items(): argument_name = data.get("name", name + "_" + extra_name) # Override default if extra_name == "exact": argument_name = name if isinstance(data, bool): data = {"type": "ID"} type_name = data.get("type") if type_name and type_name != "ID": # Create new type. operation_name = data.get( "operation", get_likely_operation_from_name(extra_name)) converted_fields = get_input_fields_for_model( field.related_model, data.get("only_fields", ()), data.get( "exclude_fields", (field.field.name, ) ), # Exclude the field referring back to the foreign key data.get("optional_fields", ()), data.get("required_fields", ()), data.get("many_to_many_extras"), data.get("foreign_key_extras"), data.get("many_to_one_extras"), data.get("one_to_one_extras"), parent_type_name=type_name, field_types=data.get("field_types"), # Don't ignore the primary key on updates ignore_primary_key=operation_name != "update", ) InputType = type(type_name, (InputObjectType, ), converted_fields) registry.register_converted_field(field, InputType) meta_registry.register( type_name, { "auto_context_fields": data.get( "auto_context_fields", {}), "optional_fields": data.get("optional_fields", ()), "required_fields": data.get("required_fields", ()), "many_to_many_extras": data.get( "many_to_many_extras", {}), "many_to_one_extras": data.get("many_to_one_extras", {}), "foreign_key_extras": data.get("auto_context_fields", {}), "one_to_one_extras": data.get("one_to_one_extras", {}), "field_types": data.get("field_types", {}), }, ) registry.register_converted_field(type_name, InputType) _field = graphene.List( type(type_name, (InputObjectType, ), converted_fields), required=False, ) else: _field = convert_many_to_many_field(field, registry, False, data, None) fields[argument_name] = _field return fields