djangodjango-modelsdjango-admindjango-contenttypesdjango-2.0

Model can have a ForeignKey with one of the two models


I need some help with an issue that I am not able to resolve on my own. So in this model, a tenancy document can either have a ForeignKey with Building or Property.

We may have a tenancy agreement on the whole building or on a single property within that building. In the former case the tenancy documents are applied to the building, and in the latter case they only apply to a property.

I used content_types to add a generic foreign key but now I can’t figure out how to add autocomplete fields to contenttype and in the dropdown, I just see building and property in admin form. I want to see building names and property names.

I learned about autocomplete fields in Django 2.0, it’s awesome but I don’t know how can I use something like that in this particular case or if there is a better way to do this?

models.py:

class TenancyDocument(models.Model):

    KINDS = Choices('Tenancy Agreement', 'Stamp Duty', 'Inventory List')

    id = FlaxId(primary_key=True)
    kind = StatusField(choices_name='KINDS')
    start_date = models.DateField(blank=True, null=True)
    end_date = models.DateField(blank=True, null=True)

    created = models.DateTimeField(auto_now_add=True)
    modified = models.DateTimeField(auto_now=True)

    content_type_limit = Q(
        app_label='properties', model='building') | Q(
            app_label='properties', model='property')

    content_type = models.ForeignKey(
        ContentType,
        limit_choices_to=content_type_limit,
        on_delete=models.CASCADE,
        verbose_name='Lease type'
        )

    object_id = FlaxId(blank=True, null=True)
    content_object = GenericForeignKey('content_type', 'object_id')

    def __str__(self):
        return self.kind

admin.py:

@admin.register(TenancyDocument)
class TenancyDocumentAdmin(admin.ModelAdmin):
    list_display = ('id', 'kind', 'start_date', 'end_date','content_type')
    list_filter = ('kind',)

Solution

  • It seems like the generic foreign key has always been more trouble than it's worth. It takes a simple concept, a relational join, and tries to make it clever, but then downstream packages like autocomplete won't work.

    I ended up switching to two separate foreign keys, then added attributes to the class to pull fields from the correct related record.

    class TenancyDocument(models.Model):
        building = models.ForeignKey(Building, ondelete='CASCADE', null=True, blank=True)
        prop = models.ForeignKey(Property, ondelete='CASCADE', null=True, blank=True)
    
        def clean(self):
            if not self.building and not self.prop:
                raise ValidationError('Must provide either building or property.')
            if self.building and self.prop:
                raise ValidationError('Select building or property, but not both.')
            super().clean()