2

I'm facing an issue, and can't figure out how to solve it.

I have the following form in my application:

class ContentForm(ModelForm):
    class Meta:
        model = Content
        fields = ('url_1','url_2','url_3','url_4','url_5',)
        widgets = {
            'url_1': forms.URLInput(attrs={'class': 'form-control', 'maxlength': 100}),
            'url_2': forms.URLInput(attrs={'class': 'form-control', 'maxlength': 100}),
            'url_3': forms.URLInput(attrs={'class': 'form-control', 'maxlength': 100}),
            'url_4': forms.URLInput(attrs={'class': 'form-control', 'maxlength': 100}),
            'url_5': forms.URLInput(attrs={'class': 'form-control', 'maxlength': 100}),
        }

What I'm trying to do is to generate those url fields dynamically through a loop. I took this approach (which implements a template tag for dynamic attribute lookup):

{% for i in 12345|make_list %}
    {% with url='url_'|add:i %}
      {{ form|getattribute:url }}
    {% endwith %}
{% endfor %}

However it's not working. Django doesn't render the input field and complains that my form object doesn't have an attribute called url_1 for example. If I call the url_1 attribute for the same form directly it works.

The implementation of the getattribute template tag is the same as here.

[UPDATE: posted my code below]

views.py

@login_required
def new(request):
    form = ContentForm()
    return render(request, "new.html", {'form': form})

new.html

<form action="{% url 'create_content' %}" method="POST">
...
{% include 'partials/urls.html' %}
...
</form>

urls.html

{% load getattribute %}

{% for i in 12345|make_list %}
    {% with url='url_'|add:i %}
      <label>URL {{i}}</label>
      {{ form|getattribute:url }}
    {% endwith %}
{% endfor %}

getattribute.py

import re
from django import template
from django.conf import settings

numeric_test = re.compile("^\d+$")
register = template.Library()

def getattribute(value, arg):
        """Gets an attribute of an object dynamically from a string name"""

        return getattr(value, arg)

    if hasattr(value, str(arg)):
        return getattr(value, arg)
    elif hasattr(value, 'has_key') and value.has_key(arg):
        return value[arg]
    elif numeric_test.match(str(arg)) and len(value) > int(arg):
        return value[int(arg)]
    else:
        return settings.TEMPLATE_STRING_IF_INVALID

register.filter('getattribute', getattribute)

ps.: django version running is 1.9.2

Community
  • 1
  • 1
Igor Belo
  • 718
  • 6
  • 14

1 Answers1

3

Change the getattribute.py as below:

import re
from django.forms import Form, ModelForm
from django import template
from django.conf import settings

numeric_test = re.compile("^\d+$")
register = template.Library()

def getattribute(value, arg):
    """Gets an attribute of an object dynamically from a string name"""
    if hasattr(value, str(arg)):
        return getattr(value, arg)
    elif hasattr(value, 'has_key') and value.has_key(arg):
        return value[arg]
    elif numeric_test.match(str(arg)) and len(value) > int(arg):
        return value[int(arg)]
    elif issubclass(value.__class__, (Form, ModelForm)) and arg in value.fields:
        return value[arg]
    else:
        return settings.TEMPLATE_STRING_IF_INVALID

register.filter('getattribute', getattribute)

You can see in 17th and 18th lines that expected for special condition, related to forms and returned related rendered widget ;)

M.javid
  • 6,387
  • 3
  • 41
  • 56
  • You shouldn't need to add special checks for forms. The `has_key` check will work for Python 2 (it should be changed for Python 3). – Alasdair Apr 14 '16 at 23:17
  • The `has_key` method do samething as `arg in value.fields`, thanks @Alasdair – M.javid Apr 14 '16 at 23:19
  • Your saying is correct, but keys in `value` is equal to `value.fields`... i change it only for you :) – M.javid Apr 14 '16 at 23:25
  • after changing my code, cant see fields... Current code is usable – M.javid Apr 14 '16 at 23:31
  • Mohsen GREAT!!!! It worked changing the subclass verification upon `ModelForm` instead of `Form`. Thanks a lot. ps: I still don't get why the `getattr` approach didn't worked as they are the same object. ps2: I'm wondering why I didn't try it before :P – Igor Belo Apr 14 '16 at 23:38
  • 1
    @Mohsen my mistake, it looks like the `has_key` check returns False for Django forms. I think the best solution might be to replace the `has_key` check with `return value[arg]`, and catch the possible `KeyError`. Then you don't have special code for the `Form` class. – Alasdair Apr 14 '16 at 23:42
  • The `getattr` function return value of a **property** but in forms field-name using as key and you must check key existing status by `in` keyword – M.javid Apr 14 '16 at 23:44
  • @Alasdair however your suggest not worked in this case, but we learned a new trick, thanks very much. – M.javid Apr 14 '16 at 23:47