django 類視圖解析 -CreateView

django 類視圖解析 -FormView一文中有小伙伴提問將POST來的數(shù)據(jù)寫入模型中的業(yè)務(wù)邏輯,這樣就有了本文拯啦,這里感謝這位小伙伴@huangkewen

Django編輯內(nèi)容的通用視圖包括

django.views.genetic.edit.FormView —與模型無關(guān)

django.views.genetic.edit.CreateView —與模型有關(guān)澡匪,創(chuàng)建模型實例

django.views.genetic.edit.UpdateView —與模型有關(guān),修改模型實例

django.views.genetic.edit.DeleteView --與模型有關(guān)提岔,刪除模型實例

我們可以理解 CreateView仙蛉、UpdateView、DeleteView 這三個通用視圖是在 FormView 的基礎(chǔ)上增加了對創(chuàng)建碱蒙、修改荠瘪、刪除模型實例的方法夯巷。

那么,我們就先以 CreateView 為例一起來詳細(xì)看看哀墓,它是怎么基于 FormView 來實現(xiàn)創(chuàng)建模型實例的趁餐,也就是怎么將 Post來的數(shù)據(jù)寫入模型的。

CreateView 與 FormView 的區(qū)別

流程分析的差異

CreateView 與 FormView 的 流程相似篮绰,只是在 Get 和 Post 請求中定義了 object后雷。那么 CreateView 如何實現(xiàn)數(shù)據(jù)與模型的聯(lián)系呢?

Create流程.png

原因在于 CreateView 依賴的 BaseCreateView 覆蓋了FormView 依賴的 Base FormView 的 get_form_class() 方法吠各,從而在 Form 實例化的過程中加入了與模型的交互臀突。

對于 BaseFromView 而言,其 get_form_class() 函數(shù)為:

def get_form_class(self):
    """
    Returns the form class to use in this view
    """
    return self.form_class

對于 BaseCreateView 而言贾漏,其 get_form_class() 函數(shù)為:

def get_form_class(self):
    """
    Returns the form class to use in this view.
    """
    if self.fields is not None and self.form_class:
        raise ImproperlyConfigured(
            "Specifying both 'fields' and 'form_class' is not permitted."
        )
    if self.form_class:
        return self.form_class
    else:
        if self.model is not None:
            # If a model has been explicitly provided, use it
            model = self.model
        elif hasattr(self, 'object') and self.object is not None:
            # If this view is operating on a single object, use
            # the class of that object
            model = self.object.__class__
        else:
            # Try to get a queryset and extract the model class
            # from that
            model = self.get_queryset().model

        if self.fields is None:
            raise ImproperlyConfigured(
                "Using ModelFormMixin (base class of %s) without "
                "the 'fields' attribute is prohibited." % self.__class__.__name__
            )

        return model_forms.modelform_factory(model, fields=self.fields)

這里要求要么設(shè)置 form_class候学,要么設(shè)置 fields,兩者不能同時設(shè)置纵散。對于處理模型的梳码,要設(shè)置 fields,然后分別通過設(shè)置的 model伍掀、object掰茶、get_queryset() 獲取使用的模型,這里蜜笤,如果第一個滿足條件則不會進(jìn)行第二濒蒋、第三個,如果有 model 屬性則不再關(guān)心后兩個瘩例。然后調(diào)用 modelforms的modelform_factory 創(chuàng)建模型實例啊胶。

代碼結(jié)構(gòu)的差異

既然前面說 CreateView 基于FormView甸各,那么我們先來看看 CreateView 和 FormView 的繼承關(guān)系:

CreateView繼承關(guān)系.png

上圖中垛贤,黑色字為 CreateView 類及它繼承的類,藍(lán)色字為 FormView 繼承的類趣倾,從圖中可以看到 CreateView繼承了 FormView 集成的所有類聘惦,此外還額外增加了一些內(nèi)容。下面我們來具體看下:

SingleObjectTemplateResponseMixin 與 TemplateResponseMixin

這里儒恋,SingleObjectTemplateResponseMixin 繼承 TemplateResponseMixin 善绎,也就說在 CreateView 在模板響應(yīng)這塊兒與 FormView 更加了功能,那么诫尽,增加的是什么呢禀酱?

我們先來看看 TemplateResponseMixin 的代碼,分析它實現(xiàn)了什么功能:

class TemplateResponseMixin(object):
    """
    A mixin that can be used to render a template.
    """
    template_name = None
    template_engine = None
    response_class = TemplateResponse
    content_type = None

    def render_to_response(self, context, **response_kwargs):
        """
        Returns a response, using the `response_class` for this
        view, with a template rendered with the given context.

        If any keyword arguments are provided, they will be
        passed to the constructor of the response class.
        """
        response_kwargs.setdefault('content_type', self.content_type)
        return self.response_class(
            request=self.request,
            template=self.get_template_names(),
            context=context,
            using=self.template_engine,
            **response_kwargs
        )

    def get_template_names(self):
        """
        Returns a list of template names to be used for the request. Must return
        a list. May not be called if render_to_response is overridden.
        """
        if self.template_name is None:
            raise ImproperlyConfigured(
                "TemplateResponseMixin requires either a definition of "
                "'template_name' or an implementation of 'get_template_names()'")
        else:
            return [self.template_name]
TemplateResponseMixin

TemplateResponseMixin 這個類 定義了template_name牧嫉、template_engine剂跟、response_class减途、content_type 四個屬性以及 render_to_response 和 get_template_names 兩個方法。

template_name 和 get_template_names 都用于返回模板名稱曹洽,這里要么設(shè)置 template_name的名稱鳍置,要么重新定義 get_template_names() 返回模板名稱列表;其它類在獲取模型名稱時調(diào)用 get_template_names() 獲取模板名稱列表送淆。

g

template_engine税产、response_class、content_type 三個屬性以及 get_template_names() 都用于render_to_response() 偷崩,分別用于設(shè)定響應(yīng)的 模板引擎辟拷、響應(yīng)類型(TemplateResponse)、內(nèi)容類型阐斜,render_to_response 用于渲染響應(yīng)梧兼。

SingleObjectTemplateResponseMixin

我們再來看看 SingleObjectTemplateResponseMixin 的代碼,分析它增加了什么功能:

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

    def get_template_names(self):
        """
        Return a list of template names to be used for the request. May not be
        called if render_to_response is overridden. Returns the following list:

        * the value of ``template_name`` on the view (if provided)
        * the contents of the ``template_name_field`` field on the
          object instance that the view is operating upon (if available)
        * ``<app_label>/<model_name><template_name_suffix>.html``
        """
        try:
            names = super(SingleObjectTemplateResponseMixin, self).get_template_names()
        except ImproperlyConfigured:
            # If template_name isn't specified, it's not a problem --
            # we just start with an empty list.
            names = []

            # If self.template_name_field is set, grab the value of the field
            # of that name from the object; this is the most specific template
            # name, if given.
            if self.object and self.template_name_field:
                name = getattr(self.object, self.template_name_field, None)
                if name:
                    names.insert(0, name)

            # The least-specific option is the default <app>/<model>_detail.html;
            # only use this if the object in question is a model.
            if isinstance(self.object, models.Model):
                names.append("%s/%s%s.html" % (
                    self.object._meta.app_label,
                    self.object._meta.model_name,
                    self.template_name_suffix
                ))
            elif hasattr(self, 'model') and self.model is not None and issubclass(self.model, models.Model):
                names.append("%s/%s%s.html" % (
                    self.model._meta.app_label,
                    self.model._meta.model_name,
                    self.template_name_suffix
                ))

            # If we still haven't managed to find any template names, we should
            # re-raise the ImproperlyConfigured to alert the user.
            if not names:
                raise

        return names

SingleObjectTemplateResponseMixin 在 TemplateResponseMixin 的基礎(chǔ)上增加了template_name_field智听、template_name_suffix 用于設(shè)定 模板名稱字段和模板名稱后綴羽杰,用于定義 get_template_names()。

get_template_names() 重寫 TemplateResponseMixin 的 get_template_names() 到推,這時如果類不定義模板名稱不會像 TemplateResponseMixin 一樣引發(fā)異常考赛,而是嘗試使用模型實例的名稱定義模板名稱。

BaseCreateView與BaseFormView

BaseCreateView 與 BaseFormView 相比增加了 object 屬性的定義莉测,object 用于定義模型實例颜骤。

class BaseCreateView(ModelFormMixin, ProcessFormView):
    """
    Base view for creating an new object instance.

    Using this base class requires subclassing to provide a response mixin.
    """
    def get(self, request, *args, **kwargs):
        self.object = None
        return super(BaseCreateView, self).get(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        self.object = None
        return super(BaseCreateView, self).post(request, *args, **kwargs)

BaseCreateView 繼承 ModelFormMixin,BaseFormView繼承 FormMixin捣卤。

class BaseFormView(FormMixin, ProcessFormView):
    """
    A base view for displaying a form
    """

ModelFormMixin 與 FormMixin

ModelFormMixin 繼承 FormMixin忍抽,此外還繼承了 SingleObjecMixin ,SingleObjectMixin 定義了 model董朝、queryset鸠项、context_object_name 等數(shù)據(jù)庫查詢的屬性,此外還定義了 get_objetct()函數(shù)用于確定模型實例子姜。

class SingleObjectMixin(ContextMixin):
    """
    Provides 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):
        """
        Returns the object the view is displaying.

        By default this requires `self.queryset` and a `pk` or `slug` argument
        in the URLconf, but 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, None)
        slug = self.kwargs.get(self.slug_url_kwarg, None)
        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."
                                 % 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.

        Note that 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(SingleObjectMixin, self).get_context_data(**context)

這里的方法主要供 get_form_class() 方法調(diào)用祟绊。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市哥捕,隨后出現(xiàn)的幾起案子牧抽,更是在濱河造成了極大的恐慌,老刑警劉巖遥赚,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件扬舒,死亡現(xiàn)場離奇詭異,居然都是意外死亡凫佛,警方通過查閱死者的電腦和手機讲坎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進(jìn)店門泽腮,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人衣赶,你說我怎么就攤上這事诊赊。” “怎么了府瞄?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵碧磅,是天一觀的道長。 經(jīng)常有香客問我遵馆,道長鲸郊,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任货邓,我火速辦了婚禮秆撮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘换况。我一直安慰自己职辨,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布戈二。 她就那樣靜靜地躺著舒裤,像睡著了一般。 火紅的嫁衣襯著肌膚如雪觉吭。 梳的紋絲不亂的頭發(fā)上腾供,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天,我揣著相機與錄音鲜滩,去河邊找鬼伴鳖。 笑死,一個胖子當(dāng)著我的面吹牛徙硅,可吹牛的內(nèi)容都是我干的榜聂。 我是一名探鬼主播,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼闷游,長吁一口氣:“原來是場噩夢啊……” “哼峻汉!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起脐往,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎扳埂,沒想到半個月后业簿,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡阳懂,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年梅尤,在試婚紗的時候發(fā)現(xiàn)自己被綠了柜思。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡巷燥,死狀恐怖赡盘,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情缰揪,我是刑警寧澤陨享,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站钝腺,受9級特大地震影響抛姑,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜艳狐,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一定硝、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧毫目,春花似錦蔬啡、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至粉私,卻和暖如春顽腾,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背诺核。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工抄肖, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人窖杀。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓漓摩,卻偏偏與公主長得像,于是被迫代替她去往敵國和親入客。 傳聞我的和親對象是個殘疾皇子管毙,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內(nèi)容