3

I have that situation: I have models Item, Region and Country.

class Item(models.Model):
    name = models.CharField(max_length=255)
    alias = models.SlugField(unique=True)
    country = models.OneToOneField(Country, default=0)
    region = models.OneToOneField(Region, default=0, related_name='')

class Region(models.Model):
    name = models.CharField(max_length=100)
    country = models.ForeignKey(Country, default=0) 

class Country(models.Model):
    name = models.CharField(max_length=100)

When I add an item in the admin area and I select country I want to automatically build the region select with Regions only from the selected Country.

I know how to do it in javascript, but i don't know how CORRECT it is do that in Django.

Timmy O'Mahony
  • 53,000
  • 18
  • 155
  • 177
yAnTar
  • 4,269
  • 9
  • 47
  • 73
  • You will need to use ajax to do this: when the value of the first select change (countries), do an ajax request for all regions in that country and filter the second select with these values. – Timmy O'Mahony Mar 29 '12 at 10:38
  • I know, but i have another question - how its to do right on Django. I can insert javascript code in template for admin area and generate another select. $('#select1').change(function(){ $,ajax({ }) }) – yAnTar Mar 29 '12 at 12:05
  • 1
    There are some django packages like http://code.google.com/p/django-ajax-filtered-fields/ and https://github.com/twidi/django-ajax-select you can try these – Pannu Mar 29 '12 at 14:59
  • Also [django-smart-selects](https://github.com/digi604/django-smart-selects) look good – ilvar Mar 30 '12 at 02:14
  • I Followed what is described in this question but I'm not sure I got all the necessary steps: For the quick selection of a country from a region, have I to add also some javascript code? – Safari Apr 07 '15 at 10:28

3 Answers3

2

You need to override the clean method of your admin form:

def clean(self):
    super(CustomItemForm, self).clean() #if necessary
    if 'region' in self._errors:
        """     
        reset the value (something like this i 
        think to set the value b/c it doesnt get set 
        b/c the field fails validation initially)
        """
        region = Region.objects.get(pk=self.data['region'])
        self.initial['region'] = region.id
        self.cleaned_data['region'] = region
        self.region = region

        # remove the error
        del self._errors['region']

    return self.cleaned_data 
Francis Yaconiello
  • 10,829
  • 2
  • 35
  • 54
1

If you're having trouble having django accept the value you selected, because it doesn't think it should have been there in the first place, have the queryset refer to every possible value, but then override the "choices" attribute with an empty set. The latter part will avoid django construct a huge option list that is just going to be overridden by the dynamic javascript.

I know this isn't drop-in-able for the admin screens, but I found this page while looking to do a similar thing for non-admin, so I thought I'd answer it here, too. Here's what I'm using:

in views.py:

from django.shortcuts import render_to_response
from django import forms
from django.template import RequestContext
from django.http import HttpResponseRedirect, HttpResponse
from django.core.urlresolvers import reverse
from django.core import serializers
# other imports

class AddRouteForm ( forms.Form ):
    router = forms.ModelChoiceField ( queryset=Router.objects.all() )
    peer = forms.ModelChoiceField ( queryset=Peer.objects.all() )
    peer.choices = []
    # other stuff not relevant

def get_peers ( request ):
    router_id = request.GET['router_id']
    router = Router.objects.get(id=router_id)
    data = serializers.serialize ( "json", router.peer_set.filter(transit_capable=True) )
    return HttpResponse ( data, content_type="text/json" )

def add_route ( request ):
    if request.method == "POST":
        form = AddRouteForm ( request.POST )
        if form.is_valid ():
            # TODO something here
            return HttpResponseRedirect ( reverse ( "staticroute.views.index" ) )
    else:
        form = AddRouteForm ()
    return render_to_response ( "staticroute/add_route.html", locals(), RequestContext(request) )

In the html file (jQuery needs to be loaded too):

<form method="POST">{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Add Route" />
</form>
<script>
    $("#id_router").change ( function () {
        var router_id = $('#id_router').val();

        var items = [];
        items.push ( '<option value="" selected="selected">---------</option>' );
        $("#id_peer").html ( items.join('') );

        if ( router_id != "" ) {
            $.getJSON ( '{% url staticroute.views.get_peers %}', {router_id:router_id}, function(data) {
                $.each ( data, function ( index, val ) {
                    items.push ( '<option value="' + val.pk + '">' + val.fields.description + ' (' + val.fields.address + ')</option>');
                } );
                $("#id_peer").html ( items.join('') );          
            } );
        }

    } ).change();
</script>
Ch'marr
  • 1,284
  • 11
  • 8
0

I solved that problem not with proposed libraries (with simple javascript), but exist another problem: when i changed country - select region changes automatically, but i can't save new region value (when another region from the same country - not problem), because error Select a valid choice. That choice is not one of the available choices.

Models are the same in first question, but default value for model country equal 1.

My first way was change country select through formfield_key function,

def formfield_for_foreignkey(self, db_field, request, **kwargs):
    if db_field.name == 'region':
        kwargs["queryset"] = Region.objects.filter(country=self.country)
        return db_field.formfield(**kwargs)

but i don't know is the saving objects, or editing objects.

Here wrote - the best way is changing through form and now i have code:

class Item(models.Model):
    country = models.ForeignKey(Country, default=1)
    region = models.ForeignKey(Region, related_name='')


class ItemAdmin(admin.ModelAdmin):
    form = CustomItemForm
    prepopulated_fields = {"alias": ("name",)}
    list_filter = ('country', 'category',)

class CustomItemForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        try:
            country = kwargs['instance'].country
        except KeyError:
            country = 1 
        super(CustomItemForm, self).__init__(*args, **kwargs)
        self.fields['region'].queryset = Region.objects.filter(country=country)
Steffen Moritz
  • 7,277
  • 11
  • 36
  • 55
yAnTar
  • 4,269
  • 9
  • 47
  • 73
  • I just solved this problem in one of my apps yesterday, see my answer below – Francis Yaconiello Apr 05 '12 at 13:41
  • also, in your `__init__` method, if `kwargs['instance']` isnt set and throws an exception `country = 1` will throw an error in the `Region.objects.fitler(country=country)` line of code b/c filter expects an object instance not an int. just FYI that could fail – Francis Yaconiello Apr 05 '12 at 13:52