def _escape_characters_in_clause(self) -> None: """ Escapes the special characters ["%", "_"] if needed. Adds `%` for `like` queries. :raises QueryDefinitionError: if contains or icontains is used with ormar model instance :return: escaped value and flag if escaping is needed :rtype: Tuple[Any, bool] """ self.has_escaped_character = False if self.operator in [ "contains", "icontains", "startswith", "istartswith", "endswith", "iendswith", ]: if isinstance(self.filter_value, ormar.Model): raise QueryDefinitionError( "You cannot use contains and icontains with instance of the Model" ) self.has_escaped_character = self.has_escaped_characters() if self.has_escaped_character: self._escape_chars() self._prefix_suffix_quote()
async def update(self, each: bool = False, **kwargs: Any) -> int: """ Updates the model table after applying the filters from kwargs. You have to either pass a filter to narrow down a query or explicitly pass each=True flag to affect whole table. :param each: flag if whole table should be affected if no filter is passed :type each: bool :param kwargs: fields names and proper value types :type kwargs: Any :return: number of updated rows :rtype: int """ # queryset proxy always have one filter for pk of parent model if not each and len(self.queryset.filter_clauses) == 1: raise QueryDefinitionError( "You cannot update without filtering the queryset first. " "If you want to update all rows use update(each=True, **kwargs)" ) through_kwargs = kwargs.pop(self.through_model_name, {}) children = await self.queryset.all() for child in children: await child.update(**kwargs) # type: ignore if self.type_ == ormar.RelationType.MULTIPLE and through_kwargs: await self.update_through_instance( child=child, # type: ignore **through_kwargs, ) return len(children)
async def delete(self, *args: Any, each: bool = False, **kwargs: Any) -> int: """ Deletes from the model table after applying the filters from kwargs. You have to either pass a filter to narrow down a query or explicitly pass each=True flag to affect whole table. :param each: flag if whole table should be affected if no filter is passed :type each: bool :param kwargs: fields names and proper value types :type kwargs: Any :return: number of deleted rows :rtype:int """ if kwargs or args: return await self.filter(*args, **kwargs).delete() if not each and not (self.filter_clauses or self.exclude_clauses): raise QueryDefinitionError( "You cannot delete without filtering the queryset first. " "If you want to delete all rows use delete(each=True)") expr = FilterQuery(filter_clauses=self.filter_clauses).apply( self.table.delete()) expr = FilterQuery(filter_clauses=self.exclude_clauses, exclude=True).apply(expr) return await self.database.execute(expr)
async def update(self, each: bool = False, **kwargs: Any) -> int: """ Updates the model table after applying the filters from kwargs. You have to either pass a filter to narrow down a query or explicitly pass each=True flag to affect whole table. :param each: flag if whole table should be affected if no filter is passed :type each: bool :param kwargs: fields names and proper value types :type kwargs: Any :return: number of updated rows :rtype: int """ if not each and not (self.filter_clauses or self.exclude_clauses): raise QueryDefinitionError( "You cannot update without filtering the queryset first. " "If you want to update all rows use update(each=True, **kwargs)" ) self_fields = self.model.extract_db_own_fields().union( self.model.extract_related_names()) updates = {k: v for k, v in kwargs.items() if k in self_fields} updates = self.model.validate_choices(updates) updates = self.model.translate_columns_to_aliases(updates) expr = FilterQuery(filter_clauses=self.filter_clauses).apply( self.table.update().values(**updates)) expr = FilterQuery(filter_clauses=self.exclude_clauses, exclude=True).apply(expr) return await self.database.execute(expr)
def _resolve_filter_groups( self, groups: Any) -> Tuple[List[FilterGroup], List[str]]: """ Resolves filter groups to populate FilterAction params in group tree. :param groups: tuple of FilterGroups :type groups: Any :return: list of resolver groups :rtype: Tuple[List[FilterGroup], List[str]] """ filter_groups = [] select_related = self._select_related if groups: for group in groups: if not isinstance(group, FilterGroup): raise QueryDefinitionError( "Only ormar.and_ and ormar.or_ " "can be passed as filter positional" " arguments," "other values need to be passed by" "keyword arguments") _, select_related = group.resolve( model_cls=self.model, select_related=self._select_related, filter_clauses=self.filter_clauses, ) filter_groups.append(group) return filter_groups, select_related
async def delete(self, each: bool = False, **kwargs: Any) -> int: if kwargs: return await self.filter(**kwargs).delete() if not each and not self.filter_clauses: raise QueryDefinitionError( "You cannot delete without filtering the queryset first. " "If you want to delete all rows use delete(each=True)") expr = FilterQuery(filter_clauses=self.filter_clauses).apply( self.table.delete()) return await self.database.execute(expr)
async def update(self, each: bool = False, **kwargs: Any) -> int: self_fields = self.model.extract_db_own_fields() updates = {k: v for k, v in kwargs.items() if k in self_fields} updates = self.model.translate_columns_to_aliases(updates) if not each and not self.filter_clauses: raise QueryDefinitionError( "You cannot update without filtering the queryset first. " "If you want to update all rows use update(each=True, **kwargs)" ) expr = FilterQuery(filter_clauses=self.filter_clauses).apply( self.table.update().values(**updates)) return await self.database.execute(expr)
async def _query_aggr_function(self, func_name: str, columns: List) -> Any: func = getattr(sqlalchemy.func, func_name) select_actions = [ SelectAction(select_str=column, model_cls=self.model) for column in columns ] if func_name in ["sum", "avg"]: if any(not x.is_numeric for x in select_actions): raise QueryDefinitionError( "You can use sum and svg only with" "numeric types of columns" ) select_columns = [x.apply_func(func, use_label=True) for x in select_actions] expr = self.build_select_expression().alias(f"subquery_for_{func_name}") expr = sqlalchemy.select(select_columns).select_from(expr) # print("\n", expr.compile(compile_kwargs={"literal_binds": True})) result = await self.database.fetch_one(expr) return dict(result) if len(result) > 1 else result[0] # type: ignore
async def bulk_update( # noqa: CCR001 self, objects: List["Model"], columns: List[str] = None) -> None: ready_objects = [] pk_name = self.model_meta.pkname if not columns: columns = list(self.model.extract_db_own_fields().union( self.model.extract_related_names())) if pk_name not in columns: columns.append(pk_name) columns = [self.model.get_column_alias(k) for k in columns] for objt in objects: new_kwargs = objt.dict() if pk_name not in new_kwargs or new_kwargs.get(pk_name) is None: raise QueryDefinitionError( "You cannot update unsaved objects. " f"{self.model.__name__} has to have {pk_name} filled.") new_kwargs = self.model.substitute_models_with_pks(new_kwargs) new_kwargs = self.model.translate_columns_to_aliases(new_kwargs) new_kwargs = { "new_" + k: v for k, v in new_kwargs.items() if k in columns } ready_objects.append(new_kwargs) pk_column = self.model_meta.table.c.get( self.model.get_column_alias(pk_name)) pk_column_name = self.model.get_column_alias(pk_name) table_columns = [c.name for c in self.model_meta.table.c] expr = self.table.update().where( pk_column == bindparam("new_" + pk_column_name)) expr = expr.values( **{ k: bindparam("new_" + k) for k in columns if k != pk_column_name and k in table_columns }) # databases bind params only where query is passed as string # otherwise it just passes all data to values and results in unconsumed columns expr = str(expr) await self.database.execute_many(expr, ready_objects) for objt in objects: objt.set_save_status(True)
def paginate(self, page: int, page_size: int = 20) -> "QuerySet[T]": """ You can paginate the result which is a combination of offset and limit clauses. Limit is set to page size and offset is set to (page-1) * page_size. :param page_size: numbers of items per page :type page_size: int :param page: page number :type page: int :return: QuerySet :rtype: QuerySet """ if page < 1 or page_size < 1: raise QueryDefinitionError("Page size and page have to be greater than 0.") limit_count = page_size query_offset = (page - 1) * page_size return self.rebuild_self(limit_count=limit_count, offset=query_offset,)