"""
Mix-ins for building your own query-sets.
"""
import functools
from django.db import transaction
from pytoolbox import module
_all = module.All(globals())
[docs]class AtomicGetUpdateOrCreateMixin(object):
savepoint = False
[docs] def get_or_create(self, defaults=None, **kwargs):
with transaction.atomic(savepoint=self.savepoint):
return super().get_or_create(defaults=defaults, **kwargs)
[docs] def update_or_create(self, defaults=None, **kwargs):
with transaction.atomic(savepoint=self.savepoint):
return super().update_or_create(defaults=defaults, **kwargs)
[docs]class AtomicGetRestoreOrCreateMixin(object):
savepoint = False
[docs] def get_restore_or_create(self, *args, **kwargs):
with transaction.atomic(savepoint=self.savepoint):
return super().get_restore_or_create(*args, **kwargs)
[docs]class CreateModelMethodMixin(object):
[docs] def create(self, *args, **kwargs):
if hasattr(self.model, 'create'):
return self.model.create(*args, **kwargs)
return super().create(*args, **kwargs)
create.alters_data = True
[docs]class StateMixin(object):
"""
Generate on the fly utility query-set filtering methods to a model using a
:class:`pytoolbox.states.StateEnum` to implement its own state machine. Then you can use
something like ``Model.objects.ready_or_canceled(inverse=True)`` to exclude models in state
READY or CANCELED.
This mixin requires the following to work:
* Add a `states` attribute to your model class set to the states class you defined earlier.
* Add a `state` field to the model for saving instance state in database.
"""
_skip_names = frozenset(['__getstate__', 'model'])
def __getattr__(self, name):
# avoid strange infinite recursion with defer()
if name not in self._skip_names:
all_states = set()
for name in name.split('_or_'):
states = self.model.states.get(name)
if not states:
raise AttributeError
method = all_states.add if isinstance(states, str) else all_states.update
method(states)
return functools.partial(self.in_states, all_states)
raise AttributeError
[docs] def in_states(self, states, inverse=False):
"""Filter query set to include instances in `states`."""
method = self.exclude if inverse else self.filter
return method(state__in={states} if isinstance(states, str) else states)
__all__ = _all.diff(globals())