2011-02-13 4 views
4

Je veux afficher ManyToManyField s dans admin, tout comme filter_horizontal fait, mais remplir les options que l'utilisateur tape dans le champ de filtre. Il y a beaucoup d'options et les charger toutes en même temps prend beaucoup de temps.Django: AJAX ManyToManyField dans admin

J'ai trouvé django-ajax-filtered-fields mais il me semble être trop exigeant car cela nécessite des changements dans les classes de modèles, alors que tout ce que je veux faire est de remplacer chaque champ de sélection multiple dans un formulaire. Écrire un champ de widget personnalisé qui hérite de admin.widgets.FilteredSelectMultiple semble être le bon moyen. Donc, j'essaie de rouler mon propre widget:

class MultiSelectWidget(FilteredSelectMultiple): 
    class Media: 
     # here should be some js to load options dynamically 
     js = (
      "some_js_to_load_ajax_options.js", 
     ) 

    def render_options(self, choices, selected_choices): 
     # this initializes the multiple select without any options 
     choices = [c for c in self.choices if str(c[0]) in selected_choices] 
     self.choices = choices 
     return super(MultiSelectWidget, 
        self).render_options([], selected_choices) 

class MyAdminForm(forms.ModelForm): 
    def __init__(self, *args, **kwargs): 
     super(MyAdminForm, self).__init__(*args, **kwargs) 
     self.fields['m2m_field'].widget = MultiSelectWidget('m2m_field', is_stacked=False) 
    class Meta: 
     model = MyModel 

class MyAdmin(admin.ModelAdmin): 
    form = MyAdminForm 

qui rend correctement.

Mais je ne suis pas sûr comment implémenter cette partie ajax some_js_to_load_ajax_options.js. Dois-je écrire mon propre extrait jQuery ou modifier SelectFilter2 qui est livré avec admin/media/js? Quelqu'un a-t-il déjà été là?

modifier: Bien que pas lié au cœur de la question, que je ne veux remplacer le widget de champ, le chemin le plus court est d'utiliser formfield_overrides:

class MultiSelectWidget(FilteredSelectMultiple): 
    # as above 

class MyAdmin(admin.ModelAdmin): 
    formfield_overrides = { 
     models.ManyToManyField: {'widget': MultiSelectWidget}, 
    } 

Répondre

0

Je serait pirater le filtre de sélection , il a un bon ensemble de fonctions que vous pouvez utiliser.

+1

pouvez-vous être plus précis? – omat

4

J'ai commencé à partir de votre code et j'ai utilisé un javascript personnalisé pour récupérer des valeurs de photologue Modèle photo; Veuillez noter que j'utilise grappelli et l'URL de Django qui obtient l'objet json est codé en dur; aussi le domaine dans mon modèle est appelé « photos »:

# urls.py 
url(r'^get_json_photos/(?P<query>[\w-]+)/$', 'catalogo.views.get_json_photos', name='get_json_photos'), 


# views.py  
from photologue.models import Photo 
from django.utils import simplejson as json 

def get_json_photos(request, query): 
    photos = Photo.objects.filter(title__icontains=query)[:20] 
    p = [ {"name":photo.title, "id":photo.id} for photo in photos ] 
    response = json.dumps(p) 
    return HttpResponse(response, mimetype="application/json") 


# admin.py 
from django.conf import settings 
from django.contrib.admin.widgets import FilteredSelectMultiple 

class MyFilteredSelectMultiple(FilteredSelectMultiple): 

    class Media: 
     js = (settings.ADMIN_MEDIA_PREFIX + "js/core.js", 
       settings.ADMIN_MEDIA_PREFIX + "js/SelectBox.js", 
       settings.ADMIN_MEDIA_PREFIX + "js/SelectFilter2.js", 
       settings.MEDIA_URL + "js/ajax_photo_list.js") 


class MyModelMultipleChoiceField(ModelMultipleChoiceField): 

    def clean(self, value): 
     return [val for val in value] 


class GalleryForm(forms.ModelForm): 
    photos = MyModelMultipleChoiceField(queryset=Photo.objects.none(), required=False, 
     widget=MyFilteredSelectMultiple(verbose_name="photos", is_stacked=False)) 

    def __init__(self, *args, **kwargs): 
     super(GalleryForm, self).__init__(*args, **kwargs) 
     try: 
      i = kwargs["instance"] 
      gallery = Gallery.objects.get(pk=i.pk) 
      qs = gallery.photos.all() 
     except: 
      qs = Photo.objects.none() 
     self.fields['photos'].queryset = qs 

    class Meta: 
     model = Gallery 
     widgets = { 
      'photos': MyFilteredSelectMultiple(verbose_name="photos", is_stacked=False) 
     } 


class GalleryAdmin(admin.ModelAdmin): 
    list_display = ('title', 'date_added', 'photo_count', 'is_public') 
    list_filter = ['date_added', 'is_public'] 
    date_hierarchy = 'date_added' 
    prepopulated_fields = {'title_slug': ('title',)} 
    filter_horizontal =() 
    form = GalleryForm 


# ajax_photo_list.js 
(function($){ 
$("#id_photos_input").live("keyup", function(){ 
    var querystring = $("#id_photos_input").val(); 
    if (querystring) { 
     $.ajax ({ 
      type: "GET", 
      url: "/get_json_photos/"+querystring+"/", 
      cache: false, 
      success: function(json) { 
       if (json) { 
        var list_from = $("#id_photos_from option").map(function() { 
         return parseInt($(this).val()); 
        }); 
        var list_to = $("#id_photos_to option").map(function() { 
         return parseInt($(this).val()); 
        }); 
        for (var pid in json) { 
         if ($.inArray(json[pid].id, list_from) == -1 && $.inArray(json[pid].id, list_to) == -1) { 
          $("#id_photos_from").prepend("<option value='"+json[pid].id+"'>"+json[pid].name+"</option>"); 
         } 
        } 
        SelectBox.init('id_photos_from'); 
        SelectBox.init('id_photos_to'); 
       } 
      } 
     }); 
    } 
}) 
}(django.jQuery)); 

Je pense à la rendre générique, puisque n'est pas la première fois que j'ai ce problème,

+0

+1 pour ces deux lignes que j'ai ajoutées à ma sélection select ajax 'code'SelectBox.init ('id_photos_from'); SelectBox.init ('id_photos_to'); 'code' – elsadek

+0

votre relation plusieurs à plusieurs est galerie-photo? Dans la galerie/modèle, vous avez un champ "photo" avec multiselect? Est-ce dans models.py: class GalleryForm (forms.ModelForm):? – Timo

0

Si l'interface utilisateur de Select2 appels Pour vous, vous pouvez utiliser Django-Select2 dans l'Admin.

Pour m2m il pourrait fonctionner comme vous suggéré:

class MyAdmin(admin.ModelAdmin): 
    formfield_overrides = { 
     models.ManyToManyField: {'widget': ModelSelect2MultipleWidget}, 
    } 

    # required to make jquery available to select2 
    # has to be loaded via Admin class (and not via widget or form class) for correct order in output 
    class Media: 
     js = ("ext/js/jquery.min.js",) 

Ajax fonctionne en ajoutant le modèle d'URL suivante pour urls.py:

# if using ModelWidget 
url(r'^select2/', include('django_select2.urls')), 

Bien sûr, vous pouvez également fournir vos propres implémentations de vue, voir la documentation liée ci-dessus.

Je ne l'utilise pas actuellement pour m2m mais pour les relations de clés étrangères inverses, donc je l'utilise sous une forme personnalisée dans l'admin Django, instanciant explicitement le widget. Ainsi, dans le cas où il ne fonctionne pas avec formfield_overrides, le long chemin serait une option.