django-audit-log icon indicating copy to clipboard operation
django-audit-log copied to clipboard

Concrete Inheritance: Reverse query name for 'model.field' clashes with reverse query name for 'another_model.field'.

Open bobort opened this issue 8 years ago • 1 comments

I have a model called Document that is concrete. I have multiple models that inherit directly from Document, such as WorkOrder(Document) and PurchaseOrder(Document). The sub classes have audit logs, and I couldn't get the audit logs to work with the parent class Document. It's okay because the PurchaseOrder and WorkOrder audit logs have a reference to the Document fields. The problem is that the Document class has two ForeignKeys with a related_name attribute set. django-audit-log does not handle them properly in its current incarnation. I suggest something along the following changes, which better correspond to Django's own related_name conventions with class and app_label:

Source in AuditLog(object):

if field.rel and field.rel.related_name:
    field.rel.related_name = '_auditlog_%s' % field.rel.related_name                
elif field.rel: 
    try:
        if field.rel.get_accessor_name():
            field.rel.related_name = '_auditlog_%s' % field.rel.get_accessor_name()
    except:
        pass

Proposed change, which does take care of the error:

if field.rel:
    format_dict = {'class': model.__name__.lower(),
                   'app_label': model._meta.app_label.lower()}
    if field.rel.related_name:
        field.rel.related_name = '_auditlog_%s' % (field.rel.related_name % format_dict)
    else:
        try:
            if field.rel.get_accessor_name():
                field.rel.related_name = '_auditlog_%s' % 
                                         (field.rel.get_accessor_name() % format_dict)
        except:
            pass

If I am handling it improperly, please let me know.

bobort avatar Oct 28 '15 18:10 bobort

Actually, I continue to run into problems with AuditLogManager and concrete inheritance. I'm trying to save a WorkOrder, which inherits from Document. PurchaseOrder also inherits from Document. Django throws this error:

ProgrammingError at /documents/wo/create
column documents_purchaseorder.field does not exist

Why would the AuditLogManager try to use a field on the PurchaseOrder model when I'm saving a WorkOrder instance? I suppose it's trying to get the Document fields, but it goes a step to far in getting all of the fields linked to the Document fields, which would include PurchaseOrder fields.

More details on the error seem to occur in the middleware in _disable_audit_log_managers(instance):

def _update_post_save_info(self, user, session, sender, instance, created, **kwargs ):
        if created:
            registry = registration.FieldRegistry(fields.CreatingUserField)
            if sender in registry:
                for field in registry.get_fields(sender):
                    setattr(instance, field.name, user)
                                _disable_audit_log_managers(instance) ...
                    instance.save()
                    _enable_audit_log_managers(instance)
            registry = registration.FieldRegistry(fields.CreatingSessionKeyField)
            if sender in registry:

And more details, it seems to be here, when getting the dir of the instance. WorkOrder inherits from Document, which does of have an implicit attribute to PurchaseOrder and all the other models that inherit from Document. PurchaseOrder does have an AuditLog associated with it. Perhaps dir is the wrong way to go about it. I found this stackoverflow answer on getting all of the managers on an instance. It may be a better way to determine which managers are instances of AuditLogManager. http://stackoverflow.com/questions/7932053/how-can-i-get-all-relatedmanagers-from-a-particular-django-model-instance

from audit_log import registration, settings
from audit_log.models import fields
from audit_log.models.managers import AuditLogManager
def _disable_audit_log_managers(instance):
    for attr in dir(instance):
        try:
                        if isinstance(getattr(instance, attr), AuditLogManager): ...
                getattr(instance, attr).disable_tracking()
        except AttributeError:
            pass
def _enable_audit_log_managers(instance):

bobort avatar Oct 29 '15 14:10 bobort