Plain is headed towards 1.0! Subscribe for development updates →

  1from plain.exceptions import ImproperlyConfigured, ObjectDoesNotExist
  2from plain.forms import Form
  3from plain.http import Http404, Response
  4
  5from .forms import FormView
  6from .templates import TemplateView
  7
  8
  9class ObjectTemplateViewMixin:
 10    context_object_name = ""
 11
 12    def get(self) -> Response:
 13        self.load_object()
 14        return self.render_template()
 15
 16    def load_object(self) -> None:
 17        try:
 18            self.object = self.get_object()
 19        except ObjectDoesNotExist:
 20            raise Http404
 21
 22        if not self.object:
 23            # Also raise 404 if the object is None
 24            raise Http404
 25
 26    def get_object(self):  # Intentionally untyped... subclasses must override this.
 27        raise NotImplementedError(
 28            f"get_object() is not implemented on {self.__class__.__name__}"
 29        )
 30
 31    def get_template_context(self) -> dict:
 32        """Insert the single object into the context dict."""
 33        context = super().get_template_context()  # type: ignore
 34        context["object"] = (
 35            self.object
 36        )  # Some templates can benefit by always knowing a primary "object" can be present
 37        if self.context_object_name:
 38            context[self.context_object_name] = self.object
 39        return context
 40
 41
 42class DetailView(ObjectTemplateViewMixin, TemplateView):
 43    """
 44    Render a "detail" view of an object.
 45
 46    By default this is a model instance looked up from `self.queryset`, but the
 47    view will support display of *any* object by overriding `self.get_object()`.
 48    """
 49
 50    pass
 51
 52
 53class CreateView(ObjectTemplateViewMixin, FormView):
 54    """
 55    View for creating a new object, with a response rendered by a template.
 56    """
 57
 58    def post(self) -> Response:
 59        """
 60        Handle POST requests: instantiate a form instance with the passed
 61        POST variables and then check if it's valid.
 62        """
 63        # Context expects self.object to exist
 64        self.load_object()
 65        return super().post()
 66
 67    def load_object(self) -> None:
 68        self.object = None
 69
 70    # TODO? would rather you have to specify this...
 71    def get_success_url(self, form):
 72        """Return the URL to redirect to after processing a valid form."""
 73        if self.success_url:
 74            url = self.success_url.format(**self.object.__dict__)
 75        else:
 76            try:
 77                url = self.object.get_absolute_url()
 78            except AttributeError:
 79                raise ImproperlyConfigured(
 80                    "No URL to redirect to.  Either provide a url or define"
 81                    " a get_absolute_url method on the Model."
 82                )
 83        return url
 84
 85    def form_valid(self, form):
 86        """If the form is valid, save the associated model."""
 87        self.object = form.save()
 88        return super().form_valid(form)
 89
 90
 91class UpdateView(ObjectTemplateViewMixin, FormView):
 92    """View for updating an object, with a response rendered by a template."""
 93
 94    def post(self) -> Response:
 95        """
 96        Handle POST requests: instantiate a form instance with the passed
 97        POST variables and then check if it's valid.
 98        """
 99        self.load_object()
100        return super().post()
101
102    def get_success_url(self, form):
103        """Return the URL to redirect to after processing a valid form."""
104        if self.success_url:
105            url = self.success_url.format(**self.object.__dict__)
106        else:
107            try:
108                url = self.object.get_absolute_url()
109            except AttributeError:
110                raise ImproperlyConfigured(
111                    "No URL to redirect to.  Either provide a url or define"
112                    " a get_absolute_url method on the Model."
113                )
114        return url
115
116    def form_valid(self, form):
117        """If the form is valid, save the associated model."""
118        self.object = form.save()
119        return super().form_valid(form)
120
121    def get_form_kwargs(self):
122        """Return the keyword arguments for instantiating the form."""
123        kwargs = super().get_form_kwargs()
124        kwargs.update({"instance": self.object})
125        return kwargs
126
127
128class DeleteView(ObjectTemplateViewMixin, FormView):
129    """
130    View for deleting an object retrieved with self.get_object(), with a
131    response rendered by a template.
132    """
133
134    class EmptyDeleteForm(Form):
135        def __init__(self, instance, *args, **kwargs):
136            self.instance = instance
137            super().__init__(*args, **kwargs)
138
139        def save(self):
140            self.instance.delete()
141
142    form_class = EmptyDeleteForm
143
144    def post(self) -> Response:
145        """
146        Handle POST requests: instantiate a form instance with the passed
147        POST variables and then check if it's valid.
148        """
149        self.load_object()
150        return super().post()
151
152    def get_form_kwargs(self):
153        """Return the keyword arguments for instantiating the form."""
154        kwargs = super().get_form_kwargs()
155        kwargs.update({"instance": self.object})
156        return kwargs
157
158    def form_valid(self, form):
159        """If the form is valid, save the associated model."""
160        form.save()
161        return super().form_valid(form)
162
163
164class ListView(TemplateView):
165    """
166    Render some list of objects, set by `self.get_queryset()`, with a response
167    rendered by a template.
168    """
169
170    context_object_name = ""
171
172    def get(self) -> Response:
173        self.objects = self.get_objects()
174        return super().get()
175
176    def get_objects(self):
177        raise NotImplementedError(
178            f"get_objects() is not implemented on {self.__class__.__name__}"
179        )
180
181    def get_template_context(self) -> dict:
182        """Insert the single object into the context dict."""
183        context = super().get_template_context()  # type: ignore
184        context["objects"] = self.objects
185        if self.context_object_name:
186            context[self.context_object_name] = self.objects
187        return context