rifaiz
rifaiz

Reputation: 39

How to change a ModelChoiceField using different QuerySet, after update of text input using jQuery?

I have a django reporting form , basically like the one here:

class EntryForm(ModelForm):
    year = forms.IntegerField()
    month = forms.ModelChoiceField(queryset = ... )
    report_category = form.ModelChoiceField(queryset = ... ) # FKey to some category model
    report_text = forms.CharField()

Initially, user would type in the year value into the text input box, select month, then select category and type the report.

now I want the month dropdown to be populated with the list of months that have no reports yet. After January 2014 report submitted, then next time the user type in the year 2014, the dropdown will be populated with only 11 months (minus January).

I know how to get the querysets done, but I'm still confused as to how to make jQuery/AJAX part for the change on dropdown, after the text input lose focus.

Upvotes: 1

Views: 3025

Answers (1)

rom
rom

Reputation: 3672

Here is a working example:

models.py

from django.db import models
#min and max values for integer fields
from django.core.validators import MinValueValidator, MaxValueValidator

class CategoryModel(models.Model):
    category = models.CharField(max_length=20, unique=True)

    def __unicode__(self):
        return u"%s" % self.category


class EntryModel(models.Model):
    year = models.IntegerField(validators=[MinValueValidator(1900), MaxValueValidator(2100)])
    month = models.IntegerField(validators=[MinValueValidator(1), MaxValueValidator(12)])
    report_category = models.ForeignKey(CategoryModel, blank=True, null=True, on_delete=models.SET_NULL)
    report_text = models.CharField(max_length=100, blank=True, null=True, default=None)

forms.py

from django import forms
from models import CategoryModel, EntryModel


class EntryForm(forms.ModelForm):
    months = (
        ('1','January'),
        ('2','February'),
        ('3','March'),
        ('4','April'),
        ('5','May'),
        ('6','June'),
        ('7','July'),
        ('8','August'),
        ('9','September'),
        ('10','October'),
        ('11','November'),
        ('12','December'),
    )

    month = forms.ChoiceField(choices=months)
    #hidden control used to populate the month drop down list
    month_hidden=forms.ChoiceField(choices=months)
    report_category = forms.ModelChoiceField(queryset = CategoryModel.objects.all())

    class Meta:
        model=EntryModel

urls.py

from django.conf.urls import patterns, url
from views import EntryCreateView


urlpatterns=patterns('so_23711764.views',
    url(r'^/?$', EntryCreateView.as_view(), name='display_entry'),
    url(r'^reload_controls.html$', 'reload_controls_view', name='reload_controls'),

)

views.py

# Create your views here.
from django.views.generic.edit import CreateView
from models import EntryModel
from forms import EntryForm
#use json for the ajax request
from django.http import HttpResponse
from django.utils import simplejson


class EntryCreateView(CreateView):
    model = EntryModel
    form_class=EntryForm
    template_name = "so_23711764/index.html"
    #~ fields = ['name']

#view called with ajax to reload the month drop down list
def reload_controls_view(request):
    context={}
    #get the year that the user has typed
    year=request.POST["year"]
    #get months without reports (months to be displayed in the drop down list)
    context["months_to_display"]=list(EntryModel.objects.filter(year=year, report_category__isnull=True).values_list('month', flat=True).distinct())

    return HttpResponse(simplejson.dumps(context), mimetype="application/json")

index.html

<!-- css of the application -->
<link rel="stylesheet" href="{{STATIC_URL}}css/so_23711764.css?{% now 'U' %}" />

<form id="entry_form" method="post" action="{% url display_entry %}">
    {% csrf_token %}

    {{ form.as_p }}

    <!-- in urls.py, set the path to the view reload_controls -->
    <div id="reload_controls_view" style="display: none;">{% url reload_controls %}</div>
</form>


<!-- jquery -->
<script type="text/javascript" src="{{ STATIC_URL }}jquery.min.js"></script>
<!-- csrf file to avoid 403 (FORBIDDEN) on ajax views -->
<script type="text/javascript" src="{{ STATIC_URL }}csrf.js"></script>
<!-- js related to the application -->
<script type="text/javascript" src="{{ STATIC_URL }}js/so_23711764.js?{% now 'U' %}"></script>

so_23711764.js

/* bind the event to the form, so it still works after controls are reloaded with ajax */
$('#entry_form').on('blur', '#id_year', function()
{
    reload_controls(this.value);
});


/* update the month drop down list with ajax */
function reload_controls(year)
{
    $.ajax
    ({
        type: 'POST',
        url: $("#reload_controls_view").text(),
        dataType: 'json',
        data: "year="+year,
        success: function(result)
        {
            //empty month drop down list
            $("#id_month").empty()

            //add months with no report
            $.each( result.months_to_display, function( index, value )
            {
                //use the hidden drop down list to populate the month field
                month=$("#id_month_hidden option[value='" + value + "']").text()
                //add months to the drop down list
                $('#id_month').append('<option value="'+value+'">'+month+'</option>')
            });
        },
        error: function(xhr, status, error) 
        {
            window.console&&console.log(xhr.responseText);
        }
    });
}

so_23711764.css

/* hide month_hidden control */
#id_month_hidden, label[for="id_month_hidden"]
{
    display: none;
}

Now, If I access the page http://127.0.0.1:8000/so_23711764/ (django development server), I get:

enter image description here

With this entrymodel table:

enter image description here

If I type "2014" for the year, when the control loses the focus, I get :

enter image description here

Hope it helps :).

Upvotes: 3

Related Questions