Plain is headed towards 1.0! Subscribe for development updates →

plain-support

Captcha...

Security considerations

Most support forms allow you to type in an email address. Be careful, because anybody can pretend to be anybody else at this point. Converations either need to continue over email (which confirms they have access to the email account), or include a verification step (emailing a code to the email address, for example).

 1from plain.assets.urls import get_asset_url
 2from plain.http import ResponseRedirect
 3from plain.runtime import settings
 4from plain.utils.module_loading import import_string
 5from plain.views import FormView, View
 6from plain.views.csrf import CsrfExemptViewMixin
 7
 8
 9class SupportFormView(FormView):
10    template_name = "support/page.html"
11
12    def get_form(self):
13        form_slug = self.url_kwargs["form_slug"]
14        form_class = import_string(settings.SUPPORT_FORMS[form_slug])
15        return form_class(**self.get_form_kwargs())
16
17    def get_template_context(self):
18        context = super().get_template_context()
19        form_slug = self.url_kwargs["form_slug"]
20        context["form_action"] = self.request.build_absolute_uri()
21        context["form_template_name"] = f"support/forms/{form_slug}.html"
22        context["success_template_name"] = f"support/success/{form_slug}.html"
23        context["success"] = self.request.GET.get("success") == "true"
24        return context
25
26    def get_form_kwargs(self):
27        kwargs = super().get_form_kwargs()
28        kwargs["user"] = self.request.user
29        kwargs["form_slug"] = self.url_kwargs["form_slug"]
30        return kwargs
31
32    def form_valid(self, form):
33        entry = form.save()
34        form.notify(entry)
35        return super().form_valid(form)
36
37    def get_success_url(self, form):
38        # Redirect to the same view and template so we
39        # don't have to create two additional views for iframe and non-iframe.
40        return "?success=true"
41
42
43class SupportIFrameView(CsrfExemptViewMixin, SupportFormView):
44    template_name = "support/iframe.html"
45
46    def get_response(self):
47        response = super().get_response()
48
49        # X-Frame-Options are typically in DEFAULT_RESPONSE_HEADERS,
50        # which will know to drop the header completely if an empty string.
51        # We can't del/pop it because DEFAULT_RESPONSE_HEADERS may add it back.
52        response.headers["X-Frame-Options"] = ""
53
54        return response
55
56
57class SupportFormJSView(View):
58    def get(self):
59        return ResponseRedirect(get_asset_url("support/embed.js"))