async def create_through_instance(self, child: "Model", **kwargs: Any) -> None: """ Crete a through model instance in the database for m2m relations. :param kwargs: dict of additional keyword arguments for through instance :type kwargs: Any :param child: child model instance :type child: Model """ model_cls = self.relation.through owner_column = self.related_field.default_target_field_name( ) # type: ignore child_column = self.related_field.default_source_field_name( ) # type: ignore rel_kwargs = {owner_column: self._owner.pk, child_column: child.pk} final_kwargs = {**rel_kwargs, **kwargs} if child.pk is None: raise ModelPersistenceError(f"You cannot save {child.get_name()} " f"model without primary key set! \n" f"Save the child model first.") expr = model_cls.Meta.table.insert() expr = expr.values(**final_kwargs) # print("\n", expr.compile(compile_kwargs={"literal_binds": True})) await model_cls.Meta.database.execute(expr)
def _extract_model_db_fields(self) -> Dict: """ Returns a dictionary with field names and values for fields that are stored in current model's table. That includes own non-relational fields ang foreign key fields. :return: dictionary of fields names and values. :rtype: Dict """ self_fields = self._extract_own_model_fields() self_fields = { k: v for k, v in self_fields.items() if self.get_column_alias(k) in self.Meta.table.columns } for field in self._extract_db_related_names(): relation_field = self.Meta.model_fields[field] target_pk_name = relation_field.to.Meta.pkname target_field = getattr(self, field) self_fields[field] = getattr(target_field, target_pk_name, None) if not relation_field.nullable and not self_fields[field]: raise ModelPersistenceError( f"You cannot save {relation_field.to.get_name()} " f"model without pk set!") return self_fields
def substitute_models_with_pks(cls, model_dict: Dict) -> Dict: # noqa CCR001 """ Receives dictionary of model that is about to be saved and changes all related models that are stored as foreign keys to their fk value. :param model_dict: dictionary of model that is about to be saved :type model_dict: Dict :return: dictionary of model that is about to be saved :rtype: Dict """ for field in cls.extract_related_names(): field_value = model_dict.get(field, None) if field_value is not None: target_field = cls.Meta.model_fields[field] target_pkname = target_field.to.Meta.pkname if isinstance(field_value, ormar.Model): pk_value = getattr(field_value, target_pkname) if not pk_value: raise ModelPersistenceError( f"You cannot save {field_value.get_name()} " f"model without pk set!") model_dict[field] = pk_value elif field_value: # nested dict if isinstance(field_value, list): model_dict[field] = [ target.get(target_pkname) for target in field_value ] else: model_dict[field] = field_value.get(target_pkname) else: model_dict.pop(field, None) return model_dict
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 ModelPersistenceError( "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)
async def update(self: T, _columns: List[str] = None, **kwargs: Any) -> T: """ Performs update of Model instance in the database. Fields can be updated before or you can pass them as kwargs. Sends pre_update and post_update signals. Sets model save status to True. :param _columns: list of columns to update, if None all are updated :type _columns: List :raises ModelPersistenceError: If the pk column is not set :param kwargs: list of fields to update as field=value pairs :type kwargs: Any :return: updated Model :rtype: Model """ if kwargs: self.update_from_dict(kwargs) if not self.pk: raise ModelPersistenceError( "You cannot update not saved model! Use save or upsert method." ) await self.signals.pre_update.send(sender=self.__class__, instance=self, passed_args=kwargs) self_fields = self._extract_model_db_fields() self_fields.pop(self.get_column_name_from_alias(self.Meta.pkname)) if _columns: self_fields = { k: v for k, v in self_fields.items() if k in _columns } self_fields = self.translate_columns_to_aliases(self_fields) expr = self.Meta.table.update().values(**self_fields) expr = expr.where(self.pk_column == getattr(self, self.Meta.pkname)) await self.Meta.database.execute(expr) self.set_save_status(True) await self.signals.post_update.send(sender=self.__class__, instance=self) return self
async def update(self: T, **kwargs: Any) -> T: if kwargs: new_values = {**self.dict(), **kwargs} self.from_dict(new_values) if not self.pk: raise ModelPersistenceError( "You cannot update not saved model! Use save or upsert method." ) self_fields = self._extract_model_db_fields() self_fields.pop(self.get_column_name_from_alias(self.Meta.pkname)) self_fields = self.translate_columns_to_aliases(self_fields) expr = self.Meta.table.update().values(**self_fields) expr = expr.where(self.pk_column == getattr(self, self.Meta.pkname)) await self.Meta.database.execute(expr) self.set_save_status(True) return self
async def create_through_instance(self, child: "T", **kwargs: Any) -> None: """ Crete a through model instance in the database for m2m relations. :param kwargs: dict of additional keyword arguments for through instance :type kwargs: Any :param child: child model instance :type child: Model """ model_cls = self.relation.through owner_column = self.related_field.default_target_field_name( ) # type: ignore child_column = self.related_field.default_source_field_name( ) # type: ignore rel_kwargs = {owner_column: self._owner.pk, child_column: child.pk} final_kwargs = {**rel_kwargs, **kwargs} if child.pk is None: raise ModelPersistenceError(f"You cannot save {child.get_name()} " f"model without primary key set! \n" f"Save the child model first.") await model_cls(**final_kwargs).save()
def substitute_models_with_pks(cls, model_dict: Dict) -> Dict: # noqa CCR001 for field in cls.extract_related_names(): field_value = model_dict.get(field, None) if field_value is not None: target_field = cls.Meta.model_fields[field] target_pkname = target_field.to.Meta.pkname if isinstance(field_value, ormar.Model): pk_value = getattr(field_value, target_pkname) if not pk_value: raise ModelPersistenceError( f"You cannot save {field_value.get_name()} " f"model without pk set!") model_dict[field] = pk_value elif field_value: # nested dict if isinstance(field_value, list): model_dict[field] = [ target.get(target_pkname) for target in field_value ] else: model_dict[field] = field_value.get(target_pkname) else: model_dict.pop(field, None) return model_dict
async def bulk_update( # noqa: CCR001 self, objects: List["Model"], columns: List[str] = None) -> None: """ Performs bulk update in one database session to speed up the process. Allows to update multiple instance at once. All `Models` passed need to have primary key column populated. You can also select which fields to update by passing `columns` list as a list of string names. Bulk operations do not send signals. :param objects: list of ormar models :type objects: List[Model] :param columns: list of columns to update :type columns: List[str] """ 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 ModelPersistenceError( "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)