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?
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()
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.
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.
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.
Related
I have a generic ListView which displays all objects from a model and I would like users to be able to choose one object for further processing by storing in session or in another model. What would be the best way to go about this?
views.py
class TranscriptListView(generic.ListView):
model = Transcript
template_name = 'transcript_list.html'
template
{% block content %}
<ul>
{% for transcript in transcript_list %}
<li>
{{transcript.name}}
<p>{{transcript.text}}</p>
</li>
{% endfor %}
</ul>
For selecting something to store in a session, I'd just do a
class SelectTranscriptView(SingleObjectMixin, View):
model = Transcript
def post(self, request, *args, **kwargs):
object = self.get_object()
request.session['selected_transcript'] = object.id
return redirect(...)
This view only accepts POST, since GET requests should be idempotent, i.e. they should not modify any state. Setting a session value is definitely modifying state. This means you'll need to use a form (or a JavaScript function) to POST data to the URL you hook this up to.
More importantly, though: Setting something in the session is not necessarily a good idea at all. Consider an advanced user deciding to open multiple tabs to your site, and trying to select one transcript for one tab, and another in the other tab. This won't be possible if you store the selected thing in the session! Instead, I'd design things so the ID of the object being edited or viewed is always in the URL (as happens with DetailViews and the ilk anyway).
I have a class-based view FooCreate and I want to use it on two different pages:
normal create view: With all the normal header and footer parts: Extending my base.html
in popup: Here I want no visible header and footer part: Extending (the not existing yet) base_popup.html
I would like to implement this without a single "if", since I like condition-less code :-)
I would advise you to checkout how Django Admin handles this with IS_POPUP_VAR.
Basically, Django Admin uses a '_popup' parameter passed in the querystring. You can then pass a "base_layout" variable to context.
class MyView(View):
def get_context_data(self, **kwargs):
if '_popup' in request.GET:
kwargs['base_layout'] = 'base_popup.html'
return super().get_context_data(**kwargs)
And your templates would start with:
{% extends base_layout|default:"base.html" %}
I have the following FormHelper that I'm using to render a formset in Django Crispy Forms. My understanding of the documentation indicates that I should end up with a formset with two HTML fields (name, user name) and two input fields, but this doesn't seem to be the case. In fact, I only have the two form fields.
I'm unclear as to how I would go about adding HTML to a formset, given that the code below doesn't seem to do the trick.
class ProposalFormSetHelper(FormHelper):
def __init__(self, *args, **kwargs):
super(ProposalFormSetHelper, self).__init__(*args, **kwargs)
self.form_method = 'post'
self.layout = Layout(
HTML('{{ form.instance.proposal.name }}'),
HTML('{{ form.instance.user.get_full_name }}'),
Field('accepted', css_class='input-mini'),
Field('rating', css_class='input-mini')
)
self.template = 'bootstrap/table_inline_formset.html'
self.add_input(Submit('submit', 'Update'))
I should note that I've also tried to, alternatively, keep the FormHelper in the form itself and attach it to the {% crispy %} tag by doing: {% crispy formset formset.form.helper %}, but this had pretty much the same affect.
I'm the lead developer of crispy-forms
The problem here is that you are using bootstrap/table_inline_formset.html which currently doesn't support specifying a layout.
It's stated clearly in the docs: http://django-crispy-forms.readthedocs.org/en/latest/crispy_tag_formsets.html#custom-templates-and-table-inline-formsets
This template doesn’t currently take into account any layout you have specified and only works with bootstrap template pack.
I'm aware this is not the expected behavior and there is an open issue about it. I'm hopefully working this week on solving it somehow.
I have this models in Django:
News
Comments
Reactions
Relations are:
a News has various Comments
a Comment has various Reactions
The problem is the user (in request / session): the user may subscribe to a reaction, or a comment; he may be logged in or not. (it's a foo example, it doesn't have much sense)
I can't do in template:
{% for reaction in this_news.comments.reactions %}
{{ reaction.name }}
{% if reaction.user_subscribed %} #reaction.user_subscribed(request.user)...
You have subscribed this reaction!
{% endif %}
{% endfor %}
Problems are:
I can't call the method in the template with a parameter (see the comment above)
Models don't have access to request
Now i'm calling an init_user method in News Model, passing the request. Then i have the same method in Comment and Reaction model, and i have to set the user_subscribed property cycling the children of each model.
Isn't there a smarter way to do this?
EDIT: thanks to the Ignacio's hint about using custom tag i'm trying to do a generic mode to pass the user (avoiding the use of closures because i don't know how to use them atm):
def inject_user(parser, token):
try:
# split_contents() knows not to split quoted strings.
tag_name, method_injected, user = token.split_contents()
except ValueError:
raise template.TemplateSyntaxError("%r tag requires exactly three arguments" % token.contents.split()[0])
return InjectUserNode(method_injected, user)
class InjectUserNode(template.Node):
def __init__(self, method_injected, user):
self.method_injected = template.Variable(method_injected)
self.user = template.Variable(user)
def render(self, context):
try:
method_injected = self.method_injected.resolve(context)
user = self.user.resolve(context)
return method_injected(user)
except template.VariableDoesNotExist:
return ''
When i use it {% inject_user object.method_that_receives_a_user request.user %} i come to this error 'str' object is not callable in method_injected(user); how i can fix that?
Write custom template tags that take the user and set a context variable to indicate presence or absence of the criterion.
I've resolved it in a less elegant way, but it worked for me.
I've created a sort of singleton in my User defined class, with a property that i set in every view i need it.
The property is User.current.
Then, inside the models, where i need that i get the current user looking in User.current.
I'd like to create widgets that add specific classes to element markup when the associated field has errors.
I'm having a hard time finding information on how to check whether a field has errors associated with it, from within widget definition code.
At the moment I have the following stub widget code (the final widget will use more complex markup).
from django import forms
from django.utils.safestring import mark_safe
class CustomTextWidget(forms.Widget):
def render(self, name, value, attrs):
field_has_errors=False # change to dynamically reflect field errors, somehow
if field_has_errors:
error_class_string="error"
else:
error_class_string=""
return mark_safe(
"<input type=\"text\" class=\"%s\" value=\"%s\" id=\"id_%s\" name=\"%s\">" % (error_class_string, value, name, name)
)
Can anyone shed light on a sensible way to populate the field_has_errors Boolean here? (or perhaps suggest a better way to accomplish what I'm trying to do). Thanks in advance.
As Jason says, the widget has no access to the field itself. I think a better solution though is to use the cascading nature of CSS.
{% for field in form %}
<div class="field{% if field.errors %} field_error{% endif %}">
{{ field }}
</div>
{% endfor %}
Now in your CSS you can do:
div.field_error input { color: red }
or whatever you need.
The widget has no knowledge of the field to which it is being applied. It is the field that maintains information about errors. You can check for error_messages in the init method of your form, and inject an error class to your widget accordingly:
class YourForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(YourForm, self).__init__(*args, **kwargs)
attrs = {}
if self.fields['your_field'].error_messages is not None:
attrs['class'] = 'errors'
self.fields['your_field'].widget = YourWidget(attrs=attrs)