13

I have a model that looks like this:

class Invite(models.Model):
    user = models.ForeignKey(User)
    event = models.ForeignKey(Event)
    roles = models.ManyToManyField(Role, blank=True, null=True)
    sent =  models.BooleanField("Invite Sent", default=False, editable=False)
    created = models.DateTimeField(auto_now_add=True)

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

    class Meta:
        unique_together =(('user','event'),)


class Role(models.Model):
    """
    This associates a user's role to an event
    """
    event = models.ForeignKey(Event, related_name="roles")
    roletype = models.ForeignKey(RoleType)
    profiles = models.ManyToManyField(Profile, related_name="roles",
            blank=True, null=True)
    modified = models.DateTimeField(auto_now=True)
    created = models.DateTimeField(auto_now_add=True)

So whenever a new Event is created, a bunch of roles gets created along with it. In the Invite model, how can I only show the roles associated with the event I've selected in the change form within Django Admin instead of showing all the entries in the Role model?

okm
  • 23,575
  • 5
  • 83
  • 90
super9
  • 29,181
  • 39
  • 119
  • 172

4 Answers4

11

You probably want something along the lines of:

class InviteAdmin(admin.ModelAdmin):
    def get_form(self, request, obj=None, **kwargs):
        self.instance = obj # Capture instance before the form gets generated   
        return super(InviteAdmin, self).get_form(request, obj=obj, **kwargs)

    def formfield_for_manytomany(self, db_field, request=None, **kwargs):
        if db_field.name == 'role' and self.instance:
            # restrict role queryset to those related to this instance:         
            kwargs['queryset'] = self.instance.event.roles.all()
        return super(InviteAdmin, self).formfield_for_manytomany(
            db_field, request=request, **kwargs)

Django documentation for formfield_for_manytomany

DrMeers
  • 4,117
  • 2
  • 36
  • 38
  • 2
    The question clearly says "how can I only show the roles associated with the event I've **selected** in the **change form**" That is definitely to be done dynamical (ajax). Your solution is static. – Pannu Mar 23 '12 at 19:39
  • Ah, I missed that detail, thanks. I wouldn't necessarily call this a *static* solution as it varies based on the object loaded and event selected in the database, but I think you're right this doesn't solve the problem *actually* being presented :) – DrMeers Mar 23 '12 at 22:14
9

You want to dynamically filter the choices of the roles choices, so you will need ajax to perform this task.

Here's how you can make this work..

1: OnChange of the event send the event_id to your custom view through ajax.

2: From the Roles model filter based on the event_id you got from the ajax request and return the filtered roles by serializing into JSON.

3: Clear the existing roles and repopulate by parsing through the JSON response.

Eg: This a jquery getJSON example

javascript:

$("#event").change(function (){         
 var event_id = $(this).val();              
    $.getJSON("/my-app/my-roles-filter-view/"+ event_id +"/",function(data){
        var roles_dd = $("#roles");
        $('#event >option').remove();
        $.each(data, function(index,value) {
        roles_dd.append($("<option />").val(value).text(value));
    });                 
})(django.jquery);

url's:

('^/my-app/my-roles-filter-view/(?P<event_id>\d+)/$','my_view'),

views:

def my_view(request,event_id):
    qs = Role.objects.filter(event=event_id).values_list('id')
    return HttpResponse(simplejson.dumps(qs),mimetype='application/javascript')

This just an example using jquery you can use any type of ajax and achieve this.

Pannu
  • 2,547
  • 2
  • 23
  • 29
  • The solution needs java script because when he chooses the `event` in the form the roles must be filtered dynamically(without pushing a button, submitting the whole form and then coming back with the filtered choices) hence `ajax` is the way to go. – Pannu Mar 23 '12 at 19:45
  • Yes, if you want it to change in the browser when you change the selected event in the widget, then of course you need to use Javascript. Sorry, I misread. – DrMeers Mar 23 '12 at 22:16
1

You need to provide a custom formfield_for_foreignkey method in your admin class for the model.

This example (from the documentation that I linked), should get you started:

class MyModelAdmin(admin.ModelAdmin):
    def formfield_for_foreignkey(self, db_field, request, **kwargs):
        if db_field.name == "car":
            kwargs["queryset"] = Car.objects.filter(owner=request.user)
        return super(MyModelAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
Burhan Khalid
  • 169,990
  • 18
  • 245
  • 284
  • 1
    This will be done at load time and its not dynamic. I believe that the `choices` must be `filtered` dynamically. That`s what the question seems to about. – Pannu Mar 22 '12 at 18:42
  • @Pannu: No, this is actually close to the correct solution, but to solve this particular problem you need access to the Invite instance in question. – DrMeers Mar 23 '12 at 08:59
1

I believe there are two ways of solving this:

  1. ajax method as described by @Pannu

  2. non-ajax method which can be achieved by moving event field outside of change form (which means that there would be another form to change event) and filtering roles based on current event. I had to solve similar problem recently with limiting available options based on belonging of an object to specific site. Here is a description and code, if you are interested: http://source.mihelac.org/2011/09/8/django-sites-ext/

bmihelac
  • 6,233
  • 34
  • 45