def get_cache_name_enum_name(field): name = field.field_name or field.source or "Choices" serializer = field.parent cache_name = "{}_{}".format(serializer.__class__.__name__, name) if isinstance(serializer, serializers.ModelSerializer): _model_fields = dict(get_model_fields(serializer.Meta.model)) _model_field = _model_fields.get(name) enum_name = f'{serializer.Meta.model.__name__}{name.capitalize()}' return enum_name, _model_field or cache_name enum_name = f'{serializer.__class__.__name__}{name}' return enum_name, cache_name
def get_fields_and_properties(cls): """ Return all fields and @property methods for a model. """ from graphene_django.utils import get_model_fields # Note: graphene-django use this method to get the model fields # cls._meta.get_fields(include_parents=False) includes symmetrical ManyToMany fields, while get_model_fields doesn't fields = [field for field, instance in get_model_fields(cls)] try: properties = [ method[0] for method in inspect.getmembers( cls, lambda o: isinstance(o, property)) ] except BaseException: properties = [] return fields + properties
def __init_subclass_with_meta__(cls, _meta=None, model=None, optional_fields=None, type_name=None, **kwargs): all_field_names = tuple(name for name, _ in get_model_fields(model)) if optional_fields is None: optional_fields = all_field_names input_type_name = type_name or f"BatchPatch{model.__name__}Input" return super().__init_subclass_with_meta__( _meta=_meta, model=model, optional_fields=optional_fields, type_name=input_type_name, **kwargs)
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 __init_subclass_with_meta__( cls, _meta=None, model=None, permissions=None, login_required=None, filter_fields=(), filter_class=None, type_name=None, only_fields=(), exclude_fields=(), optional_fields=None, required_fields=(), field_types=None, auto_context_fields={}, **kwargs, ): registry = get_global_registry() model_type = registry.get_type_for_model(model) if optional_fields is None: optional_fields = tuple(name for name, _ in get_model_fields(model)) assert model_type, f"Model type must be registered for model {model}" assert ( len(filter_fields) > 0 ), f"You must specify at least one field to filter on for deletion." input_arguments = get_filter_fields_input_args(filter_fields, model) FilterInputType = type( f"FilterUpdate{model.__name__}FilterInput", (InputObjectType, ), input_arguments, ) input_type_name = type_name or f"FilterUpdate{model.__name__}DataInput" model_fields = get_input_fields_for_model( model, only_fields, exclude_fields, tuple(auto_context_fields.keys()) + optional_fields, required_fields, None, None, None, one_to_one_extras=None, parent_type_name=input_type_name, field_types=field_types, ignore_primary_key=True, ) DataInputType = type(input_type_name, (InputObjectType, ), model_fields) # Register meta-data meta_registry.register( input_type_name, { "auto_context_fields": auto_context_fields or {}, "optional_fields": optional_fields, "required_fields": required_fields, "many_to_many_extras": {}, "many_to_one_extras": {}, "foreign_key_extras": {}, "one_to_one_extras": {}, "field_types": field_types or {}, }, ) registry.register_converted_field(input_type_name, DataInputType) arguments = OrderedDict(filter=FilterInputType(required=True), data=DataInputType(required=True)) output_fields = OrderedDict() output_fields["updated_count"] = graphene.Int() output_fields["updated_objects"] = graphene.List(model_type) if _meta is None: _meta = DjangoFilterUpdateMutationOptions(cls) _meta.model = model _meta.fields = yank_fields_from_attrs(output_fields, _as=graphene.Field) _meta.filter_fields = filter_fields _meta.permissions = permissions _meta.login_required = login_required or (_meta.permissions and len(_meta.permissions) > 0) super().__init_subclass_with_meta__(arguments=arguments, _meta=_meta, **kwargs)
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
def __init_subclass_with_meta__( cls, _meta=None, model=None, permissions=None, login_required=None, only_fields=(), exclude_fields=(), optional_fields=(), required_fields=(), auto_context_fields=None, return_field_name=None, many_to_many_extras=None, foreign_key_extras=None, many_to_one_extras=None, one_to_one_extras=None, type_name=None, field_types=None, **kwargs, ): registry = get_global_registry() meta_registry = get_type_meta_registry() model_type = registry.get_type_for_model(model) assert model_type, f"Model type must be registered for model {model}" if auto_context_fields is None: auto_context_fields = {} if many_to_one_extras is None: many_to_one_extras = {} if foreign_key_extras is None: foreign_key_extras = {} if many_to_many_extras is None: many_to_many_extras = {} if one_to_one_extras is None: one_to_one_extras = {} if not return_field_name: return_field_name = to_snake_case(model.__name__) input_type_name = type_name or f"Patch{model.__name__}Input" all_field_names = (name for name, _ in get_model_fields(model)) model_fields = get_input_fields_for_model( model, only_fields, exclude_fields, optional_fields=all_field_names, required_fields=(), many_to_many_extras=many_to_many_extras, foreign_key_extras=foreign_key_extras, many_to_one_extras=many_to_one_extras, one_to_one_extras=one_to_one_extras, parent_type_name=input_type_name, field_types=field_types, ) InputType = type(input_type_name, (InputObjectType, ), model_fields) # Register meta-data meta_registry.register( input_type_name, { "auto_context_fields": auto_context_fields or {}, "many_to_many_extras": many_to_many_extras or {}, "many_to_one_extras": many_to_one_extras or {}, "foreign_key_extras": foreign_key_extras or {}, "one_to_one_extras": one_to_one_extras, "field_types": field_types or {}, }, ) registry.register_converted_field(input_type_name, InputType) arguments = OrderedDict(id=graphene.ID(required=True), input=InputType(required=True)) output_fields = OrderedDict() output_fields[return_field_name] = graphene.Field(model_type) if _meta is None: _meta = DjangoPatchMutationOptions(cls) _meta.model = model _meta.fields = yank_fields_from_attrs(output_fields, _as=graphene.Field) _meta.return_field_name = return_field_name _meta.permissions = permissions _meta.auto_context_fields = auto_context_fields _meta.many_to_many_extras = many_to_many_extras _meta.many_to_one_extras = many_to_one_extras _meta.foreign_key_extras = foreign_key_extras _meta.one_to_one_extras = one_to_one_extras _meta.field_types = field_types or {} _meta.InputType = InputType _meta.input_type_name = input_type_name _meta.login_required = login_required or (_meta.permissions and len(_meta.permissions) > 0) super().__init_subclass_with_meta__(arguments=arguments, _meta=_meta, **kwargs)
def __init__( self, api, fields=None, # Fields that can be read and potentially edited exclude=None, # Fields that will be excluded read_only=None, # Fields that are read-only write_only=None, # Fields that are write-only properties=None, # Dynamic views available as fields on a resource actions=None, # Extra actions for graphql and rest views filters=None, # Extra filters for graphql and rest views list_display=None, # The favorite fields to display in a table permissions=None, inlines=None, viewset_class=None, filterset_class=None, serializer_class=None, admin_class=None, dump_info=False, # Print the full resource to the console upon mounting ): # Part 1: Set attributes self.api = api self.permissions = permissions or self.permissions self.viewset_class = viewset_class or self.viewset_class # Step 2: Fields self.model_fields = collections.OrderedDict(get_model_fields(self.model)) self.model_fields_simple = collections.OrderedDict( (f.name, f) for f in self.model._meta.fields ) self.relations = self.get_model_fields( lambda field: isinstance(field, related.ForeignObject) ) self.reverse_relations = self.get_model_fields( lambda field: isinstance(field, reverse_related.ForeignObjectRel) ) self.many_to_many = self.get_model_fields( lambda field: isinstance(field, related.ManyToManyField) ) self.fields = list( f for f in fields or self.fields or ( list(self.model_fields.keys()) + self.relations + self.reverse_relations ) if f != "id" ) self.read_only = ["pk",] + ( read_only or self.read_only or [f for f in self.reverse_relations if f in self.fields] ) self.write_only = write_only or self.write_only or [] self.list_display = [ f for f in ["pk",] + list( list_display or self.list_display or [ f for f in self.fields if not f in self.reverse_relations and not f in self.many_to_many ] ) if f not in self.write_only ] # Part 3: Complex setups self.inlines = inlines or self.inlines self.filterset_class = ( filterset_class or self.filterset_class or EasyFilters.Assemble(self) ) self.serializer_class = ( serializer_class or self.serializer_class or EasySerializable.Assemble(self) ) self.admin_class = admin_class or self.admin_class or admin.ModelAdmin self.filterset = self.filterset_class() self.gql_fields = { field: get_gql_type(self.model_fields, field) for field in self.fields + self.inlines + ["id",] if self.api.graphql and not isinstance(self.model_fields[field], GenericForeignKey) and field not in self.write_only and field in self.model_fields } # need to rewrite this with dir() like below self.properties = ( properties or self.properties or [ attr for attr, value in self.model.__dict__.items() if getattr(value, "_APIProperty", False) ] ) self.property_map = { name: getattr(self.model, name)._APIType for name in self.properties if getattr(getattr(self.model, name, None), "_APIProperty", False) } self.actions = ( actions or self.actions or { attr: getattr(value, "_APIAction", {}) for attr, value in self.model_attributes.items() if getattr(value, "_APIAction", False) } ) self.filters = ( filters or self.filters or { attr: getattr(value, "_APIFilter", {}) for attr, value in self.model_attributes.items() if isinstance(value, django_filters.Filter) } )