web-dev-qa-db-fra.com

Django: formulaire de recherche dans ListView basé sur des classes

J'essaie de réaliser un Class Based ListView qui affiche une sélection d'un ensemble de tables. Si le site est demandé pour la première fois, le jeu de données devrait être affiché. Je préférerais une soumission POST, mais GET convient également.

C’est un problème facile à manipuler avec function based views, mais avec des vues basées sur les classes, j’ai du mal à comprendre.

Mon problème est que je reçois un certain nombre d’erreurs, dues à ma compréhension limitée des vues classées. J'ai lu diverses documentations et je comprends les vues pour les requêtes directes, mais dès que je souhaite ajouter un formulaire à l'instruction de requête, je rencontre une erreur différente. Pour le code ci-dessous, je reçois un ValueError: Cannot use None as a query value.

Quelle serait la procédure de travail recommandée pour un ListView basé sur une classe en fonction des entrées de formulaire (sinon, sélection de la base de données entière)?

Ceci est mon exemple de code:

models.py

class Profile(models.Model):
    name = models.CharField(_('Name'), max_length=255)

    def __unicode__(self):
        return '%name' % {'name': self.name}

    @staticmethod
    def get_queryset(params):

        date_created = params.get('date_created')
        keyword = params.get('keyword')
        qset = Q(pk__gt = 0)
        if keyword:
            qset &= Q(title__icontains = keyword)
        if date_created:
            qset &= Q(date_created__gte = date_created)
        return qset

forms.py

class ProfileSearchForm(forms.Form):
    name = forms.CharField(required=False)

views.py

class ProfileList(ListView):
    model = Profile
    form_class = ProfileSearchForm
    context_object_name = 'profiles'
    template_name = 'pages/profile/list_profiles.html'
    profiles = []


    def post(self, request, *args, **kwargs):
        self.show_results = False
        self.object_list = self.get_queryset()
        form = form_class(self.request.POST or None)
        if form.is_valid():
            self.show_results = True
            self.profiles = Profile.objects.filter(name__icontains=form.cleaned_data['name'])
        else:
            self.profiles = Profile.objects.all()
        return self.render_to_response(self.get_context_data(object_list=self.object_list, form=form))

    def get_context_data(self, **kwargs):
        context = super(ProfileList, self).get_context_data(**kwargs)
        if not self.profiles:
            self.profiles = Profile.objects.all()
        context.update({
            'profiles': self.profiles
        })
        return context

Ci-dessous, j'ai ajouté le FBV qui fait le travail. Comment puis-je traduire cette fonctionnalité en CBV? Cela semble si simple dans les vues basées sur les fonctions, mais pas dans les vues basées sur les classes.

def list_profiles(request):
    form_class = ProfileSearchForm
    model = Profile
    template_name = 'pages/profile/list_profiles.html'
    paginate_by = 10

    form = form_class(request.POST or None)
    if form.is_valid():
        profile_list = model.objects.filter(name__icontains=form.cleaned_data['name'])
    else:
        profile_list = model.objects.all()

    paginator = Paginator(profile_list, 10) # Show 10 contacts per page
    page = request.GET.get('page')
    try:
        profiles = paginator.page(page)
    except PageNotAnInteger:
        profiles = paginator.page(1)
    except EmptyPage:
        profiles = paginator.page(paginator.num_pages)

    return render_to_response(template_name, 
            {'form': form, 'profiles': suppliers,}, 
            context_instance=RequestContext(request))
23
neurix

Je pense que votre objectif est d'essayer de filtrer le groupe de requêtes en fonction de la soumission du formulaire, le cas échéant, à l'aide de GET: 

class ProfileSearchView(ListView)
    template_name = '/your/template.html'
    model = Person

    def get_queryset(self):
        try:
            name = self.kwargs['name']
        except:
            name = ''
        if (name != ''):
            object_list = self.model.objects.filter(name__icontains = name)
        else:
            object_list = self.model.objects.all()
        return object_list

Ensuite, tout ce que vous avez à faire est d’écrire une méthode get pour restituer le modèle et le contexte.

Peut-être pas la meilleure approche. En utilisant le code ci-dessus, vous n'avez pas besoin de définir un formulaire Django.

Voici comment cela fonctionne: les vues basées sur les classes séparent sa manière de rendre le modèle, de traiter le formulaire, etc. Comme get gère la réponse GET, post gère la réponse POST, get_queryset et get_object est explicite, et ainsi de suite. Le moyen le plus simple de connaître la méthode disponible, démarrez un shell et tapez:

from Django.views.generic import ListView si vous voulez en savoir plus sur ListView

puis tapez dir(ListView). Là vous pouvez voir toute la méthode définie et aller visiter le code source pour le comprendre. La méthode get_queryset utilisée pour obtenir un ensemble de requêtes. Pourquoi ne pas simplement le définir comme ça, ça marche aussi: 

class FooView(ListView):
    template_name = 'foo.html'
    queryset = Photo.objects.all() # or anything

Nous pouvons le faire comme ci-dessus, mais nous ne pouvons pas faire de filtrage dynamique en utilisant cette approche. En utilisant get_queryset nous pouvons faire du filtrage dynamique, en utilisant n'importe quelle donnée/valeur/information dont nous disposons, cela signifie que nous pouvons aussi utiliser le paramètre name envoyé par GET, et qu'il est disponible sur kwargs, ou dans ce cas sur self.kwargs["some_key"]some_key est quelconque paramètre que vous avez spécifié

39
Otskimanot Sqilal

Eh bien, je pense que laisser la validation pour former est une bonne idée. Cela ne vaut peut-être pas la peine dans ce cas particulier, car il s’agit d’un formulaire très simple - mais c’est certain, avec un formulaire plus compliqué (et peut-être que le vôtre grandira également), je ferais donc quelque chose comme:

class ProfileList(ListView):
    model = Profile
    form_class = ProfileSearchForm
    context_object_name = 'profiles'
    template_name = 'pages/profile/list_profiles.html'
    profiles = []


    def get_queryset(self):
        form = self.form_class(self.request.GET)
        if form.is_valid():
            return Profile.objects.filter(name__icontains=form.cleaned_data['name'])
        return Profile.objects.all()
11
jasisz

Ceci a été bien expliqué sur le sujet des vues génériques ici Filtrage dynamique

Vous pouvez filtrer à travers GET, je ne pense pas que vous puissiez utiliser la méthode POST pour cela, car ListView n'est pas hérité des mélanges d'édition.

Ce que vous pouvez faire c'est:

urls.py

urlpatterns = patterns('', 
                (r'^search/(\w+)/$', ProfileSearchListView.as_view()),
              )

views.py

class ProfileSearchListView(ListView):
    model = Profile
    context_object_name = 'profiles'
    template_name = 'pages/profile/list_profiles.html'
    profiles = []

    def get_queryset(self):
         if len(self.args) > 0:
               return Profile.objects.filter(name__icontains=self.args[0])
         else:
               return Profile.objects.filter()
2
Rohan

Je pense que l'erreur que vous obtenez est parce que votre formulaire ne nécessite pas le champ Nom. Ainsi, bien que le formulaire soit valide, les données nettoyées pour votre champ name sont vides.

Celles-ci pourraient être les lignes problématiques:

if form.is_valid():
    self.show_results = True
    self.profiles = Profile.objects.filter(name__icontains=form.cleaned_data['name'])

Si j'étais vous, j'essaierais de changer de ligne:

self.profiles = Profile.objects.filter(name__icontains=form.cleaned_data['name'])

pour ça:

self.profiles = Profile.objects.none()

Si vous arrêtez de recevoir des erreurs (et que votre modèle reçoit un object_list vide), le problème que vous rencontrez est ce que j'ai déjà dit: nom du champ non requis.

Faites-nous savoir si cela ne fonctionne pas!

1
marianobianchi

Je pense que vous feriez mieux de le faire via get_context_data. Créez manuellement votre formulaire HTML et utilisez GET pour récupérer ces données. Un exemple de quelque chose que j'ai écrit est ci-dessous. Lorsque vous soumettez le formulaire, vous pouvez utiliser les données get pour les renvoyer via les données de contexte. Cet exemple n'est pas adapté à votre demande, mais il devrait aider les autres utilisateurs.

def get_context_data(self, **kwargs):
    context = super(Search, self).get_context_data(**kwargs)
    filter_set = Gauges.objects.all()
    if self.request.GET.get('gauge_id'):
        gauge_id = self.request.GET.get('gauge_id')
        filter_set = filter_set.filter(gauge_id=gauge_id)

    if self.request.GET.get('type'):
        type = self.request.GET.get('type')
        filter_set = filter_set.filter(type=type)

    if self.request.GET.get('location'):
        location = self.request.GET.get('location')
        filter_set = filter_set.filter(location=location)

    if self.request.GET.get('calibrator'):
        calibrator = self.request.GET.get('calibrator')
        filter_set = filter_set.filter(calibrator=calibrator)

    if self.request.GET.get('next_cal_date'):
        next_cal_date = self.request.GET.get('next_cal_date')
        filter_set = filter_set.filter(next_cal_date__lte=next_cal_date)

    context['gauges'] = filter_set
    context['title'] = "Gauges "
    context['types'] = Gauge_Types.objects.all()
    context['locations'] = Locations.objects.all()
    context['calibrators'] = Calibrator.objects.all()
    # And so on for more models
    return context
0
Sherd

Rechercher dans tous les champs du modèle

class SearchListView(ItemsListView):

# Display a Model List page filtered by the search query.

def get_queryset(self):
    fields = [m.name for m in super(SearchListView, self).model._meta.fields]
    result = super(SearchListView, self).get_queryset()
    query = self.request.GET.get('q')
    if query:
        result = result.filter(
            reduce(lambda x, y: x | Q(**{"{}__icontains".format(y): query}), fields, Q())
        )
    return result

Cette approche est similaire à celle de @jasisz, mais plus simple.

class ProfileList(ListView):
    template_name = 'your_template.html'
    model = Profile

    def get_queryset(self):
        query = self.request.GET.get('q')
        if query:
            object_list = self.model.objects.filter(name__icontains=query)
        else:
            object_list = self.model.objects.none()
        return object_list

Ensuite, tout ce que vous avez à faire sur le modèle html est:

<form method='GET'>
  <input type='text' name='q' value='{{ request.GET.q }}'>
  <input class="button" type='submit' value="Search Profile">
</form>
0
supertramp