Search code examples
djangodjango-formscaptchadjango-material

How to place captcha on Django CreateView that uses Material Design Layout() feature


I'm working in an existing codebase that uses Django Material. There is a CreateView defined with a Django Material Layout:

class OurModelCreateView(LayoutMixin, CreateView): 
    model = OurModel

    layout = Layout(
        Row('field1', 'field2', 'field3'),
        Row(...)
    )

This view is getting lots of spam signups and so needs to have a captcha. I use Django Recaptcha, and I've set up a number of captchas in the past. However, I've never set one up without using a ModelForm. If I create a Django model form and define the captcha field in the form as I've always done:

from captcha.fields import ReCaptchaField
from captcha.widgets import ReCaptchaV3

class OurModelForm(ModelForm):
    
captcha = ReCaptchaField(widget=ReCaptchaV3)

    class Meta:
        model = OurModel
        exclude = ()

and then specify form_class = OurModelForm on the CreateView, the following error is raised by ModelFormMixin.get_form_class(): "Specifying both 'fields' and 'form_class' is not permitted". This error is being raised because, though I've not explicitly specified fields, Django Material's LayoutMixin defines fields: https://github.com/viewflow/django-material/blob/294129f7b01a99832a91c48f129cefd02f2fe35f/material/base.py (bottom of the page)

I COULD drop the Material Layout() from the CreateView, but then that would mean having to create an html form to render the Django/Django Material form - less than desirable as there are actually several of these CreateViews that need to have a captcha applied.

So I think that the only way to accomplish what I'm after is to somehow dynamically insert the captcha field into the form.

I've dynamicaly inserted fields into Django forms in the past by placing the field definition in the __init__() of the Django form definition, but I can't figure out what to override in either CreateView (or the various mixins that comprise CreateView) or Django Material's LayoutMixin in order to dynamically insert the captcha field into the form. The following several attempts to override get_form and fields in order to dynamically insert the captcha field do not work:

   On the CreateView:
       def get_form(self, form_class=None):
           form = super(OurModelCreate, self).get_form(form_class)
           form.fields['captcha'] = ReCaptchaField(widget=ReCaptchaV3)
           return form

       def fields(self):
           fields = super().fields(*args, **kwargs)
           fields['captcha'] = ReCaptchaField(widget=ReCaptchaV3)
           return [field.field_name for field in fields

       # fields is actually a list, so trying the following too, but it doesn't include the ReCaptchaField(widget=ReCaptchaV3) anywhere at this point
       def fields(self):
           fields = super().fields(*args, **kwargs)
           fields.append('captcha')
           return fields

Any help would be greatly appreciated.


Solution

  • Following up on the comment from @Alasdair above which pointed me to the answer, I solved this problem by removing Django Material's LayoutMixin from CreateView, creating a Django form with the captcha field defined, and then adding to CreateView the form_class for the Django form. Also see my last comment above. It was counterintuitive to me until I looked again at the code after @Alasdair's second comment: the use of LayoutMixin on the CreateView isn't necessary for the layout = Layout(...) on the CreateView to work.