Estoy un poco confundido con las vistas genéricas de Django. Como se muestra en aquí estamos convirtiendo vistas personalizadas en genéricas puntos de vista. Y aunque entiendo lo que sucede en DetailView y ResultsView, no entiendo completamente cómo esto:

def index(request):
    latest_question_list = Question.objects.order_by('-pub_date')[:5]
    template = loader.get_template('polls/index.html')
    context = {
        'latest_question_list': latest_question_list,
    }
    return render(request, 'polls/index.html', context)

Se convierte en esto:

class IndexView(generic.ListView):
    template_name = 'polls/index.html'
    context_object_name = 'latest_question_list'

    def get_queryset(self):
        """Return the last five published questions."""
        return Question.objects.order_by('-pub_date')[:5]
  1. En el primer ejemplo, latest_question_list = Question.objects.order_by('-pub_date')[:5]
  2. Pero en el segundo ejemplo, ¿a qué variable latest_question_list es igual aquí? Ni siquiera lo hemos definido ...

¿Alguien puede arrojar algo de luz sobre esto?

3
Andrey Kurnikovs 10 may. 2019 a las 19:54

3 respuestas

La mejor respuesta

Un ListView detrás de las cortinas realiza muchas operaciones para crear un contexto y pasarlo a un motor de renderizado. Podemos echar un vistazo a la implementación a través de Vistas con clase con clase .

En esencia, cuando active dicha vista basada en clases, según el método HTTP, activará el método get(..), post(..), etc.

El método get(..) está definido por la clase BaseListView y se define como:

def get(self, request, *args, **kwargs):
    self.object_list = self.get_queryset()
    allow_empty = self.get_allow_empty()
    if not allow_empty:
        # When pagination is enabled and object_list is a queryset,
        # it's better to do a cheap query than to load the unpaginated
        # queryset in memory.
        if self.get_paginate_by(self.object_list) is not None and hasattr(self.object_list, 'exists'):
            is_empty = not self.object_list.exists()
        else:
            is_empty = not self.object_list
        if is_empty:
            raise Http404(_("Empty list and '%(class_name)s.allow_empty' is False.") % {
                'class_name': self.__class__.__name__,
            })
    context = self.get_context_data()
    return self.render_to_response(context)

La parte de importación es que primero somos el resultado de get_queryset() a self.objects_list, y luego construimos un contexto con self.get_context_data(). Luego hacemos una llamada a self.render_to_response(..), que básicamente usará la plantilla especificada y la representaremos con el context dado.

Los datos get_context tienen dos padres con una implementación. El más básico (el más alto en la jerarquía de herencia) es el de ContextMixin, pero esta función no hace mucho:

def get_context_data(self, **kwargs):
    kwargs.setdefault('view', self)
    if self.extra_context is not None:
        kwargs.update(self.extra_context)
    return kwargs

Simplemente toma el diccionario construido por los argumentos de las palabras clave (vacío si no hay argumentos de palabras clave, como es el caso aquí), y agrega una clave adicional 'view' que está asociada con self. También puede agregar pares clave-valor adicionales que se pueden definir en self.extra_context, pero podemos ignorar eso aquí.

La lógica más interesante se implementa en MultipleObjectMixin:

def get_context_data(self, *, object_list=None, **kwargs):
    """Get the context for this view."""
    queryset = object_list if object_list is not None else self.object_list
    page_size = self.get_paginate_by(queryset)
    context_object_name = self.get_context_object_name(queryset)
    if page_size:
        paginator, page, queryset, is_paginated = self.paginate_queryset(queryset, page_size)
        context = {
            'paginator': paginator,
            'page_obj': page,
            'is_paginated': is_paginated,
            'object_list': queryset
        }
    else:
        context = {
            'paginator': None,
            'page_obj': None,
            'is_paginated': False,
            'object_list': queryset
        }
    if context_object_name is not None:
        context[context_object_name] = queryset
    context.update(kwargs)
    return super().get_context_data(**context)

Lo que sucede aquí es que primero asignamos self.object_list, la variable que primero hemos establecido con el resultado de self.get_queryset a una variable local llamada queryset. Luego paginaremos eso queryset, pero eso no es muy relevante para su pregunta.

Luego obtenemos el nombre llamando self.get_context_object_name(queryset). Por defecto esto se implementa como:

def get_context_object_name(self, object_list):
    """Get the name of the item to be used in the context."""
    if self.context_object_name:
        return self.context_object_name
    elif hasattr(object_list, 'model'):
        return '%s_list' % object_list.model._meta.model_name
    else:
        return None

Entonces, si ha establecido el atributo context_object_name, como lo hizo, simplemente devolverá ese nombre. Por lo tanto, podemos concluir que en el método get_context_data(..), el context_object_name tendrá el nombre que le dio, aquí 'latest_question_list'.

Seguimos procesando el código en get_context_data(..): construimos un diccionario y en la parte inferior verificamos si context_object_name no es None. Si ese es el caso, asociamos el queryset con esa clave (así que aquí con 'latest_question_list'). Eventualmente, cuando se construye el diccionario de contexto correcto, realizamos una llamada super() con el contexto construido como **kwargs, y como discutimos antes, el ContextMixin, simplemente devolverá ese diccionario con muy pequeño cambios

Entonces, al final, context tendrá el nombre de su lista (aquí 'latest_question_list') asociado con queryset, y mostrará la plantilla con los datos de contexto.

2
Willem Van Onsem 10 may. 2019 a las 17:09

En la vista basada en clase usaste context_object_name = 'latest_question_list' Por eso es similar a latest_question_list, que usaste en la vista basada en funciones.

En la vista basada en clases, si no agrega context_object_name, entonces su valor es automático object_list. Algo así como context_object_name='object_list'.

1
shafik 10 may. 2019 a las 17:08

TL; versión DR de la respuesta aceptada.

Bajo el capó, Django hace muchas cosas en la vista genérica ListView.

Para tal vista:

class IndexView(generic.ListView):
      model=Question

La variable de contexto generada automáticamente es question_list. Si desea anularlo, debe usar la variable context_object_name para establecer un nombre para su variable de contexto personalizada. Eso es todo, solo establecer un nombre.

Luego debe usar la función predefinida get_queryset, que relacionará su salida con la variable context_object_name.

Por lo tanto, es importante utilizar estos nombres particulares para la variable y la función.

0
arctic.queenolina 18 abr. 2020 a las 10:06