0

I have a complex detail view where a lot of varied data is rendered dynamically. My model has hundreds of fields, many of which are related to one another. I would like to be able to group a number of these fields in order to do queries. Is there are way to do this?

In order to cherry-pick specific fields and group them, my current approach has been to use word extensions to the field names that is common for each group. For example, field names like:

ampicillin = models.CharField(...)
ciprofloxacin = models.CharField(...)
...

become:

ampicillin_antimicro = models.CharField(...)
ciprofloxacin_antimicro = models.CharField(...)
...

The idea is then to create a field name list and access specific groups of fields using a substring search. How am I to do this? Below is a summary of my attempts and issues so far.

Using _meta

I can access the raw model field name information using _meta:

all_fields = []
for field in MyModel._meta.fields:
    all_fields.append(field)

This gives a list like:

[<django.db.models.fields.CharField: ampicillin_antimicro>, 
<django.db.models.fields.CharField: ciprofloxacin_antimicro>, ...]

but I've not been able to figure out how to extract the information I need from this, however.

Using _meta and field.get_attname_column()[0]

I can make a list of field name objects as strings:

field_names = []
for field in MyModel._meta.fields:
    newfield = field.get_attname_column()[0]
    field_names.append(newfield)

This gives an appended list for field_names:

['ampicillin_antimicro', 'ciprofloxacin_antimicro', ...]

Adding a substring search allows me to get the subset of field names I want. For example, using the search string "antimic":

field_names = []
search_string = "antimic"
for field in Bacteria._meta.fields:
    newfield = field.get_attname_column()[0]
    if search_string in newfield:
        field_names.append(newfield)

isolates the field names containing the 'group word extension' successfully. All good, except I can no longer access the actual value of the field names - just the field name itself. When the list is iterated over in the template only the field name is returned.

<li>
    {% if field_names %}
    <i style="color: blue"><b>A</b>ntibiotic tolerance:</i>
    {% for f in field_names %}
    {{f|default_if_none:''}}
    {% endfor %}
    {% endif %}
</li>

Using tuples and dictionaries

If I tuplify and dictify the list and try to use the resulting dictionary:

field_names = []
field_names_dict = dict(zip(field_names, field_names))
field_names_tuple = tuple(field_names_dict)
for key, val in field_names_tuple.items():
    if val is not None:
        field_names.append(val)

I still get the name as a result, not the value.

Hard-coded dictionaries

So far, the only thing that works is to hard-code a specific dictionary for each query in the detail view:

model = Model.objects.all()
antibiotics = []
antibiotics_dict = {'amp': model.ampicillin_antimicro, 
              'cipro': model.ciprofloxacin_antimicro, ...}
for key, val in antibiotics_dict.items():
    if val is not None:
        antibiotics.append(val)

Although this works fine, it gets complicated when the dictionaries get large and I don't like the idea of hard coding the key-value pairs. I feel like I'm very close to a solution but I'm obviously missing something because I'm a coding novice. Can someone help? Cheers.

The Shelvington Solution

Following Mr. Shelvington's suggestion, I have modified the code thus:

search_string = "antimic"
field_names = [f.name for f in Bacteria._meta.get_fields() 
    if search_string in f.name]
prelist = get_object_or_404(Bacteria.objects.values(*field_names), slug=slug)
for key, val in prelist.items():
    if val is not None:
        antibiotics = prelist

Note that there are only two variables (the substring and 'antibiotics'). The code is reusable for additional substring and dictionary creation. Then, I changed the template to suit:

<li>
    {% if antibiotics %}
    <i style="color: blue"><b>A</b>ntibiotic tolerance:</i>
    {% for key, val in antibiotics.items %}
    {{ val }}
    {% endfor %}
    {% endif %}
</li>

Additional note: I've iterated over the dictionary within the view so that the html text title disappears if values for the iteration are not found.

Danhr
  • 1
  • 3
  • Are you looping over a queryset of model instances or are you just displaying fields from a single instance? – Iain Shelvington Nov 29 '21 at 08:05
  • If I understand you correctly, I think I'm effectively iterating over a queryset of model instances, right? The initial qs is from model = get_object_or_404(Model, slug=slug). The variable 'model' is then used to derive each sub-queryset (e.g. model.ampicillin_antimicro), which I then combine into a list, dict or tuple before looping over it in the template. The result I end up seeing displayed is from the combined set of sub-querysets, if that makes sense. – Danhr Nov 29 '21 at 10:20
  • You could use `values()` and pass it a list of the fields you want to get a dict that only contains those fields? `field_names = [f.name for f in Model._meta.get_fields() if search_string in f.name]; get_object_or_404(Model.objects.values(*field_names), pk=1)`. You can then loop over the selected fields and values in the template just like a dict – Iain Shelvington Nov 29 '21 at 10:34
  • @IainShelvington: this works beautifully so far. I will update the question, and will then attempt to refactor the repetitive code using mixins. Thank you! – Danhr Nov 29 '21 at 11:47

0 Answers0