Usage
=====

``django-bulk-tracker`` will emit a signal whenever you update, create, or delete a record in the database

``django-bulk-tracker`` supports bulk operations:

- ``queryset.update()``
- ``queryset.bulk_update()``
- ``queryset.bulk_create()``
- ``queryset.delete()``

and single operations:

- ``create()``
- ``save() # update and create``
- ``delete()``

all you need to do is to define your Model and inherit from::

    from bulk_tracker.models import BulkTrackerModel

    class Author(BulkTrackerModel):
    first_name = models.CharField(max_length=200)
    last_name = models.CharField(max_length=200)

OR if you have a custom queryset inherit from or Don't want to support single-operation::

    from bulk_tracker.managers import BulkTrackerQuerySet

    class MyModelQuerySet(BulkTrackerQuerySet):
        def do_something_custom(self):
            pass


now you can listen to the signals ``post_update_signal``, ``post_create_signal``, ``post_delete_signal``::

    @receiver(post_update_signal, sender=MyModel)
    def i_am_a_receiver_function(
        sender,
        objects: list[ModifiedObject[MyModel]],
        tracking_info_: TrackingInfo | None = None,
        **kwargs,
    ):
        do_stuff()

.. hint:: 
    all signals have the same signature for consistency,
    and also in case you want to assign one function to listen to multiple signals


``ModifiedObject`` is a very simple object, it contains 2 attributes: 
1- ``instance`` this is your model instance after it has been updated, or created
2- ``changed_values`` is dict[str, Any] which contains the changed fields only in case of ``post_update_signal``,
in case of ``post_create_signal`` and ``post_delete_signal``, ``changed_values`` will be an empty dict ``{}``


**Optionally** you can pass ``tracking_info_`` to your functions, as in::

    from bulk_tracker.helper_objects import TrackingInfo
    
    def a_function_that_updates_records():
        user = self.request.user
        MyModel.objects.filter(name='john').update(
            name='jack',
            tracking_info_=TrackingInfo(user=user, comment="Updated from a function", kwargs={'app-build':1.1.8}),
            )

.. hint::
     ``tracking_info_`` has a trailing underscore to avoid collision with your actual fields

you can use ``TrackingInfo`` to implement any kind of behavior like logging in your 
signal handlers and you need to capture more info about the operation that is happening.



For single operations as well
-----------------------------

- ``create()``
- ``save() # update and create``
- ``delete()``

to support we rely on the amazing library ``django-model-utils`` to track the model instances

1. do the above
2. you need to to inherit your model from ``BulkTrackerModel``
3. add ``tracker = FieldTracker()`` to your model

as in ::

    from bulk_tracker.models import BulkTrackerModel
    from model_utils import FieldTracker


    class MyModel(BulkTrackerModel):

        objects = MyModelQuerySet.as_manager() # MyModelManager() if you have 
        tracker = FieldTracker()



Complete Example
================

::

    # models.py
    from bulk_tracker.models import BulkTrackerModel
    from model_utils import FieldTracker

    from myapp.managers import MyModelManager


    class MyModel(BulkTrackerModel):
        first_field = models.CharField()
        second_field = models.CharField()

        objects = MyModelManager()
        tracker = FieldTracker()

::

    # managers.py
    from bulk_tracker.managers import BulkTrackerQuerySet # optional


    class MyModelQuerySet(BulkTrackerQuerySet):
        pass


    class MyModelManager(BulkTrackerManager.from_queryset(MyModelQuerySet)): # optional
        pass

::

    # signal_handlers.py
    from bulk_tracker.signals import post_update_signal
    from bulk_tracker.helper_objects import ModifiedObject, TrackingInfo


    @receiver(post_update_signal, sender=MyModel)
    def i_am_a_receiver_function(
        sender,
        objects: list[ModifiedObject[MyModel]],
        tracking_info_: TrackingInfo | None = None,
        **kwargs,
    ):
        user = tracking_info_.user if tracking_info_ else None
        for modified_objects in modified_objects:
            if 'name' in modified_object.changed_values:
                log(f"field 'name' has changed by {user or ''}")
                notify_user()
