6

Another basic question I'm afraid which I'm struggling with. I've been through the various Django documentation pages and also search this site. The only thing I have found on here was back in 2013 which suggested setting up a custom filter template.

Anyhow, I'm trying to generate my own form instead of using Django's own way of generating it through {{ form }}. This is simply so I can control the way the form is presented.

I've worked out various ways to access the required information such as (within my for item in form loop);

  • item.help_text
  • item.label_tag
  • item.id_for_label

I'm trying to identify the item type so I can use the correct input type, however I'm struggling to workout what item.xxxx should be. Since this is correctly displayed through {{ form }} I am making an assumption that this information is available somewhere in the form, just struggling to find out how to access it so I can identify the correct input type. I'm doing this manually so I can use the correct Bootstrap styles to display the input fields.

Any assistance would be appreciated (or just pointing in the right direction). I'm very new to this so apologies for my very basic questions, its difficult without knowing someone I can just go an ask these questions to.

Regards

Wayne

Not sure if you need it but here is some code;

Form:

class NewsForm(ModelForm):
    class Meta:
        model = News_Article
        exclude = ('news_datetime_submitted', 'news_yearmonth', )
        labels = {
            'news_title': _('Enter News Title'),
        }
        help_texts = {
            'news_title': _('Enter a title to give a short description of what the news is.'),
        }
        error_messages = {
            'news_title': {
                'max_length': _("News title is too long."),
            },
        }

View (not worked on the POST bit yet, this is just what's from the Django documentation, POST is my next thing to work out)

def create(request, dataset):
    if dataset not in ['news', 'announcement']:
        return HttpResponseRedirect(reverse('pages'))
    rDict = {}
    if request.method == 'POST':
        if dataset == "news":
            form = NewsForm(request.POST)
        elif dataset == "announcement":
            form = AnnouncementForm(request.POST)
        if form.is_valid():
            return HttpResponseRedirect('/home/')
        else:
            pass
    else:
        announcement = get_announcement()
        if not announcement == None:
            rDict['announcement'] = announcement
        if dataset == "news":
            rDict['form'] = NewsForm()
            rDict['branding'] = {'heading': 'Create News Item', 'breadcrumb': 'Create News', 'dataset': 'create/' + dataset + '/'}
        elif dataset == "announcement":
            rDict['form'] = AnnouncementForm()
            rDict['branding'] = {'heading': 'Create Announcement', 'breadcrumb': 'Create Announcement', 'dataset': 'create/' + dataset + '/'}
        rDict['sitenav'] = clean_url(request.path, ['"', "'"])
        rDict['menu'] = Menu.objects.all().order_by('menu_position')
#        pdb.set_trace()
        return render(request, 'en/public/admin/admin_create.html', rDict)

Template code

<form action="/siteadmin/{{ branding.dataset }}" method="post">
    {% csrf_token %}
    {% for item in form %}
        <div class="row">
            <div class="col-xs-2 col-md-2">
            </div>
            <div class="col-xs-4 col-md-4">
                <div class="panel-title pull-right">
                    {% if item.help_text %}
                      <img src="/static/images/info.png" height="20" width="20" aria-hidden="true" data-toggle="popover" title="{{ item.help_text }}">&nbsp
                    {% endif %}
                    {{ item.label_tag }}
                </div>
            </div>
            <div class="col-xs-4 col-md-4">
                <div class="input-group">       
                    <input type="{{ item.widget }}" class="form-control" placeholder="" aria-describedby="{{ item.id_for_label }}">
                </div>
            </div>
            <div class="col-xs-2 col-md-2">
                {% if forloop.last %}
                    <input type="submit" value="Submit" />
                {% endif %}
            </div>          
        </div>
    {% endfor %}
</form>
Moe Far
  • 2,742
  • 2
  • 23
  • 41
Smurf
  • 541
  • 1
  • 7
  • 12

2 Answers2

7

Try this:

<input type="{{ item.field.widget.input_type }}" ...

No link to docs, found it by using debugger (not the best practice, I know...)

As per @Smurf's comment, this won't work for all widgets, like Select, CheckBox, any MultiWidget, etc... Seems to only work for text input and its variants (password, email...)


A better solution is to create custom widgets and render your form fields in template as usual. You can set any custom attributes there, see https://docs.djangoproject.com/en/1.8/ref/forms/widgets/#customizing-widget-instances


If you absolutely have to modify widgets in the template, then use django-widget-tweaks

This app provides a nice-looking form filters to alter widgets (i.e. their attributes). But be aware that it does so by way of string mongering with the already-rendered HTML ("rendered" as concernes the Widget instance).

frnhr
  • 12,354
  • 9
  • 63
  • 90
  • 2
    Please do not use this - this wont appear on all `Widget` types. For example, it will not appear on `Select` widget. Creating custom widgets is the correct answer. – DanielB Jul 21 '15 at 12:08
  • Thanks again @frnhr for the response. Interestingly, CheckBox doesn't seem to work. Item.field.widget will list the CheckBox item as – Smurf Jul 21 '15 at 12:09
  • Thanks DanielB. Noticed that. Do you have a link to a howto guide for creating custom widgets if I'm using the Model to create the form. Unless I'm missunderstanding something in the Django guide, that goes through creating custom widgets when create the form from scratch. Its not too much of a problem doing it manually for the project I'm doing currently, but my next project will have a lot of form fields. Thanks, Wayne – Smurf Jul 21 '15 at 12:13
  • The answer @Daniel Roseman gave should work for you, if you still need to create custom widgets you can use the `widgets` attribute in the same way you've used `labels`, `help_texts`, ... in the `Meta` class. – DanielB Jul 21 '15 at 12:18
  • @Smurf see the link in my answer, it shows exactly how to add attributes to widgets. – frnhr Jul 21 '15 at 12:24
  • Hi again. I've tried the following to try and add it to my NewsForm class; widgets = { 'news_text': _('Textarea(attrs={'cols': 80, 'rows': 20})'),} and also widgets = {'news_text': {'Textarea': {'cols': 80, 'rows': 20},}}. Cannot seem to work out how to add this widgets parameter. Looking at https://docs.djangoproject.com/en/1.8/topics/forms/modelforms/#modelforms-factory. – Smurf Jul 21 '15 at 12:57
  • `widgets = {'news_text': Textarea(attrs={'rows': 20, 'cols': 80}), }`, ref: https://docs.djangoproject.com/en/1.8/ref/forms/widgets/#customizing-widget-instances – frnhr Jul 21 '15 at 13:00
3

This is the wrong way of doing it. The customisations you're doing here are all to attributes of the input, which are trivially done in the form class itself.

class NewsForm(ModelForm):
    news_title = forms.CharField(widget=forms.TextInput(
        attrs={'class': 'form-control', 'placeholder': 'whatever', label: ('Enter News Title')})

and now you can do:

<div class="input-group">       
    {{ item }}
</div>

Edit

If you don't want to redefine each field, you can set the widgets directly in the Meta class:

class Meta:
    model = News_Article
    widgets = {
        'news_title': forms.TextInput(attrs={'class': 'form-control'})
    }

If even that is too much repetition, you can modify the attributes directly in the form's __init__ method:

class NewsForm(ModelForm):
    def __init__(self, *args, **kwargs)
        super(NewsForm, self).__init__(*args, **kwargs)
        for field in self.fields.values():
            field.widget.attrs['class'] = 'form-control'
Daniel Roseman
  • 588,541
  • 66
  • 880
  • 895
  • Thanks for the reply daniel-roseman. I did think about doing it this way, however this doesn't allow you to base the form on the Model, which is how I have done it above. Its frustrating that Django doesn't provide the necessary attributes to manually create the forms easier (i.e. I've found no way to detect the input type the field is so I can customise my input type more. – Smurf Jul 23 '15 at 11:34
  • I don't understand your comment. This is absolutely based on the model; the only thing you're doing is overriding the `news_title` field. You even just customise the widget itself, by defining a `widgets` dictionary in the Meta class. – Daniel Roseman Jul 23 '15 at 11:37
  • Sorry Daniel, I'm getting myself well confused. Isn't your class manually creating the form fields for the NewsForm ? Thats what I meant with my replay. Mine has class Meta: model = News_Article so i dont need to define each field and the data type. Am i not doing it correct when using the already defined model ? – Smurf Jul 23 '15 at 12:04