views:

248

answers:

2

Hi,

I'm using a custom MM/YY field and widget based on this example. I want to iterate over the individual month and year options defined in the widget class in order to apply "selected='selected'" to the MM/YY value that corresponds with the MM/YY value stored in the database. This seems like such a messy way of doing this, so if you have any better ideas please post them here.

class MonthYearWidget(forms.MultiWidget):
    def __init__(self, attrs=None):
        months = (
            ('01', 'Jan (01)'),
            ('02', 'Feb (02)'),
            ('03', 'Mar (03)'),
            ('04', 'Apr (04)'),
            ('05', 'May (05)'),
            ('06', 'Jun (06)'),
            ('07', 'Jul (07)'),
            ('08', 'Aug (08)'),
            ('09', 'Sep (09)'),
            ('10', 'Oct (10)'),
            ('11', 'Nov (11)'),
            ('12', 'Dec (12)'),
        )

        year = int(datetime.date.today().year)
        year_digits = range(year, year+10)
        years = [(year, year) for year in year_digits]

        widgets = (forms.Select(attrs=attrs, choices=months), forms.Select(attrs=attrs, choices=years))
        super(MonthYearWidget, self).__init__(widgets, attrs)

    def decompress(self, value):
        if value:
            return [value.month, value.year]
        return [None, None]

    def render(self, name, value, attrs=None):
        try:
            value = datetime.date(month=int(value[0]), year=int(value[1]), day=1)
        except:
            value = ''
        return super(MonthYearWidget, self).render(name, value, attrs)

class MonthYearField(forms.MultiValueField):
    def __init__(self, *args, **kwargs):
        forms.MultiValueField.__init__(self, *args, **kwargs)
        self.fields = (forms.CharField(), forms.CharField(),)
    def compress(self, data_list):
        if data_list:
            return datetime.date(year=int(data_list[1]), month=int(data_list[0]), day=1)
        return datetime.date.today()

and then here's where I'm stuck, in the template. I can't figure out what the name of the iterable list for the months and years is (if there is one). Finding that iterable list is the problem; I already plan to use an ifequal statement to determine which option the "selected='selected'" should apply to. I have only tried to iterate over the months so far.

<form action="#" method="POST">
{% csrf_token %}
    <p>{{ form.from_email.label_tag }}:  {{ form.from_email }}</p>
    <p>{{ form.working_month.label_tag }}:
        <select name="working_month_0" id="id_working_month_0">
        {% for i in form.working_month.data_list %}
            <option value="{{ i }}">{{ option.from_email }}</option>
        {% endfor %}
        </select>
    <p><input type="submit" value="Change Settings Now" /></p>
</form>

Thanks in advance for any guidance you all can provide.

EDIT: Here is the generic view:

def option_edit(request,option_id):
    try:
        option = Option.objects.get(pk=option_id)
    except Option.DoesNotExist:
        raise Http404

    return create_update.update_object(
        request,
        form_class = OptionForm,
        template_name = 'options.html',
        template_object_name = 'option',
        object_id = option_id,
        post_save_redirect = '/some/address/' + option_id + '/edit/'
    )

... and the form class:

class OptionForm(ModelForm):
    class Meta:
        model = Option
    working_month = MonthYearField(widget=MonthYearWidget)

I think the model is relevant, too:

class Option(models.Model):
    from_email = models.EmailField()
    working_month = models.DateField()

Do I have to create a custom model field in addition to the custom form field, or can I use this setup?

+2  A: 

The magic of django forms is that you don't need to do all that. By calling the form's select field by name, it will render it and select the right option as based on initial/instance data passed into the form on instantation.

{{form.working_month}}

If you're still having troubles, can you post the form class as well?

Good luck!

EDIT

In looking at the first comment on the link you posted, this issue is addressed. The commenter included this code

def __init__(self, *args, **kwargs):
  forms.MultiValueField.__init__(self, *args, **kwargs)
  self.fields = (forms.CharField(), forms.CharField(),)
czarchaic
Well, the problem is that {{ form.working_month }} always displays the first option, January, 2009, even though the actual MM/YY data in the database is August 2010. I have posted the generic view I am using as well as the form class. Thanks.
John
Are you populating the form with an instance? If so, I think the issue is with your overridden render() method-- do you need it?
Tom
Do the other form fields render with correct initial data?
czarchaic
Form fields in other models, as well as {{form.from_email}} in this model, are rendering with correct initial data. And doesn't passing object_id to the generic view populate the form with an instance? I just removed the overridden render() method, but {{form.working_month}} still populates with the first available options and not with the correct data.
John
@John Please see the edit above.
czarchaic
Thanks, but I have already included that __init__ definition in the MonthYearField class as shown above.
John
UPDATE: Now that I removed the render method, the year is showing up correctly, but the month is still showing as January when it fact it is not.
John
I think you should put the render back in (temporarily) and print to the console (or other debug method) if your get to the exception.
czarchaic
Good call. I removed the try statement and the else block and got: "Caught an exception while rendering: 'datetime.date' object is unsubscriptable". Looking for a solution now.
John
Hmm... now I've got "value = datetime.date(month=3, year=2009, day=1)", just to test, and {{ form.working_month }} seems to be totally ignoring the month, but the year is passed as 2009. Same problem as I have without the render() method. Something must be wrong in passing the month.
John
Also, there is a strange behavior occuring: when I select, say, 2015 or 2017 and submit the form to #, the form displays the year as 2012 - until I refresh the page, at which point it displays the correct year stored in the database.
John
The form **always** displays 2012?
czarchaic
Maybe changing the choices in the months to integers `((1,'Jan (01)'),(2, 'Feb (02)'))`
czarchaic
AHA, solved! All that needed to be done was 1) change the choices to integers, as you said, and 2) remove the leading zeros on moths 1 through 9. See this question for additional commentary on why the zeros needed to be removed: http://stackoverflow.com/questions/336181/python-invalid-token Thank you!
John
Cool, glad you got it solved.
czarchaic