rsinger86/django-lifecycle

has_changed incompatible with model with vector_fields

Opened this issue · 5 comments

I just added a django-pgvector VectorField to my model and am now having an issue with using has_changed.

The error is ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all().

The source of the error is /mixins.py", line 91, in _diff_with_initial

This doesn't happen if is_changed=False but if True then every field in the model is checked for changes.

I think there are two issues here:

  1. The check for changes should cope with the case where the field value is a vector/list
  2. The check for changes should only be applied to the fields in when_any, not to all field

Cheers!

Hi, can you give an example of how you're using the hook? i.e.:

@hook(AFTER_SAVE, has_changed=True)

This doesn't happen if is_changed=False but if True then every field in the model is checked for changes.

This is the expected behaviour

class Player(LifecycleModelMixin, AbstractUser):
    name_embedding = VectorField(dimensions=1536, blank=True, null=True)

    # has_changed=False at the moment because of bug with VectorField
    @hook(AFTER_CREATE)
    @hook(AFTER_SAVE, when_any=["username", "first_name", "last_name"])
    def update_name_embedding(self):
        ...

This doesn't happen if is_changed=False but if True then every field in the model is checked for changes.
This is the expected behaviour

I can see that could make sense, but it's ambiguous when using when_any.

Perhaps it would be better if has_changed could accept a boolean (checks all fields) or a string or list of strings (checks only these fields)?

I'm sorry but I'm not sure if I understand what you want to achieve. Could you write in words when you expect your hooks to be fired so I can help you better?

I have firstname, last name username fields on the Player model (a User subclass).

I use these to create a text embedding of a combined string f"{firstname} {last_name} {username}.
This is used by a semantic search engine to match usernames in unstructured user-generated data.

I have a function that calls the OpenAI API to generate the embedding. I want this to be called:

  • When a player is added (AFTER_CREATE)
  • When any of the 3 fields mentioned above are changed (AFTER_SAVE, when_any=["username", "first_name", "last_name"])

I don't want it to be called if other fields on the Player model change, so would like the has_changed flag to apply only to the fields mentioned in the AFTER_SAVE decorator.

so would like the has_changed flag to apply only to the fields mentioned in the AFTER_SAVE decorator.

That's how it works.

This should do the trick, it will fire the hook when any of these changes.
@hook(AFTER_SAVE, when_any=["username", "first_name", "last_name"], has_changed=True)

class Player(LifecycleModelMixin, AbstractUser):
    name_embedding = VectorField(dimensions=1536, blank=True, null=True)

    @hook(AFTER_CREATE)
    @hook(AFTER_SAVE, when_any=["username", "first_name", "last_name"], has_changed=True)
    def update_name_embedding(self):
        ...