Display additional fields on detail view -- Not in admin

I have a view as below:

class CompanyDetailView(DetailView):
    model = Company
    fields = ('name', 'status', 'created')
    ......

and have template to loop through each of the fields to display the fields as below:

    ......
          {% for field_name in fields %}
              <tr>
                <th>{{ object|get_verbose_name:field_name }}:</th>
                <td>
                  {{ object|get_value:field_name }}
                </td>
              </tr>
          {% endfor %}
    ......

This all worked fine. Now I want to add some display fields that’s not on Company model. Say, I want to change the view as below, just like what we would do with CompanyAdmin class:

    ......
    fields = ('name', 'user_count', 'status', 'created',)
    user_count.verbose_name = 'User Count'

    def user_count(self):
        return <some calculated integer>

As you can see, I need to feed in a verbose name and call the method to get value when a name in template loop is a method name, not a model field. I may calculate in view and pass the name/value with context, but I don’t want to hard code any particular name inside template, so my view can pass in any field as long as I provide necessary things in the view.
I’d think such a usage is pretty common. I’d like to know the common/best practice to display additional fields out of normal model fields.
Thanks!

That is the purpose of the get_context_data method in the Django-provided CBVs. You can override that method to add additional data to the context to be rendered by the template.

However, be aware that those additional context entries are not fields in the model and would not be iterated as part of those model fields.

If use context, I’d have to update the template every time I add a new field. Is it possible I do the template once, and next time if I have new field like this, just add it on view like we do with admin additional fields (add a method, add method name to fields, and provide a verbose name)?

That is not accurate.

The context is a dict. There is no requirement that the values in the context dict be strings.

You can add a dict as the value for an entry in the context. (Or a list, or a tuple, or other objects, etc.)

You can then iterate over the key/value pairs of that dict in your template.

In template, I passed in a dict like {‘user_count’: (‘User Count’, 1)}
But I got error on the code below

     {% if field_name in method_fields %}
          {{ method_fields[field_name][0] }}:  {{ method_fields[field_name][1] }}

The error was

Could not parse the remainder: '[field_name][0]' from 'method_fields[field_name][0]'

Do you see any syntax error on this line?

Review the docs for template variables. Also, you may want to look at some of the Django-provided templates for forms and form fields to see how they function.

You’ll probably also want to review the docs for the for tag.

Changed {{ method_fields[field_name][0] }} to {{ method_fields.field_name.0 }}, and it gave blank. If I change it to {{ method_fields }}, it gave result {‘user_count’: (‘User Count’, 1)}. So the value exists, just could not be retrieved.
Although I still like to know why this dict cannot be read, for this particular issue, I’m more interested in a statement on the doc you pointed:

Technically, when the template system encounters a dot, it tries the following lookups, in this order:

** Dictionary lookup*
** Attribute or method lookup*
** Numeric index lookup*

This really indicates that I don’t need to call the method and pass in the result in dict, but let the template call the view method. But I tried object.field_name and view.field_name, both returned nothing.
So right now I have 2 puzzles: how to read the dict, and how to access view attribute and method from template.

If the dict is:

Then {{ method_fields.user_count.0 }} would print ‘User Count’.

Your user_count method is a method on the view, not on the model, so {{ object.user_count }} won’t do anything, and view isn’t a part of the context, so {{ view.user_count }} doesn’t mean anything either.

Both {{ method_fields.user_count.0 }} and {{ method_fields.field_name.0 }} gave blank.
The user_count is the actual value on dict, the field_name is a for loop variable contains value of “user_count”.

Ok, let’s take a step back here.

Please post the complete view, so that I can see what you’re supplying to the context. Also post the full template you’re using.

The view:

class CompanyDetailView(DetailView):
    model = Company
    template_name = 'object-detail.html'
    fields = ('id', 'name', 'user_count', 'status', 'created',)
    user_count_verbose_name = 'User Count'
    

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['fields'] = self.fields
        context['method_fields'] = self.get_method_fields()
        return context
    
    # wished not have to, but need to generate a dict with this
    def get_method_fields(self):
        method_fields = {}
        model_fields = [field.name for field in Company._meta.fields]
        for field_name in self.fields:
            if not field_name in model_fields:
                method = getattr(self, field_name)
                value = method()
                key = field_name.replace('_', ' ').title()
                if hasattr(self, field_name + "_verbose_name"):
                    key = getattr(self, field_name + "_verbose_name")
                method_fields[field_name] = (key, value)
        return method_fields

    
    def user_count(self):
        company = self.get_object()
        return company.user_set.count()

and the template:

{% load company_tags %}
<table>
  {% for field_name in fields %}
      <tr>
        <th>
          {% if field_name in method_fields %}
            {{ method_fields.field_name.0 }}:   <!-- issue is mainly here -->
          {% else %}
            {{ object|get_verbose_name:field_name }}:
          {% endif %}
        </th>
        <td>
          {% if field_name in method_fields %}
            {{ method_fields.field_name.1 }}
          {% else %}
            {{ object|get_value:field_name }}
          {% endif %}
        </td>
      </tr>
  {% endfor %}
</table>

Ok, there are a number of different issues - but they all tend to revolve around your attempt to use variables as keys when accessing entries in dicts - which is not how Django templates work.

The first thing you probably want to do is identify a better structure for your context dict. In general, you would want to create it something like this:

context['fields'] = {
  'ID': self.object.id,
  'Name': self.object.name,
  'User Count': self.user_count(),
  'Status': self.object.status,
}

The mechanism(s) you use to build that dict is up to you - but that’s the type of structure you’re looking for.

Then, using the technique shown in the for tag docs, you would render it in your template like this:

{% for key, value in fields.items %}
    {{ key }} - {{ value }}
{% endfor %}

This worked. Thanks!
BTW, from the context above, can I pass HTML to the template? The code below didn’t work, it just showed the whole string literally, not as a link.

context['fields'] = {
  'ID': self.object.id,
  'Name': self.object.name,
  'Website': f'<a href="{self.object.url}" alt="No website">{self.object.url}</a>',
}

See the safestring module, specifically mark_safe function.
Also see the safe filter.

Everything works now. Thanks a lot, Ken!