def test_columnInfo(self): info = meta.model_info(Vehicle) col = info.primary_keys["id"] self.assertEqual(col.name, "id") self.assertDictEqual({"label": "Id", "help_text": "The primary key", "required": True}, col.field_kwargs) col = info.properties["type"] self.assertDictEqual( {"enum_class": VehicleType, "help_text": None, "label": "Type", "max_length": 3, "required": True}, col.field_kwargs, ) col = info.properties["paint"] self.assertDictEqual( {"choices": COLORS, "help_text": None, "label": "Paint", "max_length": 6, "required": False}, col.field_kwargs, ) self.assertEqual(col.parent_model, Vehicle) self.assertEqual(repr(col), "<column_info(Vehicle.paint)>") info = meta.model_info(AllKindsOfFields) col = info.properties["decimal"] self.assertDictEqual( {"decimal_places": None, "help_text": None, "label": "Decimal", "max_digits": None, "required": False}, col.field_kwargs, )
def test_app(self): info = meta.model_info(Owner) self.assertIs(info.app_config, apps.get_containing_app_config(Owner.__module__)) info = meta.model_info(OtherAppInOtherApp) self.assertIs(info.app_config, apps.get_containing_app_config(Owner.__module__))
def test_primary_keys_from_dict(self): info = meta.model_info(Owner) key = info.primary_keys_from_dict({"id": 1}) self.assertEqual(key, 1) info = meta.model_info(CompositePkModel) key = info.primary_keys_from_dict({"id": 1, "pk": 2}) self.assertEqual(key, (1, 2))
def test_model_meta(self): info = meta.model_info(Business) info_from_meta = meta.model_info(Business.__mapper__) info_from_instance = meta.model_info(Business()) self.assertIs(info, info_from_meta) self.assertIs(info_from_meta, info_from_instance)
def test_reprs(self): owner_info = meta.model_info(Owner) self.assertEqual( repr(owner_info), "\n".join( [ "<model_info(Owner)>", " <integer_column_info(Owner.id) pk>", " <string_column_info(Owner.first_name)>", " <string_column_info(Owner.last_name)>", " <relation_info(Owner.vehicles)>", ] ), ) vehicle_info = meta.model_info(Vehicle) self.assertEqual( repr(vehicle_info), "\n".join( [ "<model_info(Vehicle)>", " <integer_column_info(Vehicle.id) pk>", " <integer_column_info(Vehicle._owner_id)>", " <datetime_column_info(Vehicle.created_at)>", " <boolean_column_info(Vehicle.is_used)>", " <numeric_column_info(Vehicle.msrp)>", " <string_column_info(Vehicle.name)>", " <choice_column_info(Vehicle.paint)>", " <enum_column_info(Vehicle.type)>", " <relation_info(Vehicle.options)>", " <relation_info(Vehicle.owner)>", " <relation_info(Vehicle.parts)>", ] ), ) business_info = meta.model_info(Business) self.assertEqual( repr(business_info), "\n".join( [ "<model_info(Business)>", " <integer_column_info(Business.id) pk>", " <integer_column_info(Business.employees)>", " <string_column_info(Business.name)>", " <composite_info(Address, Business.location)>", " <enum_column_info(Address.state)>", " <string_column_info(Address.street)>", " <string_column_info(Address.zip)>", " <composite_info(Address, Business.other_location)>", " <enum_column_info(Address.state)>", " <string_column_info(Address.street)>", " <string_column_info(Address.zip)>", ] ), )
def test_primary_keys_from_instance(self): info = meta.model_info(Owner) instance = Owner(id=1) self.assertIsNone(info.primary_keys_from_instance(None)) key = info.primary_keys_from_instance(instance) self.assertEqual(key, 1) info = meta.model_info(CompositePkModel) instance = CompositePkModel(id=1, pk=2) key = info.primary_keys_from_instance(instance) self.assertDictEqual(key, {"id": 1, "pk": 2})
def get_object(self): """Returns the object the view is displaying. We ignore the `lookup_field` and `lookup_url_kwarg` values only when tere are multiple primary keys """ queryset = self.get_queryset() model = self.get_model() info = model_info(model) kwargs = self.kwargs.copy() # we want to honor DRF lookup_field and lookup_url_kwarg API # but only if they are defined and there is single primary key. # When there are multiple, all bets are off so we restrict url kwargs # to model column names if len(info.primary_keys) == 1: lookup_field = self.lookup_field lookup_url_kwarg = self.lookup_url_kwarg or lookup_field kwargs[lookup_field] = kwargs.pop(lookup_url_kwarg) obj = queryset.get(info.primary_keys_from_dict(kwargs)) if not obj: raise Http404("No %s matches the given query." % model.__name__) return obj
def build_nested_field(self, field_name, relation_info, nested_depth): """Builds nested serializer to handle relationshipped model.""" target_model = relation_info.related_model nested_fields = self.get_nested_relationship_fields(relation_info, nested_depth) field_kwargs = self.get_relationship_kwargs(relation_info, nested_depth) field_kwargs = self.include_extra_kwargs(field_kwargs, self._extra_kwargs.get(field_name)) nested_extra_kwargs = {} nested_info = meta.model_info(target_model) if not field_kwargs.get("required", True): for nested_field in nested_info.primary_keys: nested_extra_kwargs.setdefault(nested_field, {}).setdefault("required", False) if not field_kwargs.get("allow_nested_updates", True): nested_depth = 0 for nested_field in nested_info.properties: nested_extra_kwargs.setdefault(nested_field, {}).setdefault("read_only", True) nested_extra_kwargs.setdefault(nested_field, {}).pop("required", None) class NestedSerializer(getattr(self.Meta, "nested_serializer_class", ModelSerializer)): class Meta: model = target_model session = self.session depth = max(0, nested_depth - 1) fields = nested_fields extra_kwargs = nested_extra_kwargs return type(str(target_model.__name__ + "Serializer"), (NestedSerializer,), {})(**field_kwargs)
def get_fields(self): """Return the dict of field names -> field instances that should be used for `self.fields` when instantiating the serializer.""" if self.url_field_name is None: self.url_field_name = api_settings.URL_FIELD_NAME assert hasattr(self, "Meta"), 'Class {serializer_class} missing "Meta" attribute'.format( serializer_class=self.__class__.__name__ ) declared_fields = copy.deepcopy(self._declared_fields) info = meta.model_info(self.model) depth = getattr(self.Meta, "depth", 0) if depth is not None: assert depth >= 0, "'depth' may not be negative." assert depth <= 5, "'depth' may not be greater than 5." field_names = self.get_field_names(declared_fields, info) # Determine the fields that should be included on the serializer. _fields = OrderedDict() for field_name in field_names: # If the field is explicitly declared on the class then use that. if field_name in declared_fields: _fields[field_name] = declared_fields[field_name] continue source = self._extra_kwargs.get(field_name, {}).get("source") or field_name _fields[field_name] = self.build_field(source, info, self.model, depth) return _fields
def test_column_properties(self): info = meta.model_info(Owner) self.assertListEqual( list(info.column_properties), [("id", info.id), ("first_name", info.first_name), ("last_name", info.last_name)], )
def to_internal_value(self, data): """Same as in DRF but also handle ``partial_by_pk`` by making all non- pk fields optional. Even though flag name implies it will make serializer partial, that is currently not possible in DRF as partial flag is checked on root serializer within serializer validation loops. As such, individual serializers cannot be marked partial. Therefore when flag is provided and primary key is provided in validated data, we physically mark all other fields as not required to effectively make them partial without using ``partial`` flag itself. To make serializer behave more or less like real partial serializer, only passed keys in input data are preserved in validated data. If they are not stripped, it is possible to remove some existing data. """ if not self.partial_by_pk or not self.get_primary_keys(data): return super().to_internal_value(data) info = meta.model_info(self.model) for _, field in self.fields.items(): if field.source not in info.primary_keys: field.required = False passed_keys = set(data) data = super().to_internal_value(data) for k in set(data) - passed_keys: if k in self.fields and self.fields[k].get_default() == data[k]: data.pop(k) return data
def get_lookup_regex(self, viewset, lookup_prefix=""): """ Given a viewset, return the portion of the url regex that is used to match against a single instance. Can be overwritten by providing a `lookup_url_regex` on the viewset. """ lookup_url_regex = getattr(viewset, "lookup_url_regex", None) if lookup_url_regex: return lookup_url_regex model = getattr(viewset, "get_model", lambda: None)() if model: info = meta.model_info(model) base_regex = "(?P<{lookup_prefix}{lookup_url_kwarg}>{lookup_value})" lookup_keys = [getattr(viewset, "lookup_url_kwarg", None) or getattr(viewset, "lookup_field", None)] if not lookup_keys[0] or len(info.primary_keys) > 1: lookup_keys = list(info.primary_keys) regexes = [] for key in lookup_keys: regexes.append( base_regex.format(lookup_prefix=lookup_prefix, lookup_url_kwarg=key, lookup_value="[^/.]+") ) return "/".join(regexes) return super().get_lookup_regex(viewset, lookup_prefix)
def test_get_key(self): info = meta.model_info(Owner) owner = Owner(id=1) key = info.get_key(owner) self.assertEqual(key, (1, )) self.assertIsNone(info.get_key(Owner()))
def test_identity_key_from_instance(self): info = meta.model_info(Owner) owner = Owner(id=1) key = info.identity_key_from_instance(owner) self.assertEqual(key, meta.Identity(Owner, (1, ))) self.assertIsNone(info.identity_key_from_instance(Owner()))
def expand_queryset(self, queryset, values): to_expand = [] for value in values: to_load = [] components = value.split(LOOKUP_SEP) model = queryset._only_entity_zero().class_ for c in components: props = meta.model_info(model).relationships try: field = getattr(model, c) model = props[ c].relationship._dependency_processor.mapper.class_ except (KeyError, AttributeError): to_load = [] break else: to_load.append(field) if to_load: to_expand.append(to_load) if to_expand: queryset = queryset.options(*[ six.moves.reduce(lambda a, b: a.joinedload(b), expand, orm) for expand in to_expand ]) return queryset
def test_get_field(self): info = meta.model_info(Owner) self.assertIs(info.get_field("first_name"), info.properties.get("first_name")) with self.assertRaises(FieldDoesNotExist): info.get_field("zzzzzz")
def test_factory_with_relation(self): info = meta.model_info(Owner) formset_class = inlineformset_factory(relation=Owner.vehicles, fields=ALL_FIELDS, session=db) self.assertEqual(formset_class.fk, info.relationships["vehicles"])
def expand_queryset(self, queryset, values): to_expand = [] for value in values: to_load = [] components = value.split(LOOKUP_SEP) model = queryset._only_entity_zero().class_ for c in components: props = meta.model_info(model).relationships try: field = getattr(model, c) prop = props[c] model = prop.relationship._dependency_processor.mapper.class_ except (KeyError, AttributeError): to_load = [] break else: to_load.append(ToLoadField(field, prop.direction)) if to_load: to_expand.append(to_load) if to_expand: queryset = queryset.options(*[ six.moves.reduce( lambda a, b: (a.selectinload(b.field) if b.direction in { orm.interfaces.ONETOMANY, orm.interfaces.MANYTOMANY } else a.joinedload(b.field)), expand, orm, ) for expand in to_expand ]) return queryset
def test_composite_meta(self): info = meta.model_info(Vertex) self.assertEqual(set(info.composites.keys()), {"start", "end"}) self.assertListEqual( repr(info).split("\n"), [ "<model_info(Vertex)>", " <integer_column_info(Vertex.pk) pk>", " <composite_info(Point, Vertex.end)>", " <integer_column_info(Point.x)>", " <integer_column_info(Point.y)>", " <composite_info(Point, Vertex.start)>", " <integer_column_info(Point.x)>", " <integer_column_info(Point.y)>", ], ) start = info.composites["start"] self.assertEqual(set(start.properties.keys()), {"x", "y"}) self.assertEqual(start.properties["x"].property, Vertex.x1.property) self.assertEqual(start.properties["y"].property, Vertex.y1.property) self.assertIs(start.attribute, Vertex.start) end = info.composites["end"] self.assertEqual(set(end.properties.keys()), {"x", "y"}) self.assertEqual(end.properties["x"].property, Vertex.x2.property) self.assertEqual(end.properties["y"].property, Vertex.y2.property) self.assertIs(end.attribute, Vertex.end) self.assertEqual(set(start.field_names), {"x", "y"}) self.assertEqual(start.model_class, Point)
def get_primary_keys(self, validated_data): """Returns the primary key values from validated_data.""" if not validated_data: return info = meta.model_info(self.queryset._only_entity_zero().mapper) return info.primary_keys_from_dict( {getattr(self.fields.get(k), "source", None) or k: v for k, v in validated_data.items()} )
def test_composite_meta(self): info = meta.model_info(Vertex) self.assertEqual(set(info.composites.keys()), {"start", "end"}) start = info.composites["start"] self.assertEqual(set(start.properties.keys()), {"x", "y"}) self.assertEqual(start.properties["x"].property, Vertex.x1.property) self.assertEqual(set(start.field_names), {"x", "y"})
def test_plain_sqla(self): info = meta.model_info(AllKindsOfFields) col = info.decimal self.assertDictEqual( { "help_text": None, "label": "Decimal", "required": False, "validators": [] }, col.field_kwargs)
def __init__(self, *args, **kwargs): composite_attr = kwargs.pop("composite", None) or getattr(getattr(self, "Meta", None), "composite", None) self._info = meta.model_info(composite_attr.prop.parent).composites[composite_attr.prop.key] super().__init__(*args, **kwargs) self.composite_class = self._info.prop.composite_class self.read_only = False self.required = False self.default = None self.allow_nested_updates = True self._extra_kwargs = {}
def test_formfield(self): info = meta.model_info(Vehicle) with self.assertRaises(ImproperlyConfigured): info.owner.formfield() formfield = info.owner.formfield(session=Owner.query.session) self.assertIsInstance(formfield, fields.ModelChoiceField) formfield = info.parts.formfield(session=Part.query.session) self.assertIsInstance(formfield, fields.ModelMultipleChoiceField)
def test_relationship_meta(self): info = meta.model_info(Owner) rel = info.relationships["vehicles"] self.assertEqual(rel.related_model, Vehicle) self.assertEqual(rel.name, "vehicles") self.assertEqual(rel.direction, sa.orm.relationships.ONETOMANY) self.assertEqual(list(rel.foreign_keys), Vehicle._owner_id.property.columns) self.assertTrue(rel.uselist) self.assertEqual(repr(rel), "<relation_info(Owner.vehicles)>")
def test_get_relation_objects_for_validation(self): instance = Vehicle() info = meta.model_info(instance) self.assertDictEqual( instance._get_relation_objects_for_validation(), { "options": info.options, "owner": info.owner, "parts": info.parts }, )
def get_url(self, obj, view_name, request, format): info = meta.model_info(obj.__class__) # Unsaved objects will not yet have a valid URL. if not all(getattr(obj, i) for i in info.primary_keys): return None if len(info.primary_keys) == 1: kwargs = {self.lookup_url_kwarg: getattr(obj, self.lookup_field)} else: kwargs = {k: getattr(obj, k) for k in info.primary_keys} return self.reverse(view_name, kwargs=kwargs, request=request, format=format)
def get_url_kwargs(model): """Gets kwargs for the UriField.""" info = meta.model_info(model) lookup_field = list(info.primary_keys.keys())[0] field_kwargs = { "read_only": True, "view_name": get_detail_view_name(model), "lookup_field": lookup_field, "lookup_url_kwarg": "pk", } return field_kwargs
def test_column_info_enum(self): info = meta.model_info(Vehicle) col = info.type self.assertFalse(col.null) self.assertDictEqual( { "choices": VehicleType, "help_text": None, "label": "Type", "required": True, "validators": [] }, col.field_kwargs, ) self.assertTrue(col.required)
def test_choice_enum(self): info = meta.model_info(Vehicle) col = info.paint self.assertDictEqual( { "choices": [(x, x) for x in COLORS], "help_text": None, "label": "Paint", "required": False, "validators": [], }, col.field_kwargs, ) self.assertEqual(col.parent_model, Vehicle) self.assertEqual(repr(col), "<choice_column_info(Vehicle.paint)>")