django --- DetailView源碼分析


背景

  最近在看django官方文檔的class-based-views這一節的時候一直不得要領,感覺自己清楚,但是回想起來又沒有脈絡;於是沒有辦法只

  能是“暗中觀察”django的源碼了。 剛打開源碼看了沒有多久就疑竇叢生,比如說下面這一段,能看的出get_object方法中用到的self.kwargs

  屬性是在哪里設置過呢?如果沒有設置直接用是會有報錯的,詳細內容看下面源碼

class SingleObjectMixin(ContextMixin):
    """
    Provide the ability to retrieve a single object for further manipulation.
    """
    model = None
    queryset = None
    slug_field = 'slug'
    context_object_name = None
    slug_url_kwarg = 'slug'
    pk_url_kwarg = 'pk'
    query_pk_and_slug = False

    def get_object(self, queryset=None):
        """
        Return the object the view is displaying.

        Require `self.queryset` and a `pk` or `slug` argument in the URLconf.
        Subclasses can override this to return any object.
        """
        # Use a custom queryset if provided; this is required for subclasses
        # like DateDetailView
        if queryset is None:
            queryset = self.get_queryset()

        # Next, try looking up by primary key.
        pk = self.kwargs.get(self.pk_url_kwarg)
        slug = self.kwargs.get(self.slug_url_kwarg)
        if pk is not None:
            queryset = queryset.filter(pk=pk)

        # Next, try looking up by slug.
        if slug is not None and (pk is None or self.query_pk_and_slug):
            slug_field = self.get_slug_field()
            queryset = queryset.filter(**{slug_field: slug})

        # If none of those are defined, it's an error.
        if pk is None and slug is None:
            raise AttributeError(
                "Generic detail view %s must be called with either an object "
                "pk or a slug in the URLconf." % self.__class__.__name__
            )

        try:
            # Get the single item from the filtered queryset
            obj = queryset.get()
        except queryset.model.DoesNotExist:
            raise Http404(_("No %(verbose_name)s found matching the query") %
                          {'verbose_name': queryset.model._meta.verbose_name})
        return obj

    def get_queryset(self):
        """
        Return the `QuerySet` that will be used to look up the object.

        This method is called by the default implementation of get_object() and
        may not be called if get_object() is overridden.
        """
        if self.queryset is None:
            if self.model:
                return self.model._default_manager.all()
            else:
                raise ImproperlyConfigured(
                    "%(cls)s is missing a QuerySet. Define "
                    "%(cls)s.model, %(cls)s.queryset, or override "
                    "%(cls)s.get_queryset()." % {
                        'cls': self.__class__.__name__
                    }
                )
        return self.queryset.all()

    def get_slug_field(self):
        """Get the name of a slug field to be used to look up by slug."""
        return self.slug_field

    def get_context_object_name(self, obj):
        """Get the name to use for the object."""
        if self.context_object_name:
            return self.context_object_name
        elif isinstance(obj, models.Model):
            return obj._meta.model_name
        else:
            return None

    def get_context_data(self, **kwargs):
        """Insert the single object into the context dict."""
        context = {}
        if self.object:
            context['object'] = self.object
            context_object_name = self.get_context_object_name(self.object)
            if context_object_name:
                context[context_object_name] = self.object
        context.update(kwargs)
        return super().get_context_data(**context)

  

 

眾里尋它

  一看SingleObjectMixin類的定義發現它里面並沒有kwargs這個屬性、所以這個kwargs屬性應該在是定義在它的父類里、ContextMixin源碼如下

class ContextMixin:
    """
    A default context mixin that passes the keyword arguments received by
    get_context_data() as the template context.
    """
    extra_context = None

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

  !我的天kwargs沒有在父類里、也不可能在object類里面。粗看起來get_object方法用使用self.kwargs已經違反Python這門語言的基本法了,但是

  這個的可能性也不太;django這么有名的一個項目,而且自己在使用這上塊的功能時並沒有出問題。

 

刻苦學習在View.as_view方法中發現新的天地

class View:
    http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']

    def __init__(self, **kwargs):
        for key, value in kwargs.items():
            setattr(self, key, value)

    @classonlymethod
    def as_view(cls, **initkwargs):
        """Main entry point for a request-response process."""
        #不允許給View的實例傳入get/post/head這樣的字段、原因是這些名字是留給方法用的!
        for key in initkwargs:
            if key in cls.http_method_names:
                raise TypeError("You tried to pass in the %s method name as a "
                                "keyword argument to %s(). Don't do that."
                                % (key, cls.__name__))
             #如下View類中已經定義了這個“名字”,為了防止實例覆蓋類的同名屬性所以這里也不支持傳入同名屬性
            if not hasattr(cls, key):
                raise TypeError("%s() received an invalid keyword %r. as_view "
                                "only accepts arguments that are already "
                                "attributes of the class." % (cls.__name__, key))
        #如果上面的檢查都過了,但是django並沒有步入通常的套路。它在這里定義了一個叫view的函數,它在view中創建View類的實例
        def view(request, *args, **kwargs):
            #通過as_veiw中傳入的參數來創建一個view實例、由__init__可以知道它是對**kwargs全盤接收的
            #initkwargs是通過閉包的形式記錄下來的
            self = cls(**initkwargs)
            #在沒有指定`head`方法的情況下就直接用`get`來處理`head`
            if hasattr(self, 'get') and not hasattr(self, 'head'):
                self.head = self.get
            #下面這三行搞的對於任何一個通過上述方試創建出來的View實例都有了request、args、kwargs這三個屬性
            self.request = request
            self.args = args
            self.kwargs = kwargs
            #調用 調度方法、以調度方法的返回結果為view這個Function的執行結果、由於調度方法會調用get | post | head ... 而這些方法都返回
            #HttpResponse所以view最終返回的就是HttpResponse的實例
            return self.dispatch(request, *args, **kwargs)
        view.view_class = cls
        view.view_initkwargs = initkwargs
        update_wrapper(view, cls, updated=())
        update_wrapper(view, cls.dispatch, assigned=())
        return view

    def dispatch(self, request, *args, **kwargs):
        """根據http的不同請求方式、調用不同的方法、並返回HttpResponse"""
        if request.method.lower() in self.http_method_names:
            handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
        else:
            handler = self.http_method_not_allowed
        return handler(request, *args, **kwargs)

    def http_method_not_allowed(self, request, *args, **kwargs):
        """用於設定當客戶端使用了不被支持的請求方式時應該如何處理
        """
        logger.warning(
            'Method Not Allowed (%s): %s', request.method, request.path,
            extra={'status_code': 405, 'request': request}
        )
        return HttpResponseNotAllowed(self._allowed_methods())

  可以看到as_view里面做了一個操作叫 self.kwargs = kwargs 、看到了希望!我的三觀還能保全、還要進一步求證這兩個self.kwargs是不是同一個

 

找到關鍵

  1、在使用View時只能用as_view方式

urlpatterns = [
    path('publishers/', PublisherList.as_view()),
]

  2、我們並不直接使用SingleObjectMixin而是使用DetailView、而DetailView就給實例加上了self.kwargs = kwargs這一層邏輯

class BaseDetailView(SingleObjectMixin, View):
    """A base view for displaying a single object."""
    def get(self, request, *args, **kwargs):
        self.object = self.get_object()
        context = self.get_context_data(object=self.object)
        return self.render_to_response(context)


class SingleObjectTemplateResponseMixin(TemplateResponseMixin):
    template_name_field = None
    template_name_suffix = '_detail'



class DetailView(SingleObjectTemplateResponseMixin, BaseDetailView):
    """
    Render a "detail" view of an object.

    By default this is a model instance looked up from `self.queryset`, but the
    view will support display of *any* object by overriding `self.get_object()`.
    """

  可以看到BaseDetailView把SingleObjectMixin和View、也就是說第一個BaseDetailView的實例都會有kwargs屬性,這個原因可以追溯到

  “刻苦學習在View.as_view方法中發現新的天地”這一節中提到的self.kwargs = kwargs。

 

  由於View的實例都是由as_view創建的、在as_view的邏輯里面就會給實例加上kwargs屬性、SingleObjectMixin.get_object中執行self.kwargs

  自然就不會有錯誤了!

 

DetailView相關的層次結構】 

 

 

 

----


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM