Opened 12 years ago

Closed 12 years ago

Last modified 11 years ago

#20267 closed New feature (worksforme)

default form values (not just initial)

Reported by: clime Owned by: nobody
Component: Forms Version: 1.5
Severity: Normal Keywords: form, default
Cc: Triage Stage: Unreviewed
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

Hello,

I am finding the "initial" functionality on forms lacking.

With this form:

class Form(forms.Form):
    name = forms.CharField(required=False, initial='Hello world')

If in view, I do something like:

form = Form(request.GET)
if form.is_valid():
    name = form.cleaned_data['name']

Then initial value of name is lost even if request.GET does not contain name as key.

Why there is not just "default" attribute that would maintain the default value until being explicitly overwritten by request data? There are so few things I find inconvenient in Django but this is one of them.

Change History (5)

comment:1 by Claude Paroz, 12 years ago

Resolution: worksforme
Status: newclosed

Sorry, but your use case is not very well explained. When providing an initial value, it should show up in the form, and then it's the user's ability to change it to something else. For any special use case, you are free to subclass any clean_[field_name] methods to implement any additional logic you'd like to have (like some default).

comment:2 by clime, 12 years ago

Well my problem was that I have this form for filtering:

class CragFilter(forms.Form):
    country = forms.ChoiceField(choices=Country.objects.all().order_by('name').values_list('id', 'name'), required=False)
    crag_type = forms.MultipleChoiceField(choices=crag_types, initial=[str(BOULDER), str(ROUTE)], widget=forms.CheckboxSelectMultiple, required=False)
    order_by = forms.CharField(required=False, initial='crag')
    order_asc = forms.BooleanField(required=False, initial=True)

Now there were actually two problems. The first one was with the initial value for crag_type. Filtering should be done by GET requests, I believe, so I simply did this in my view:

def crag_list(request):
    crag_filter = CragFilter(request.GET)
    
    if crag_filter.is_valid():
       filter_params = crag_filter.cleaned_data
    
    crag_list = Crag.get_objects(filter_params)

      context = {
          'crag_filter': crag_filter,
          'crag_list': crag_list,
      }
      return render(request, 'crags.html', context)

Seems quite natural to me. But I have found out that checkboxes in frontend are not checked after the initial page load (without any query params). And later I have found out it is because form has been bounded to request.GET data. But mainly the crag_list was empty because the initial values has been lost. So I have decided to do this in the view:

if request.GET:
  crag_filter = CragFilter(request.GET)
else:
  crag_filter = CragFilter()

which I don't like at all but it was the simplest solution.

But I have worked on and discovered yet another problem with order_by and order_asc fields. These fields are not part of the frontend form (by graphic design and provided templates) but it seemed natural to put them into the backend/logic form anyway so that I can simply pass these parameters to model (to the method get_objects) with others and then in the model differentiate what to put as param of .filter() and what to put as a param of .order_by(). This approach simply makes view simpler (which is good, I think). Backend form does not "match" the frontend/rendered form but it was (and should be) possible to do it like this without any javascript hacks and it worked well. Except from initial values because checking if request.GET is empty before passing it to the filter was no longer working. Basically, if querystring has not been populated, submitting the form trashed initial values for ordering and submitting the ordering trashed initial values for filtering. In the end I needed to do something like this in view:

      data = request.GET.copy()
      data.setlistdefault('crag_type', [str(BOULDER), str(ROUTE)])
      data.setdefault('order_by', 'crag')
      data.setdefault('order_asc', True)
      crag_filter = CragFilter(data)

which was really painful. Maybe there is some other option with some clean_[field_name] methods but that is painful too.

Well, allright I understand that I can't expect "the initial" values to behave like "default values" (except I totally did when I was learning about forms.) but I don't understand why there is not the "default functionality" that would work in the same way like that one for models. It would cover the "initial functionality" but also would be more solid, robust and intuitive...basically it would decouple frontend (rendering) functionality from backend (logic) functionality.

Last edited 12 years ago by clime (previous) (diff)

comment:3 by Claude Paroz, 12 years ago

Thanks for sharing your use case. You have to admit that it is somewhat specialized, and do not match the traditional form usage. So I maintain my original opinion, and with using clean_order_by-type methods you can easily achieve your goals.

I think the role of a framework is to 1. provide easy-to-use tools for the 80% of use cases, 2. provide ways/hooks to customize for the 20% special needs. And I think the current implementation does a rather good job in this regard.

But of course, you are free to discuss that and get opinions from others on django-developers mailing list and point to this ticket for more details.

comment:4 by clime, 12 years ago

You are very friendly community here. Maybe more people will have the similar problem, if not, then it is not important.

comment:5 by clime, 11 years ago

Just a note. This is what I do atm:

class WebFilter(forms.Form):
    def clean(self):
        cleaned_data = super(WebFilter, self).clean()
        try: defaults = self.WebMeta.defaults
        except: return cleaned_data
        for field_name in self.fields:
            html_name = self[field_name].html_name
            if not self.data.get(html_name, False):
                if field_name in defaults:
                    cleaned_data[field_name] = defaults[field_name]
                else:
                    del cleaned_data[field_name]
        return cleaned_data

This is a class that a form needs to inherit from if it wants to have filter-like behaviour (WebMeta is a special class for defining defaults). It wouldn't be needed with with default attribute on fields.

Version 2, edited 11 years ago by clime (previous) (next) (diff)
Note: See TracTickets for help on using tickets.
Back to Top