views:

270

answers:

1

I've been trying to modify the AuditTrail code so that it does not copy ForeignKey fields, but rather copies the related field (ie I don't want a foreign key on my database table for the audit model).

I've written a copy_field function that looks like so:

def copy_field(field):
    while(isinstance(field, models.OneToOneField, models.ForeignKey)):
        field = field.rel.get_related_field()
    if isinstance(field, models.AutoField):
        f = models.IntegerField()
    else:
        f = copy(field)
    #...snip some adjusting of fs attributes...
    return f

This code is run when the model that has the AuditTrail attribute is prepared (via the class_prepared signal).

However, this runs into problems when a ForeignKey is related to a field on a model that has not been prepared yet - the get_related_field() call will fail, because field.rel.to is a string containing the name of the related model, rather than a model instance.

I'm at a loss of what to do to work around this. Do I have to determine what dependencies a model has, and wait until they have all been prepared, before I start copying fields? Any ideas about the best way to go about this?

A: 

What I ended up doing was to list all the dependencies the model had (by determining a canoncial app/name pair; copying some code from django.db.models.fields.related to determine this) and modifying my class_prepared signal handler to listen to all models rather than just my target model.

When the handler recognized a model in my dependencies list, it would remove it from the list and check if the list was empty; if it was, it is ok to create the audit model. Important note was to disconnect the class_prepared handler before creating the model, else I encountered an infinite recursion (alternatively I could have gated the handler more specifically).

    dependencies = []
    for field in cls._meta.local_fields:
        while isinstance(field, (models.OneToOneField, models.ForeignKey)):
            if isinstance(field.rel.to,basestring):
                dependencies.append(get_canonical(cls,field.rel.to))
                break
            else:
                field = field.rel.get_related_field()

    def _contribute(sender, **kwargs):
        key = (sender._meta.app_label, sender.__name__)
        if key in dependencies:
            dependencies.remove(key)
        if not dependencies:
            models.signals.class_prepared.disconnect(_contribute)
            model = create_audit_model(cls)

    models.signals.class_prepared.connect(_contribute, weak=False)
Wogan