1

I have a Contract class where contract_mod allows to extend a contract from a previous one. contract_mod should only show contracts related with the person that we selected previously. The Contract class returns the field person. As I've never work with AJAX/jQuery I don't know where to start.

So, my goal is that the field contract_mod depends on hte field person (using the Admin interface).

class Contract(models.Model):
    person        = models.ForeignKey(Person) #person hired
    contract_mod  = models.OneToOneField('self', blank = True, null = True) #allows to extend a contract
        ...

As the field contract_mod is OneToOneField I can't use django-smart-selects or django-ajax-select

loar
  • 1,505
  • 1
  • 15
  • 34

1 Answers1

0

In a similar situation I did the following (now adapted to your model):

models

class Person(models.Model):
    name = models.CharField(max_length=20)
    def __unicode__(self):
        return self.name
    def get_name(self):
        return self.name

class Contract(models.Model):
    person        = models.ForeignKey(Person) #person hired
    contract_mod  = models.OneToOneField('self', blank = True, null = True)
    contract_name = models.CharField(max_length=20) #just for testing

    def __unicode__(self):
        return self.get_name() + " " +self.contract_name
    def get_name(self):
        return self.person.get_name() #to make sure you get the person name in the admin
    def contract_mod_name(self):
        if self.contract_mod:
            return self.contract_mod.contract_name
        else:
            return ""

admin

class SelectField(forms.ChoiceField):
    def clean(self, value):
        return value

class CForm(forms.ModelForm):
    contracts_from_selected = SelectField()
    class Meta:
        model = Contract
        widgets = { 'contract_mod' : forms.widgets.Select(attrs={'hidden' : 'true'}) }

class CAdmin(admin.ModelAdmin):
    form = CForm
    list_display = ('contract_name','get_name','contract_mod_name')#what you like
    def save_model(self, request, obj, form, change):
        if request.POST.get('contracts_from_selected'):
            obj.contract_mod=Contract.objects.get(id=int(request.POST.get('contracts_from_selected')))

        obj.save()

Override the change_form.html template (by copying it from the django/contrib/admin/templates/admin into your yourapp/templates/admin/yourapp directory) and add the following Javascript:

$(function () {
    $("#id_person").change(function () {    
        var options = $("#id_contract_mod option").filter(function () {
            return $(this).html().split(" ")[0] === $("#id_person   option:selected").html();
        }).clone();
        $("#id_contracts_from_selected").empty();
        $("#id_contracts_from_selected").append(options);
    });
});

One shortcoming is, that this uses the visible html entry to store the person - contract relation. So the person is also visible in the dropdown. To avoid this you could add an attribute to the options instead - see here: Django form field choices, adding an attribute

Yes and it would be good to completely hide the contract_mod ChoiceField. Via the hidden=True in the widget only the dropdown is hidden.

Community
  • 1
  • 1
ger.s.brett
  • 3,267
  • 2
  • 24
  • 30
  • It doesn't show any error but doesn't work yet. As I'm using Grappelli I take the change_form from its [directory](https://github.com/sehmaschine/django-grappelli/blob/master/grappelli/templates/admin/change_form.html) and I'm adding the function in the part of Javascript (should I put it in somewhere else?) Anyway, glad to know I'm not alone :) – loar Feb 23 '15 at 09:41
  • If, I don't hide the widget, I can see it empty. @ger.s.brett – loar Feb 23 '15 at 11:46
  • If it is empty the Javascript probably does not work. Can you check if the browser finds the Javascript (e.g. with Firebug or depending on what browser you use)? I have not worked with Grapelli (yet). I had put it on the bottom of the template just before the final {% endblock %} statement. – ger.s.brett Feb 23 '15 at 13:19
  • It works but shows the name in the field contracts_from_selected not in contract_mod – loar Feb 27 '15 at 12:42
  • Yes that is the idea behind it. The valid selection options are loaded into the contracts_from_selected formfield. – ger.s.brett Feb 27 '15 at 14:02
  • Last thing: In CAdmin I want to get the attribute contracts_from_selected from CForm in order to use it in a fieldset. Is there any way to implemt a getter method? I'm trying with `return CForm.contracts_from_selected` and things like that without any results and I don't find documentation... Any suggestion? – loar Mar 03 '15 at 14:45
  • The id of the selected contract you can get as shown above: Contract.objects.get(id=int(request.POST.get('contracts_from_selected'))). Is that what you need? – ger.s.brett Mar 03 '15 at 15:51
  • I didn't explain well. What I meant is that in admin.py, when I'm creating a fieldset with the fields that I want to be shown, I have to put the contracts_from_selected in order to see the results in the admin interface of Contract. `fieldsets = [ [ None, { "fields" : [ ("contracts_from_selected") ] } ]` But if I put it there I get an error so I guess I'll need a getter in CAdmin to import the values of contract_from_selected. Thanks for your patience. – loar Mar 03 '15 at 16:13
  • One quick thought: you could define the contracts_from_selected in your model and leave it empy (null=True). Then you can use it in your fieldset. You only have to make sure that you set it always to Null when you save the model! – ger.s.brett Mar 03 '15 at 19:57