def bulk_upsert2(
    queryset, model_objs, unique_fields, update_fields=None, returning=False,
    ignore_duplicate_updates=True, return_untouched=False
):
    """
    Performs a bulk update or insert on a list of model objects. Matches all objects in the queryset
    with the objs provided using the field values in unique_fields.
    If an existing object is matched, it is updated with the values from the provided objects. Objects
    that don't match anything are bulk created.
    A user can provide a list update_fields so that any changed values on those fields will be updated.
    However, if update_fields is not provided, this function reduces down to performing a bulk_create
    on any non extant objects.

    Args:
        queryset (Model|QuerySet): A model or a queryset that defines the collection to sync
        model_objs (List[Model]): A list of Django models to sync. All models in this list
            will be bulk upserted and any models not in the table (or queryset) will be deleted
            if sync=True.
        unique_fields (List[str]): A list of fields that define the uniqueness of the model. The
            model must have a unique constraint on these fields
        update_fields (List[str], default=None): A list of fields to update whenever objects
            already exist. If an empty list is provided, it is equivalent to doing a bulk
            insert on the objects that don't exist. If ``None``, all fields will be updated.
        returning (bool|List[str]): If ``True``, returns all fields. If a list, only returns
            fields in the list. Return values are split in a tuple of created and updated models
        ignore_duplicate_updates (bool, default=False): Ignore updating a row in the upsert if all of the update fields
            are duplicates
        return_untouched (bool, default=False): Return values that were not touched by the upsert operation

    Returns:
        UpsertResult: A list of results if ``returning`` is not ``False``. created, updated, and untouched,
            results can be obtained by accessing the ``created``, ``updated``, and ``untouched`` properties
            of the result.

    Examples:

    .. code-block:: python

        # Start off with no objects in the database. Call a bulk_upsert on the TestModel, which includes
        # a char_field, int_field, and float_field
        bulk_upsert2(TestModel.objects.all(), [
            TestModel(float_field=1.0, char_field='1', int_field=1),
            TestModel(float_field=2.0, char_field='2', int_field=2),
            TestModel(float_field=3.0, char_field='3', int_field=3),
        ], ['int_field'], ['char_field'])

        # All objects should have been created
        print(TestModel.objects.count())
        3

        # Now perform a bulk upsert on all the char_field values. Since the objects existed previously
        # (known by the int_field uniqueness constraint), the char fields should be updated
        bulk_upsert2(TestModel.objects.all(), [
            TestModel(float_field=1.0, char_field='0', int_field=1),
            TestModel(float_field=2.0, char_field='0', int_field=2),
            TestModel(float_field=3.0, char_field='0', int_field=3),
        ], ['int_field'], ['char_field'])

        # No more new objects should have been created, and every char field should be 0
        print(TestModel.objects.count(), TestModel.objects.filter(char_field='-1').count())
        3, 3

        # Do the exact same operation, but this time add an additional object that is not already
        # stored. It will be created.
        bulk_upsert2(TestModel.objects.all(), [
            TestModel(float_field=1.0, char_field='1', int_field=1),
            TestModel(float_field=2.0, char_field='2', int_field=2),
            TestModel(float_field=3.0, char_field='3', int_field=3),
            TestModel(float_field=4.0, char_field='4', int_field=4),
        ], ['int_field'], ['char_field'])

        # There should be one more object
        print(TestModel.objects.count())
        4

        # Note that one can also do the upsert on a queryset. Perform the same data upsert on a
        # filter for int_field=1. In this case, only one object has the ability to be updated.
        # All of the other objects will be created
        bulk_upsert2(TestModel.objects.filter(int_field=1), [
            TestModel(float_field=1.0, char_field='1', int_field=1),
            TestModel(float_field=2.0, char_field='2', int_field=2),
            TestModel(float_field=3.0, char_field='3', int_field=3),
            TestModel(float_field=4.0, char_field='4', int_field=4),
        ], ['int_field'], ['char_field'])

        # There should be three more objects
        print(TestModel.objects.count())
        7

        # Return creates and updates on the same set of models
        created, updated = bulk_upsert2(TestModel.objects.filter(int_field=1), [
            TestModel(float_field=1.0, char_field='1', int_field=1),
            TestModel(float_field=2.0, char_field='2', int_field=2),
            TestModel(float_field=3.0, char_field='3', int_field=3),
            TestModel(float_field=4.0, char_field='4', int_field=4),
        ], ['int_field'], ['char_field'])

        # All four objects should be updated
        print(len(updated))
        4
    """
    results = upsert2.upsert(queryset, model_objs, unique_fields,
                             update_fields=update_fields, returning=returning,
                             ignore_duplicate_updates=ignore_duplicate_updates,
                             return_untouched=return_untouched)
    post_bulk_operation.send(sender=queryset.model, model=queryset.model)
    return results
Performs a bulk update or insert on a list of model objects. Matches all objects in the queryset
    with the objs provided using the field values in unique_fields.
    If an existing object is matched, it is updated with the values from the provided objects. Objects
    that don't match anything are bulk created.
    A user can provide a list update_fields so that any changed values on those fields will be updated.
    However, if update_fields is not provided, this function reduces down to performing a bulk_create
    on any non extant objects.

    Args:
        queryset (Model|QuerySet): A model or a queryset that defines the collection to sync
        model_objs (List[Model]): A list of Django models to sync. All models in this list
            will be bulk upserted and any models not in the table (or queryset) will be deleted
            if sync=True.
        unique_fields (List[str]): A list of fields that define the uniqueness of the model. The
            model must have a unique constraint on these fields
        update_fields (List[str], default=None): A list of fields to update whenever objects
            already exist. If an empty list is provided, it is equivalent to doing a bulk
            insert on the objects that don't exist. If ``None``, all fields will be updated.
        returning (bool|List[str]): If ``True``, returns all fields. If a list, only returns
            fields in the list. Return values are split in a tuple of created and updated models
        ignore_duplicate_updates (bool, default=False): Ignore updating a row in the upsert if all of the update fields
            are duplicates
        return_untouched (bool, default=False): Return values that were not touched by the upsert operation

    Returns:
        UpsertResult: A list of results if ``returning`` is not ``False``. created, updated, and untouched,
            results can be obtained by accessing the ``created``, ``updated``, and ``untouched`` properties
            of the result.

    Examples:

    .. code-block:: python

        # Start off with no objects in the database. Call a bulk_upsert on the TestModel, which includes
        # a char_field, int_field, and float_field
        bulk_upsert2(TestModel.objects.all(), [
            TestModel(float_field=1.0, char_field='1', int_field=1),
            TestModel(float_field=2.0, char_field='2', int_field=2),
            TestModel(float_field=3.0, char_field='3', int_field=3),
        ], ['int_field'], ['char_field'])

        # All objects should have been created
        print(TestModel.objects.count())
        3

        # Now perform a bulk upsert on all the char_field values. Since the objects existed previously
        # (known by the int_field uniqueness constraint), the char fields should be updated
        bulk_upsert2(TestModel.objects.all(), [
            TestModel(float_field=1.0, char_field='0', int_field=1),
            TestModel(float_field=2.0, char_field='0', int_field=2),
            TestModel(float_field=3.0, char_field='0', int_field=3),
        ], ['int_field'], ['char_field'])

        # No more new objects should have been created, and every char field should be 0
        print(TestModel.objects.count(), TestModel.objects.filter(char_field='-1').count())
        3, 3

        # Do the exact same operation, but this time add an additional object that is not already
        # stored. It will be created.
        bulk_upsert2(TestModel.objects.all(), [
            TestModel(float_field=1.0, char_field='1', int_field=1),
            TestModel(float_field=2.0, char_field='2', int_field=2),
            TestModel(float_field=3.0, char_field='3', int_field=3),
            TestModel(float_field=4.0, char_field='4', int_field=4),
        ], ['int_field'], ['char_field'])

        # There should be one more object
        print(TestModel.objects.count())
        4

        # Note that one can also do the upsert on a queryset. Perform the same data upsert on a
        # filter for int_field=1. In this case, only one object has the ability to be updated.
        # All of the other objects will be created
        bulk_upsert2(TestModel.objects.filter(int_field=1), [
            TestModel(float_field=1.0, char_field='1', int_field=1),
            TestModel(float_field=2.0, char_field='2', int_field=2),
            TestModel(float_field=3.0, char_field='3', int_field=3),
            TestModel(float_field=4.0, char_field='4', int_field=4),
        ], ['int_field'], ['char_field'])

        # There should be three more objects
        print(TestModel.objects.count())
        7

        # Return creates and updates on the same set of models
        created, updated = bulk_upsert2(TestModel.objects.filter(int_field=1), [
            TestModel(float_field=1.0, char_field='1', int_field=1),
            TestModel(float_field=2.0, char_field='2', int_field=2),
            TestModel(float_field=3.0, char_field='3', int_field=3),
            TestModel(float_field=4.0, char_field='4', int_field=4),
        ], ['int_field'], ['char_field'])

        # All four objects should be updated
        print(len(updated))
        4