今回のお題
今回はdjangoアプリのクラスベースビューの中でself.object
を使う際の注意点を書きます。
ほとんど自分用のメモです。
発生した問題
class ShopDetailView(UserPassesTestMixin, DetailView):
model = Shop
def test_func(self):
return self.request.user.shop.pk == self.object.pk
get_success_url(self):
return reverse("shop:detal", kwargs = { "pk": self.object.pk })
としたときにエラーになった(selfにはobjectという属性値はないと言われた)。
このエラーはtest_funcをコメントアウトすると解消された。
結論
UserPassesMixin
などのAccessMinin
を継承しているMixinの中では
self.object
は使えない。
self.kwargs["pk"]
でpk
を拾って参照中のオブジェクトにアクセスする。
理由
まず汎用ビューでself.object
とすると参照中のオブジェクトが取得できる理由について。
例えばDetailView
であれば継承のルートはSingleObjectMixin
> BaseDetailVeiw
> DetailView
そしてself.object
の取得はBaseDeatilView
のget
メソッドの中で定義されており、そこで使われているget_object
メソッドは大元のSingleObjectMixin
で定義されている。
この流れはCreateView
とUpdateView
でも同様で、SingleObjectMixin
> ModelFormMixin
> BaseCreateView, BaseUpdateView
> CreateView, UpdateView
という順序で継承されている(objectについてはself.object = form.save()
としてModelFormMixin
の中で取得している)。
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
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 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()`.
"""
一方でAccessMixin由来のMixinではobject
という属性値が定義されていない。
このため、実装するテンプレートビューが他のテンプレートビューとの多重継承であったとしても、test_func
などの上記Mixin由来のものの中ではself.object
を使うことはできない。
対処法
``python
self.kwargs["pk"]
を用いて参照中のオブジェクトにアクセスする。
なお、以下のように記述するのは問題ない(すなわち`self.kwargs`はMixinでもTemplateViewでも使える)。
```python
class ShopDetailView(UserPassesTestMixin, DetailView):
model = Shop
get_success_url(self):
return reverse("shop:detal", kwargs = { "pk": self.kwargs["pk"] })