Hello,
I'm having a hard time figuring out both from docs and code what the best strategy is for preventing a loop, if in post_save() you need to save the provided instance.
I'm implementing an ImageField that is able to handle multiple arbitrary thumbnail images.
For that purpose I've registered a signal, analog to what ImageField is doing, but using the post_save signal as I need to have the already saved original available:
def contribute_to_class(self, cls, name, **kwargs):
super().contribute_to_class(cls, name, **kwargs)
# noinspection PyProtectedMember
if not cls._meta.abstract:
signals.pre_save.connect(
self.generate_thumbs, sender=cls,
)
The idea was to have the available thumbnails and their url and path stored in a PostgreSQL Hstore field. For that to work, I need to save the model again, hence the loop.
To test how to prevent the loop using dispatch_uid, I've created a small test app. The model uses a HashedTextField:
class HashedTextField(TextField):
def __init__(self, hash_fieldname, *args, **kwargs):
self.hash_fieldname = hash_fieldname
super(HashedTextField, self).__init__(*args, **kwargs)
def contribute_to_class(self, cls, name, **kwargs):
super(HashedTextField, self).contribute_to_class(cls, name, **kwargs)
post_save.connect(self.hash_field, sender=cls)
def hash_field(self, instance, *args, **kwargs):
value = getattr(instance, self.name) # type: str
ctx = Hash('ripemd160') # type: HashDescriptor
ctx.update(value)
setattr(instance, self.hash_fieldname, ctx.hexdigest())
instance.save(update_fields=[self.hash_fieldname])
def deconstruct(self):
name, path, args, kwargs = super(HashedTextField, self).deconstruct()
kwargs['hash_fieldname'] = self.hash_fieldname
return name, path, args, kwargs
(Hash and HashDescriptor are simple wrappers around hashlib, provided below sig for the interested).
I've tried:
* dispatch_uid in above contribute_to_class, either with a static string or dymically generated
* the same but then in __init__() (no diff, as __init__() is called when field is attached to model, not when an instance is created).
I can either use a different approach (save the hash as a ManyToMany) or use something like this on the model:
def __init__(self, *args, **kwargs):
self._saving_hash = False
super().__init__(*args, **kwargs)
and then update _saving_hash within hash_field(), which is less then ideal as it requires the consuming model to alter it's __init__().
Am I missing an option to do this with dispatch_uid from within the field?
--
Melvyn Sopacua
# noinspection PyUnresolvedReferences
class HashDescriptor(object):
def update(self, data, encode=True):
if encode:
self._ctx.update(data.encode())
else:
self._ctx.update(data)
def hexdigest(self):
return self._ctx.hexdigest()
class Hash(HashDescriptor):
_ctx = None
def __init__(self, name):
if name in hashlib.algorithms_available:
self._ctx = hashlib.new(name)
else:
self._ctx = hashlib.sha256()
super(Hash, self).__init__()