python – Linkify Foreign Keys with Django Tables2 and Slugs

I am trying to build summary tables with clickable links that drill down to lower levels. The hierarchy in descending order is Division -> Region -> Branch -> Profit Center where each profit center can have multiple “receivables.” I want the division summary view to show all of the regions (eg California, Central Texas, Ohio, etc) and a sum of the ‘total_amount’ for the regions. I want to be to click a region and it drills down to the branches where I can see a summary for each branch. So on and so forth

I’ve spent far longer than I would like to admit on this and the lines commented out on the tables.py is just a fraction of what I have tried.

What am I missing?

class Division(models.Model):
    name = models.CharField(max_length=128, unique=True)
    slug = models.SlugField(unique=True, null=True)

    def __str__(self):
        return self.name

    def get_absolute_url(self):
        return reverse('division-detail', args=[str(self.slug)])

class Region(models.Model):
    name = models.CharField(max_length=128, unique=True)
    division = models.ForeignKey('Division', to_field='name', on_delete=models.CASCADE)
    slug = models.SlugField(unique=True, null=True)

    def __str__(self):
        return self.name

    def get_absolute_url(self):
        return reverse('region-summary', args=[str(self.slug)])

    def save(self, *args, **kwargs):
        if not self.slug:
            self.slug = slugify(self.name)
        super().save(*args, **kwargs)

class Branch(models.Model):
    name = models.CharField(max_length=128)
    number = models.CharField(max_length=4, unique=True)
    region = models.ForeignKey('Region', to_field='name', on_delete=models.CASCADE)
    slug = models.SlugField(unique=True, null=True)

    def __str__(self):
        return self.number

    def save(self, *args, **kwargs):
        if not self.slug:
            self.slug = slugify(self.number)
        super().save(*args, **kwargs)

    def get_absolute_url(self):
        return reverse('branch-detail', args=[str(self.slug)])

class Profit_Center(models.Model):
    branch = models.ForeignKey('Branch', on_delete=models.CASCADE)
    number = models.CharField(max_length=32, unique=True, editable=False, null=True, blank=True)
    slug = models.SlugField(unique=True, null=True)

class Receivable(models.Model):
    document = models.IntegerField(primary_key=True, blank=False)
    payer = models.ForeignKey('Payer', on_delete=models.PROTECT)
    profit_center = models.ForeignKey(Profit_Center, to_field = 'number', on_delete=models.PROTECT,  null=True)
    total_amount = models.DecimalField(decimal_places=2, max_digits=20, null=True, blank=True)
    slug = models.SlugField(unique=True, null=True, editable=False)
tables.py

#summary by selected division, showing summary by region
class ReceivableDivisionSummaryHTMxTable(tables.Table):
    #profit_center__branch__region = tables.Column(linkify=('region-summary', {'slug': 'central-texas'})) #always goes to central texas
    #profit_center__branch__region = tables.Column(linkify=('region-summary', {'region': tables.A('region'), 'slug': tables.A('region.slug')})) #fails on accessor key
    #profit_center__branch__region = tables.Column(linkify=('region-summary', {'slug': tables.A('profit_center__branch__region.slug')}))
    #profit_center__branch__region = tables.Column(linkify=('region-summary', {'slug': tables.A('name')})) #fails on accessor key
    #profit_center__branch__region = tables.LinkColumn('region-summary', args=[A('profit_center__branch__region.slug')])
    #profit_center__branch__region = tables.Column(linkify=True)
    #profit_center__branch__region = tables.LinkColumn('region-summary', kwargs={'slug': 'record.slug'}) #half-ass works, returns regionsummary/region_slug insteald of the real slug
    #profit_center__branch__region = tables.LinkColumn('region-summary', args=[A('pk')])
    #profit_center__branch__region = tables.Column(linkify=('region-summary', {'pk': tables.A('region__pk')}))  #half-ass works, returns regionsummary/region_slug insteald of the real slug
    #profit_center__branch__region = tables.LinkColumn('region-summary', args=[A('slug')])
    #profit_center__branch__region = tables.Column(linkify=('region-summary', {'slug': tables.A('region.slug')}))
    #species =tables.TemplateColumn('<a href="{% url "main:species" name value %}">{{value}}</a>')
    profit_center__branch__region = tables.TemplateColumn('<a href="{% url "region-summary" slug %}">{{value}}</a>') #sooooo fucking close, returns the division- (e.g. Ford) rather region (e.g. Ohio), though it displays Ohio correctly as the value

    class Meta:
        model = Receivable
        template_name="tables/bootstrap_htmx.html"
        fields = ('profit_center__branch__region', 'sum_col')
views.py
def ReceivableDivisionSummaryHTMxTableView(request, slug):
    #post = get_object_or_404(Receivable, slug=slug)
    queryset = Receivable.objects.values('profit_center__branch__region').annotate(sum_col=Sum('total_amount'))
    table= ReceivableDivisionSummaryHTMxTable(queryset)
    context = {'table': table, 'slug': slug}

    if request.htmx:
        template_name = "app/receivable_table_partial.html"
    else:
        template_name="app/receivable_summary_htmx.html"
    template_name
    return render(request, template_name, context)

def ReceivableRegionSummaryHTMxTableView(request, slug):
    queryset = Receivable.objects.values('profit_center').annotate(sum_col=Sum('total_amount'))
    table= ReceivableRegionSummaryHTMxTable(queryset)
    context = {'table': table, 'slug':slug}

    if request.htmx:
        template_name = "app/receivable_table_partial.html"
    else:
        template_name="app/receivable_summary_htmx.html"
    template_name
    return render(request, template_name, context)

Update: I’ve reduced it to the absolute bare minimum and I am still running into the error: for linkify=True, '100100' must have a method get_absolute_url

100100 is a profit center

models.py

class Profit_Center(models.Model):
    branch = models.ForeignKey('Branch', on_delete=models.CASCADE)
    number = models.CharField(max_length=32, unique=True, editable=False, null=True, blank=True)
    profit_center_slug = models.SlugField(unique=True, null=True)
     def get_absolute_url(self):
        return reverse('branch-detail', args=[str(self.profit_center_slug)])


class Receivable(models.Model):
    document = models.IntegerField(primary_key=True, blank=False)
    profit_center = models.ForeignKey(Profit_Center, to_field = 'number', on_delete=models.PROTECT,  null=True)
    total_amount = models.DecimalField(decimal_places=2, max_digits=20, null=True, blank=True)
    receivable_slug = models.SlugField(unique=True, null=True, editable=False)
tables.py

class AllProfitCenterSummaryHTMxTable(tables.Table):
    profit_center = tables.Column(linkify=True)

    class Meta:
        model = Receivable
        template_name="tables/bootstrap_htmx.html"
        fields = ('profit_center', 'sum_col', )
views.py

def AllProfitCenterSummaryHTMxTableView(request):
    queryset = Receivable.objects.values('profit_center').annotate(sum_col=Sum('total_amount'))
    table= AllProfitCenterSummaryHTMxTable(queryset)
    
    if request.htmx:
        template_name = "app/receivable_table_partial.html"
    else:
        template_name="app/receivable_summary_htmx.html"
    template_name
    return render(request, template_name, {'table':table})
urls.py
path('htmx/allprofitcentersummary/', views.AllProfitCenterSummaryHTMxTableView, name="all-profit-center-summary"),

Update 2: getting closer. I’ve got it half working with the below code:

class ReceivableDivisionSummaryHTMxTable(tables.Table):
    class Meta:
        model = Receivable
        template_name="tables/bootstrap_htmx.html"
        fields = ('profit_center__branch__region', 'sum_col',)

    def render_profit_center__branch__region(self, value):
        return format_html('<a href="regionsummary/{}">{}</a>', value, value)

However, it is taking the text value of the field (eg Central Texas). However, I need it to be slugified (eg central-texas). I tried to do it with the following, but it isn’t taking.

    def render_profit_center__branch__region(self, value):
        value_slug = value
        slugify(value_slug)
        return format_html('<a href="regionsummary/{}">{}</a>', value_slug, value)

Can I pass a slug into the render?

Update 3: I’ve got the slugification to work with the following:

    def render_profit_center__branch__region(self, value):
        return format_html('<a href="regionsummary/{}">{}</a>', slugify(value), value)

However, I still need to pass a slug into the render. I want my links to formatted as divisionsummay/NAMEOFDIVSION/regionsummary/NAMEOFDIVSION

I need to pass a slug back to the render similar to the following:

    def render_profit_center__branch__region(self, value, division_slug):
        return format_html('<a href="{}/regionsummary/{}">{}</a>', division_slug, slugify(value), value)

Obviously, that isn’t working. Not sure how to pass the division_slug.

Leave a Comment