def _make_query(self): table = Table(self.model._meta.table) self.query = self._db.query_class.update(table) self.resolve_filters( model=self.model, q_objects=self.q_objects, annotations=self.annotations, custom_filters=self.custom_filters, ) for key, value in self.update_kwargs.items(): field_object = self.model._meta.fields_map.get(key) if not field_object: raise FieldError( "Unknown keyword argument {} for model {}".format( key, self.model)) if field_object.generated: raise IntegrityError( "Field {} is generated and can not be updated") if isinstance(field_object, fields.ForeignKeyField): db_field = "{}_id".format(key) value = value.id else: db_field = self.model._meta.fields_db_projection[key] self.query = self.query.set(db_field, value)
def order_by(self, *orderings: str) -> "QuerySet[MODEL]": """ Accept args to filter by in format like this: .. code-block:: python3 .order_by('name', '-tournament__name') Supports ordering by related models too. :raises FieldError: If unknown field has been provided. """ queryset = self._clone() new_ordering = [] for ordering in orderings: field_name, order_type = self._resolve_ordering_string(ordering) if not (field_name.split("__")[0] in self.model._meta.fields or field_name in self._annotations): raise FieldError( f"Unknown field {field_name} for model {self.model.__name__}" ) new_ordering.append((field_name, order_type)) queryset._orderings = new_ordering return queryset
def add_field_to_select_query(self, field, return_as) -> None: table = Table(self.model._meta.table) if field in self.model._meta.fields_db_projection: db_field = self.model._meta.fields_db_projection[field] self.query = self.query.select( getattr(table, db_field).as_(return_as)) return if field in self.model._meta.fetch_fields: raise ValueError( 'Selecting relation "{}" is not possible, select concrete field on related model' .format(field)) field_split = field.split('__') if field_split[0] in self.model._meta.fetch_fields: related_table, related_db_field = self._join_table_with_forwarded_fields( model=self.model, field=field_split[0], forwarded_fields='__'.join(field_split[1:]), ) self.query = self.query.select( getattr(related_table, related_db_field).as_(return_as)) return raise FieldError('Unknown field "{}" for model "{}"'.format( field, self.model.__name__, ))
def _get_actual_filter_params(self, model, key, value) -> Tuple[str, Any]: if key in model._meta.fk_fields or key in model._meta.o2o_fields: field_object = model._meta.fields_map[key] if hasattr(value, "pk"): filter_value = value.pk else: filter_value = value filter_key = field_object.source_field elif key in model._meta.m2m_fields: filter_key = key if hasattr(value, "pk"): filter_value = value.pk else: filter_value = value elif (key.split("__")[0] in model._meta.fetch_fields or key in self._custom_filters or key in model._meta.filters): filter_key = key filter_value = value else: allowed = sorted( list(model._meta.fields | model._meta.fetch_fields | set(self._custom_filters))) raise FieldError( f"Unknown filter param '{key}'. Allowed base values are {allowed}" ) return filter_key, filter_value
def __init__(self, model, update_kwargs, db, q_objects, annotations, custom_filters) -> None: super().__init__(model, db) table = Table(model._meta.table) self.query = self._db.query_class.update(table) self.resolve_filters(model=model, q_objects=q_objects, annotations=annotations, custom_filters=custom_filters) for key, value in update_kwargs.items(): field_object = model._meta.fields_map.get(key) if not field_object: raise FieldError( 'Unknown keyword argument {} for model {}'.format( key, model)) if field_object.generated: raise IntegrityError( 'Field {} is generated and can not be updated') if isinstance(field_object, fields.ForeignKeyField): db_field = '{}_id'.format(key) value = value.id else: db_field = model._meta.fields_db_projection[key] self.query = self.query.set(db_field, value)
def filter(self, *args, **kwargs) -> 'QuerySet': """ Filters QuerySet by given kwargs. You can filter by related objects like this: .. code-block:: python3 Team.filter(events__tournament__name='Test') You can also pass Q objects to filters as args. """ queryset = self._clone() for arg in args: if not isinstance(arg, Q): raise TypeError('expected Q objects as args') queryset._q_objects_for_resolve.append(arg) for key, value in kwargs.items(): if key in queryset.model._meta.filters: queryset._filter_kwargs[key] = value elif key in self.model._meta.fk_fields: field_object = self.model._meta.fields_map[key] queryset._filter_kwargs[field_object.source_field] = value.id elif key.split('__')[0] in self.model._meta.fetch_fields: queryset._q_objects_for_resolve.append(Q(**{key: value})) elif key in self._available_custom_filters: queryset._having[key] = value else: allowed = sorted(list(self.model._meta.fields | self.model._meta.fetch_fields | set(self._available_custom_filters))) raise FieldError("Unknown filter param '{}'. Allowed base values are {}".format( key, allowed)) return queryset
def order_by(self, *orderings: str) -> "QuerySet[MODEL]": """ Accept args to filter by in format like this: .. code-block:: python3 .order_by('name', '-tournament__name') Supports ordering by related models too. """ queryset = self._clone() new_ordering = [] for ordering in orderings: order_type = Order.asc if ordering[0] == "-": field_name = ordering[1:] order_type = Order.desc else: field_name = ordering if not (field_name.split("__")[0] in self.model._meta.fields or field_name in self._annotations): raise FieldError( f"Unknown field {field_name} for model {self.model.__name__}" ) new_ordering.append((field_name, order_type)) queryset._orderings = new_ordering return queryset
def resolve_to_python_value(self, model: Type[MODEL], field: str) -> Callable: if field in model._meta.fetch_fields: # return as is to get whole model objects return lambda x: x if field in (x[1] for x in model._meta.db_native_fields): return lambda x: x if field in self.annotations: annotation = self.annotations[field] field_object = getattr(annotation, "field_object", None) if field_object: return field_object.to_python_value return lambda x: x if field in model._meta.fields_map: return model._meta.fields_map[field].to_python_value field_split = field.split("__") if field_split[0] in model._meta.fetch_fields: new_model = model._meta.fields_map[ field_split[0]].related_model # type: ignore return self.resolve_to_python_value(new_model, "__".join(field_split[1:])) raise FieldError(f'Unknown field "{field}" for model "{model}"')
def add_field_to_select_query(self, field, return_as) -> None: table = self.model._meta.basetable if field in self.model._meta.fields_db_projection: db_field = self.model._meta.fields_db_projection[field] self.query._select_field(getattr(table, db_field).as_(return_as)) return if field in self.model._meta.fetch_fields: raise ValueError('Selecting relation "{}" is not possible, select ' "concrete field on related model".format(field)) if field in self.annotations: annotation = self.annotations[field] annotation_info = annotation.resolve(self.model) self.query._select_other(annotation_info["field"].as_(return_as)) return field_split = field.split("__") if field_split[0] in self.model._meta.fetch_fields: related_table, related_db_field = self._join_table_with_forwarded_fields( model=self.model, field=field_split[0], forwarded_fields="__".join(field_split[1:])) self.query._select_field( getattr(related_table, related_db_field).as_(return_as)) return raise FieldError( f'Unknown field "{field}" for model "{self.model.__name__}"')
def resolve_to_python_value(self, model: "Type[Model]", field: str) -> Callable: if field in model._meta.fetch_fields: # return as is to get whole model objects return lambda x: x if field in [x[1] for x in model._meta.db_native_fields]: return lambda x: x if field in self.annotations: field_object = self.annotations[field].field_object if field_object: return field_object.to_python_value return lambda x: x if field in model._meta.fields_map: return model._meta.fields_map[field].to_python_value field_split = field.split("__") if field_split[0] in model._meta.fetch_fields: new_model = model._meta.fields_map[ field_split[0]].model_class # type: ignore return self.resolve_to_python_value(new_model, "__".join(field_split[1:])) raise FieldError(f'Unknown field "{field}" for model "{model}"')
def add_field_to_select_query(self, field: str, return_as: str) -> None: table = self.model._meta.basetable if field in self.model._meta.fields_db_projection: db_field = self.model._meta.fields_db_projection[field] self.query._select_field(table[db_field].as_(return_as)) return if field in self.model._meta.fetch_fields: raise ValueError('Selecting relation "{}" is not possible, select ' "concrete field on related model".format(field)) if field in self.annotations: self._annotations[return_as] = self.annotations[field] return field_split = field.split("__") if field_split[0] in self.model._meta.fetch_fields: related_table, related_db_field = self._join_table_with_forwarded_fields( model=self.model, table=table, field=field_split[0], forwarded_fields="__".join(field_split[1:]), ) self.query._select_field( related_table[related_db_field].as_(return_as)) return raise FieldError( f'Unknown field "{field}" for model "{self.model.__name__}"')
def prefetch_related(self, *args: str) -> 'QuerySet': """ Like ``.fetch_related()`` on instance, but works on all objects in QuerySet. """ queryset = self._clone() queryset._prefetch_map = {} for relation in args: if isinstance(relation, Prefetch): relation.resolve_for_queryset(queryset) continue relation_split = relation.split('__') first_level_field = relation_split[0] if first_level_field not in self.model._meta.fetch_fields: raise FieldError( 'relation {} for {} not found'.format( first_level_field, self.model._meta.table ) ) if first_level_field not in queryset._prefetch_map.keys(): queryset._prefetch_map[first_level_field] = set() forwarded_prefetch = '__'.join(relation_split[1:]) if forwarded_prefetch: queryset._prefetch_map[first_level_field].add(forwarded_prefetch) return queryset
def resolver_arithmetic_expression( cls, model: "Type[Model]", arithmetic_expression_or_field: Term, ) -> Tuple[Term, Optional[Field]]: field_object = None if isinstance(arithmetic_expression_or_field, Field): name = arithmetic_expression_or_field.name try: arithmetic_expression_or_field.name = model._meta.fields_db_projection[name] field_object = model._meta.fields_map.get(name, None) if field_object: func = field_object.get_for_dialect( model._meta.db.capabilities.dialect, "function_cast" ) if func: arithmetic_expression_or_field = func( field_object, arithmetic_expression_or_field ) except KeyError: raise FieldError(f"There is no non-virtual field {name} on Model {model.__name__}") elif isinstance(arithmetic_expression_or_field, ArithmeticExpression): left = arithmetic_expression_or_field.left right = arithmetic_expression_or_field.right ( arithmetic_expression_or_field.left, left_field_object, ) = cls.resolver_arithmetic_expression(model, left) if left_field_object: if field_object and type(field_object) != type(left_field_object): raise FieldError( "Cannot use arithmetic expression between different field type" ) field_object = left_field_object ( arithmetic_expression_or_field.right, right_field_object, ) = cls.resolver_arithmetic_expression(model, right) if right_field_object: if field_object and type(field_object) != type(right_field_object): raise FieldError( "Cannot use arithmetic expression between different field type" ) field_object = right_field_object return arithmetic_expression_or_field, field_object
def to_python_value( self, value: Optional[Union[str, dict, list]]) -> Optional[Union[dict, list]]: if isinstance(value, str): try: return self.decoder(value) except Exception: raise FieldError(f"Value {value} is invalid json value.") return value
def to_db_value(self, value: Optional[Union[dict, list, str]], instance: "Union[Type[Model], Model]") -> Optional[str]: if isinstance(value, str): try: self.decoder(value) except Exception: raise FieldError(f"Value {value} is invalid json value.") return value return None if value is None else self.encoder(value)
def values(self, *args: str, **kwargs: str) -> "ValuesQuery": """ Make QuerySet return dicts instead of objects. Can pass names of fields to fetch, or as a ``field_name='name_in_dict'`` kwarg. If no arguments are passed it will default to a dict containing all fields. :raises FieldError: If duplicate key has been provided. """ if args or kwargs: fields_for_select: Dict[str, str] = {} for field in args: if field in fields_for_select: raise FieldError(f"Duplicate key {field}") fields_for_select[field] = field for return_as, field in kwargs.items(): if return_as in fields_for_select: raise FieldError(f"Duplicate key {return_as}") fields_for_select[return_as] = field else: _fields = [ field for field in self.model._meta.fields_map.keys() if field in self.model._meta.db_fields ] + list(self._annotations.keys()) fields_for_select = {field: field for field in _fields} return ValuesQuery( db=self._db, model=self.model, q_objects=self._q_objects, fields_for_select=fields_for_select, distinct=self._distinct, limit=self._limit, offset=self._offset, orderings=self._orderings, annotations=self._annotations, custom_filters=self._custom_filters, group_bys=self._group_bys, )
def resolve_term(self, term: PyPikaTerm, queryset: "AwaitableStatement[MODEL]", accept_relation: bool) -> Tuple[Optional[Field], PyPikaTerm]: if isinstance(term, ArithmeticExpression): pypika_term = copy(term) field_left, pypika_term.left = self.resolve_term(term.left, queryset, accept_relation) field_right, pypika_term.right = self.resolve_term(term.right, queryset, accept_relation) field = field_left or field_right return field, pypika_term if isinstance(term, PyPikaFunction): # # There are two options, either resolve all function args, like below, # in this case either all the string params are expected to be references # to model fields, and hence something like `Coalesce("desc", "demo")` # will raise FieldError if `demo` is not a model field. Now a reasonable solution # might be to allow unresolvable strings as is, without raising exceptions, # but that also has other undesired implication. # # term_new_args = [] # field = None # # for arg in term.args: # term_field, term_arg = resolve_term(arg, queryset, context) # term_new_args.append(term_arg) # field = field or term_field # # term.args = term_new_args # return field, term # # Another solution is allow on the the first parameter of the function to be # a field reference as we do here: # pypika_term = copy(term) field = None if len(term.args) > 0: pypika_term.args = copy(term.args) field, pypika_term.args[0] = self.resolve_term(term.args[0], queryset, accept_relation) return field, pypika_term elif isinstance(term, Negative): pypika_term = copy(term) field, pypika_term.term = self.resolve_term(term.term, queryset, accept_relation) return field, pypika_term elif isinstance(term, ValueWrapper): if isinstance(term.value, str): return self.resolve_field_name(term.value, queryset, accept_relation) return None, term raise FieldError(f"Unresolvable term: {term}")
def _make_query(self) -> None: table = self.model._meta.basetable self.query = self._db.query_class.update(table) self.resolve_filters( model=self.model, q_objects=self.q_objects, annotations=self.annotations, custom_filters=self.custom_filters, ) # Need to get executor to get correct column_map executor = self._db.executor_class(model=self.model, db=self._db) for key, value in self.update_kwargs.items(): field_object = self.model._meta.fields_map.get(key) if not field_object: raise FieldError( f"Unknown keyword argument {key} for model {self.model}") if field_object.pk: raise IntegrityError( f"Field {key} is PK and can not be updated") if isinstance(field_object, (ForeignKeyFieldInstance, OneToOneFieldInstance)): fk_field: str = field_object.source_field # type: ignore db_field = self.model._meta.fields_map[fk_field].source_field value = executor.column_map[fk_field](getattr( value, field_object.to_field_instance.model_field_name), None) else: try: db_field = self.model._meta.fields_db_projection[key] except KeyError: raise FieldError( f"Field {key} is virtual and can not be updated") if isinstance(value, Term): value = F.resolver_arithmetic_expression( self.model, value)[0] elif isinstance(value, Function): value = value.resolve(self.model, table)["field"] else: value = executor.column_map[key](value, None) self.query = self.query.set(db_field, value)
def to_db_value( self, value: BaseGeometry, instance: Union[Type[Model], Model], ) -> str: if value is None: return value if not isinstance(value, BaseGeometry): raise FieldError("The value to be saved must be a Shapely geometry.") return shapely.wkb.dumps(value, hex=True, srid=self.srid)
def to_python_value( self, value: Optional[Union[str, bytes, dict, list]] ) -> Optional[Union[dict, list]]: if isinstance(value, (str, bytes)): try: return self.decoder(value) except Exception: raise FieldError( f"Value {value if isinstance(value,str) else value.decode()} is invalid json value." ) self.validate(value) return value
def resolve_ordering(self, model, table, orderings, annotations) -> None: for ordering in orderings: field_name = ordering[0] if field_name in model._meta.fetch_fields: raise FieldError( "Filtering by relation is not possible. Filter by nested field of related model" ) if field_name.split("__")[0] in model._meta.fetch_fields: related_field_name = field_name.split("__")[0] related_field = model._meta.fields_map[related_field_name] related_table = self._join_table_by_field( table, related_field_name, related_field) self.resolve_ordering( related_field.model_class, related_table, [("__".join(field_name.split("__")[1:]), ordering[1])], {}, ) elif field_name in annotations: annotation = annotations[field_name] annotation_info = annotation.resolve(self.model, table) self.query = self.query.orderby(annotation_info["field"], order=ordering[1]) else: field_object = self.model._meta.fields_map.get(field_name) if not field_object: raise FieldError( f"Unknown field {field_name} for model {self.model.__name__}" ) field_name = field_object.source_field or field_name field = getattr(table, field_name) func = field_object.get_for_dialect( model._meta.db.capabilities.dialect, "function_cast") if func: field = func(field_object, field) self.query = self.query.orderby(field, order=ordering[1])
def values(self, *args: str, **kwargs: str) -> ValuesQuery: """ Make QuerySet return dicts instead of objects. Can pass names of fields to fetch, or as a ``field_name='name_in_dict'`` kwarg. If no arguments are passed it will default to a dict containing all fields. """ if args or kwargs: fields_for_select: Dict[str, str] = {} for field in args: if field in fields_for_select: raise FieldError(f"Duplicate key {field}") fields_for_select[field] = field for return_as, field in kwargs.items(): if return_as in fields_for_select: raise FieldError(f"Duplicate key {return_as}") fields_for_select[return_as] = field else: fields_for_select = { field_name: field_name for field_name, field_object in self.model._meta.fields_map.items() if field_object.has_db_column } return ValuesQuery( db=self._db, model=self.model, q_objects=self.q_objects, fields_for_select=fields_for_select, distinct=self._distinct, limit=self._limit, offset=self._offset, orderings=self._orderings, annotations=self.annotations, )
def resolve_to_python_value(self, model, field): if field in model._meta.fetch_fields: # return as is to get whole model objects return lambda x: x if field in model._meta.fields_map: return model._meta.fields_map[field].to_python_value field_split = field.split("__") if field_split[0] in model._meta.fetch_fields: new_model = model._meta.fields_map[field_split[0]].type return self.resolve_to_python_value(new_model, "__".join(field_split[1:])) raise FieldError('Unknown field "{}" for model "{}"'.format(field, model))
def _get_actual_key(self, queryset: "AwaitableStatement[MODEL]", model: Type["Model"], key: str) -> str: if key in model._meta.fields_map: field = model._meta.fields_map[key] if isinstance(field, (ForeignKey, OneToOneField)): return field.id_field_name return key (field_name, sep, comparision) = key.partition(LOOKUP_SEP) if field_name == "pk": return f"{model._meta.pk_attr}{sep}{comparision}" if field_name in model._meta.fields_map or field_name in queryset.annotations: return key allowed = sorted(list(model._meta.fields_map.keys() | queryset.annotations.keys())) raise FieldError(f"Unknown filter param '{key}'. Allowed base values are {allowed}")
def get_actual_field_name(model, annotations, field_name: str): if field_name in model._meta.fields_map: field = model._meta.fields_map[field_name] if isinstance(field, (ForeignKey, OneToOneField)): return field.id_field_name return field_name if field_name == "pk": return model._meta.pk_attr if field_name in annotations: return field_name allowed = sorted( list(model._meta.fields_map.keys() | annotations.keys())) raise FieldError( f"Unknown field name '{field_name}'. Allowed base values are {allowed}" )
def to_python_value(self, value: Optional[Union[str, dict]]) -> Optional[Dict]: if isinstance(value, str): try: value = self.decoder(value) except Exception: raise FieldError(f"Value {value} is invalid json value.") if not value: return {k: Score(calculator_name=k) for k in _AVAILABLE} output_dict: Dict[str, Score] = {} for k in value.keys(): if isinstance(value[k], Score): output_dict[k] = value[k] else: if isinstance(value[k], str): value[k] = json.loads(value[k]) output_dict[k] = Score(**value[k]) return output_dict
def _get_actual_filter_params(self, model, key, value) -> Tuple[str, Any]: if key in model._meta.fk_fields: field_object = model._meta.fields_map[key] if hasattr(value, 'id'): filter_value = value.id else: filter_value = value filter_key = field_object.source_field elif (key.split('__')[0] in model._meta.fetch_fields or key in self._custom_filters or key in model._meta.filters): filter_key = key filter_value = value else: allowed = sorted( list(model._meta.fields | model._meta.fetch_fields | set(self._custom_filters))) raise FieldError( "Unknown filter param '{}'. Allowed base values are {}".format( key, allowed)) return filter_key, filter_value
def resolve_field_name( self, field_name, queryset: "AwaitableStatement[MODEL]", accept_relation: bool, check_annotations=True, expand_annotation=True) -> Tuple[Optional[Field], PyPikaField]: # # When expand_annotation is False, we need to make sure the annotation # will show up (will be expanded) in the final query, since we are just # referring to it here. # if check_annotations and field_name in queryset.annotations: if expand_annotation: return None, queryset.annotations[field_name].field else: return None, PyPikaField(field_name) model = self.top.model table = self.top.table if field_name == "pk": field_name = model._meta.pk_attr relation_field_name, _, field_sub = field_name.partition(LOOKUP_SEP) relation_field = model._meta.fields_map.get(relation_field_name) if not relation_field: raise UnknownFieldError(relation_field_name, model) if isinstance(relation_field, RelationField): if field_sub: join_data = self.join_table_by_field(table, relation_field) self.push(join_data.model, join_data.table) (field_object, pypika_field) = self.resolve_field_name( field_sub, queryset, accept_relation, check_annotations=False) self.pop() return field_object, pypika_field elif accept_relation: join_data = self.join_table_by_field(table, relation_field, full=False) if join_data: return join_data.field_object, join_data.pypika_field else: # this can happen only when relation_field is instance of ForeignKey or OneToOneField field_object = model._meta.fields_map[relation_field.id_field_name] pypika_field = table[field_object.db_column] return field_object, pypika_field else: raise FieldError("{} is a relation. Try a nested field of the related model".format(relation_field_name)) else: if field_sub: if isinstance(relation_field, JSONField): path = "{{{}}}".format(field_sub.replace(LOOKUP_SEP, ',')) return None, table[relation_field.db_column].get_path_json_value(path) raise NotARelationFieldError(relation_field_name, model) field_object = relation_field pypika_field = table[field_object.db_column] func = field_object.get_for_dialect("function_cast") if func: pypika_field = func(pypika_field) return field_object, pypika_field
def resolve_ordering( self, model: "Type[Model]", table: Table, orderings: Iterable[Tuple[str, str]], annotations: Dict[str, Any], ) -> None: """ Applies standard ordering to QuerySet. :param model: The Model this queryset is based on. :param table: ``pypika.Table`` to keep track of the virtual SQL table (to allow self referential joins) :param orderings: What columns/order to order by :param annotations: Annotations that may be ordered on :raises FieldError: If a field provided does not exist in model. """ # Do not apply default ordering for annotated queries to not mess them up if not orderings and self.model._meta.ordering and not annotations: orderings = self.model._meta.ordering for ordering in orderings: field_name = ordering[0] if field_name in model._meta.fetch_fields: raise FieldError( "Filtering by relation is not possible. Filter by nested field of related model" ) if field_name.split("__")[0] in model._meta.fetch_fields: related_field_name = field_name.split("__")[0] related_field = cast( RelationalField, model._meta.fields_map[related_field_name]) related_table = self._join_table_by_field( table, related_field_name, related_field) self.resolve_ordering( related_field.related_model, related_table, [("__".join(field_name.split("__")[1:]), ordering[1])], {}, ) elif field_name in annotations: annotation = annotations[field_name] if isinstance(annotation, Term): self.query = self.query.orderby(annotation, order=ordering[1]) else: annotation_info = annotation.resolve(self.model, table) self.query = self.query.orderby(annotation_info["field"], order=ordering[1]) else: field_object = model._meta.fields_map.get(field_name) if not field_object: raise FieldError( f"Unknown field {field_name} for model {model.__name__}" ) field_name = field_object.source_field or field_name field = table[field_name] func = field_object.get_for_dialect( model._meta.db.capabilities.dialect, "function_cast") if func: field = func(field_object, field) self.query = self.query.orderby(field, order=ordering[1])