Skip to content

service

django_spire.contrib.service

__all__ = ['BaseDjangoModelService', 'ServiceError'] module-attribute

ServiceError

BaseDjangoModelService

Bases: BaseDjangoModelConstructor[TypeDjangoModel_co], ABC, Generic[TypeDjangoModel_co]

Source code in django_spire/contrib/constructor/constructor.py
def __init__(self, obj: Any = None):
    self._obj_type_name: str = str(
        next(iter(self.__class__.__annotations__.values()))
    ).split('.')[-1]

    if obj is None:
        return

    self._obj_mro_type_names = [cls.__name__ for cls in obj.__class__.__mro__]

    if self._obj_type_name not in self._obj_mro_type_names:
        message = f'{self.__class__.__name__} was instantiated with obj type "{obj.__class__.__name__}" and failed as it was expecting "{self._obj_type_name}".'
        raise ConstructorError(message)

    self._obj_type: type[TypeAny] = obj.__class__

    if self._obj_type is None or self._obj_type is ...:
        message = f'{self.__class__.__name__} top class attribute must have an annotated type.'
        raise ConstructorError(message)

    self.obj: TypeAny = obj

    if ABC not in self.__class__.__bases__:
        if not self._obj_is_valid:
            message = f'{self._obj_type_name} failed to validate on {self.__class__.__name__}'
            raise ConstructorError(message)

    self.__post_init__()

save_model_obj

This is the core service method for saving a Django model object with field data provided via kwargs. It will update the object with the given kwargs and handle any upstream attribute changes that were applied directly to the model instance (i.e. it can also be called without any kwargs, similar to Model.save()).

Its purpose is to run extra operations related to the model instance that need to run each time the model is saved - it is meant to replace the need for overriding BaseModelForm.save() or Model.save().

It is designed to emulate django's BaseModelForm.save() method as close as possible: first, in _set_non_m2m_fields, it updates the fields on self.obj using logic similar to django.forms.models.construct_instance, then it calls self.obj.save(), then updates the m2m fields on the object instance using logic similar to django's BaseModelForm._save_m2m() method. In all cases, it treats the incoming field_data exactly the same as cleaned_data is treated in the django code that it is emulating, and therefore does not perform any validation on the data or the model instance as it is assumed that field_data has already been validated upstream.

Args: **field_data:

Returns: tuple[Model, bool]

Source code in django_spire/contrib/service/django_model_service.py
@transaction.atomic
def save_model_obj(self, **field_data: dict | None) -> tuple[Model, bool]:
    """
    This is the core service method for saving a Django model object with field data provided via kwargs.
    It will update the object with the given kwargs and handle any upstream attribute changes that were applied
    directly to the model instance (i.e. it can also be called without any kwargs, similar to `Model.save()`).

    Its purpose is to run extra operations related to the model instance that need to run each time
    the model is saved - it is meant to replace the need for overriding `BaseModelForm.save()` or `Model.save()`.

    It is designed to emulate django's `BaseModelForm.save()` method as close as possible:
    first, in `_set_non_m2m_fields`, it updates the fields on `self.obj` using logic similar to `django.forms.models.construct_instance`,
    then it calls self.obj.save(), then updates the m2m fields on the object instance
    using logic similar to django's `BaseModelForm._save_m2m()` method. In all cases,
    it treats the incoming `field_data` exactly the same as `cleaned_data` is treated
    in the django code that it is emulating, and therefore does not perform any validation 
    on the data or the model instance as it is assumed that field_data has already been validated upstream.

    Args:
        **field_data:

    Returns:
        tuple[Model, bool]

    """

    new_model_obj_was_created = True if self.model_obj_is_new else False

    self._set_non_m2m_fields(**field_data)
    self.obj.save()
    self._set_m2m_fields(**field_data)

    return self.obj, new_model_obj_was_created