2

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',)
cezar
  • 11,616
  • 6
  • 48
  • 84
Zed
  • 379
  • 2
  • 6
  • 17

1 Answers1

1

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()
Zed
  • 379
  • 2
  • 6
  • 17
  • I like this approach because it is explicit, however it increases complexity because your code must manage business rules around FK population and form interactivity etc. Also this approach increases the risk of circular imports as your code base and project complexity grows. It can force you into an eventual re-write or spreading your code out across many different modules or apps (I've accidentally gone down that path before, it's nasty). – Bosco Apr 07 '19 at 03:58