5

I need to make a Form class that may or not have a ReCaptcha field depending on whether the user is logged in or not.

Because this is a CommentForm, I have no access to the request object on form creation/definition, so I can't rely on that.

For the POST request the solution is easy: I've got this:

class ReCaptchaCommentForm(CommentForm):
    def __init__(self, data=None, *args, **kwargs):
        super(ReCaptchaCommentForm, self).__init__(data, *args, **kwargs)
        if data and 'recaptcha_challenge_field' in data:
            self.fields['captcha'] = ReCaptchaField()

Having done this, form validation should work as intended. The problem now is on the template side. I need the template to be like this:

<form action={% comment_form_target %} method="post">
{# usual form stuff #}
{% if not user.is_authenticated %}
<script  type="text/javascript"
         src="http://www.google.com/recaptcha/api/js/recaptcha_ajax.js"></script>
<div id="recaptcha-div"></div>
<script type="text/javascript">
  Recaptcha.create({{ public_key }}, "recaptcha-div",
                   { theme: 'white',
                     callback: Recaptcha.focus_response_field });
</script>
{% endif %}
</form>

But I'd like not to have to repeat that code on every comments/*/form.html template. I gather there should be some way of adding equivalent code from a widget's render method and Media definition.

Can anyone think of a nice way to do this?

Lacrymology
  • 2,269
  • 2
  • 20
  • 28
  • 1
    Problem is that widgets don't know anything about the request, so there's no way to conditionally render based on the logged in user. Although, you may want to look at django-floppyforms. Since it uses templates to render widgets, it's possible you could do what you want that way. – Chris Pratt Jun 06 '12 at 20:22
  • yes, that's the idea I had, render a widget through a template. But I'm not sure I can have the request.user in my template vars even if I do this (widget.render can do whatever it wants, including rendering a template, the context is the problem). What I'd want to do is to render a template, that will get parsed later on – Lacrymology Jun 06 '12 at 20:29
  • 1
    That's why I suggested django-floppyforms. I'm not completely sure, but I think it works off the standard include pattern, rather than simply having `render` use a template. It should have access to `request` in the template context just like any other included template. Try it out and see. – Chris Pratt Jun 06 '12 at 20:52
  • just read the docs, http://django-floppyforms.readthedocs.org/en/latest/usage.html the included context is only widget data, no request. Thanks for the suggestion, though – Lacrymology Jun 06 '12 at 21:19
  • What exactly is preventing you from accessing the request object at from creation? – Maccesch Jun 06 '12 at 21:57
  • that the comments system doesn't pass user or request to form instantiation, it's run from a templatetag – Lacrymology Jun 08 '12 at 22:24

4 Answers4

5

I assume that you instatiate your form in a view, so you could just pass the user from request to the form (just like in auth app SetPassword form):

def __init__(self, user, data=None, *args, **kwargs):
    super(ReCaptchaCommentForm, self).__init__(data, *args, **kwargs)
    if user.is_authenticated():
        self.fields['captcha'] = ReCaptchaField()
zaan
  • 887
  • 4
  • 12
  • no, the comments system doesn't work like that (it's not a view, it's a templatetag, and it doesn't pass the request (OR the user) as a parameter. This is part of the description! – Lacrymology Jun 08 '12 at 22:23
3

Use crispy-forms! You can include html elements in the form layout that would allow you to exclude/include a field based on the views request context. Extremely useful features outside of that as well.

Here's the relevant doc section.

Uyghur Lives Matter
  • 18,820
  • 42
  • 108
  • 144
Murph
  • 511
  • 7
  • 16
  • oh, I'll come back to this task after I've fixed a couple other things. I'm actually already including crispy-forms but I haven't used it yet, since we use a more template-driven method. – Lacrymology Jun 11 '12 at 13:30
1

What I am doing about conditional fields is having a base class (which inherits from Form) and other subclasses with the extra conditional fields.

Then in my view, depending on the condition I choose the required subclassed form. I know that it involves some duplicated code, but it seems easier than other approaches.

manu
  • 66
  • 1
  • 2
0

Well, it's unfortunate that django-floppyforms doesn't give access to the request. It would have been nice to know it was an option, as I've recently started using django-floppyforms in my own project.

Short of that, the best thing I can think of is to simply rely on template inheritance. You can create a comments/form.html file and then have each comments/*/form.html extend that. Put the Recaptcha code as you have it in the base form.html and you're good to go.

Chris Pratt
  • 232,153
  • 36
  • 385
  • 444
  • that's what I did, but I'd like a nicer solution. Specially since a) I'll need to do the same for forms outside the comments system, and b) this should be trivial – Lacrymology Jun 08 '12 at 22:22
  • Well, it kind of *is* trivial. You just have to add the script to a base template that all the forms inherit from. And, if you need it outside the comments system, there's nothing saying that you can't have an even higher-level form.html template, perhaps immediately in the main templates directory, that this app and all other apps that need it inherit from. – Chris Pratt Jun 11 '12 at 14:21