I want to add recaptcha for signup view in my Django app. This below uses decorators.py to achieve that. I have tried other tutorials for adding reCAPTCHA also but does not seem working. Any idea why?
views.py
class signup_view(generic.CreateView):
form_class = RegisterForm
template_name = 'users/signup.html'
success_url = reverse_lazy('users:login')
def form_valid(self, form):
if self.request.recaptcha_is_valid:
form.save()
return render(self.request, 'users/login.html', self.get_context_data())
return render(self.request, 'users/signup.html', self.get_context_data())
urls.py
path("signup", check_recaptcha(signup_view.as_view()), name="signup"),
decorators.py
from django.conf import settings
from django.contrib import messages
import requests
def check_recaptcha(function):
def wrap(request, *args, **kwargs):
request.recaptcha_is_valid = None
if request.method == 'POST':
recaptcha_response = request.POST.get('g-recaptcha-response')
data = {
'secret': settings.GOOGLE_RECAPTCHA_SECRET_KEY,
'response': recaptcha_response
}
r = requests.post('https://www.google.com/recaptcha/api/siteverify', data=data)
result = r.json()
if result['success']:
request.recaptcha_is_valid = True
else:
request.recaptcha_is_valid = False
messages.error(request, 'Invalid reCAPTCHA. Please try again.')
return function(request, *args, **kwargs)
wrap.__doc__ = function.__doc__
wrap.__name__ = function.__name__
return wrap
signup.html
<div class="form">
<form method="POST">
{% csrf_token %}
{{ form|crispy }}
<br>
<script src='https://www.google.com/recaptcha/api.js'></script>
<div class="g-recaptcha" data-sitekey="6LfzEg8gAAAAABcVpBvOjuLjs787K8_4Fu0N2wgu"></div>
<input type="submit" value="Sign Up">
</form>
</div>
Change your decorator to:
def wrap(request, *args, **kwargs):
request.recaptcha_is_valid = None
def wrap(obj, *args, **kwargs):
request = obj.request
request.recaptcha_is_valid = None
....
return function(obj, *args, **kwargs)
so it can works with django views.
In view put it before form_valid:
#check_recaptcha
def form_valid(self, form):
Related
I have a Django view and I want to send the request data to a form.
class PostDetailView(DetailView):
model = Post
form = CommentForm
def get_form_kwargs(self):
kwargs = super(PostDetailView, self).get_form_kwargs()
kwargs['user'] = self.request.user.username
return kwargs
def post(self, request, *args, **kwargs):
form = CommentForm(request.POST)
if form.is_valid():
post = self.get_object()
form.instance.user = request.user
form.instance.post = post
form.save()
return redirect(reverse("post-detail", args=[post.pk]))
from django import forms
from .models import Comment
from djrichtextfield.widgets import RichTextWidget
class CommentForm(forms.ModelForm):
user = forms.ChoiceField(required=True)
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user', None)
super(CommentForm, self).__init__(*args, **kwargs)
self.fields['user'].choices = self.user
content = forms.CharField(widget=RichTextWidget(attrs={
'class': 'md-textarea form-control',
'placeholder': 'Comment here ...',
'rows': '4',
}))
class Meta:
model = Comment
fields = ('author', 'content',)
<form method="POST" href="{% url 'post-detail' post.id %}">
{% csrf_token %}
<div class="form-group">
{{form | safe}}
</div>
<button class="btn btn-primary btn-block" type="submit">Comment</button>
</form>
urlpatterns = [
path('post/<int:pk>/', PostDetailView.as_view(), name="post-detail"),
]
According to the docs, get_form_kwargs allows key-value pairs to be set in kwargs. The kwargs is then passed into the form. The form's init function should then be able to pick up the user value from kwargs.
However, self.user returns None, and debugging showed that get_form_kwargs did not run at all.
I have two questions: how do functions in view classes get executed? And what is the correct method to pass data from a view to a form?
EDIT
I have refactored the comment feature into another view.
class AddCommentView(UpdateView):
model = Post
form = CommentForm
def post(self, request, *args, **kwargs):
form = CommentForm(request.POST)
if form.is_valid():
post = self.get_object()
form.instance.user = request.user
form.instance.post = post
form.save()
return redirect(reverse("post-detail", args=[post.pk]))
def get_form_kwargs(self):
kwargs = super(PostDetailView, self).get_form_kwargs()
kwargs.pop('instance', None)
kwargs['user'] = self.request.user.username
return kwargs
<form method="POST" href="{% url 'add-comment' post.id %}">
{% csrf_token %}
<div class="form-group">
{{form | safe}}
</div>
<button class="btn btn-primary btn-block" type="submit">Comment</button>
</form>
urlpatterns = [
path('post/<int:pk>/', AddCommentView.as_view(), name="add-comment")
]
However UpdateView cannot handle POST requests (405).
It's not explicitly described in the docs but get_form_kwargs is only triggered with a CreateView or an UpdateView.
In your case you can use UpdateView, and then use form_valid to do your post process. But note that we need to delete kwargs['instance'], because by default this view will think we are working with a Post object when in fact it's a Comment:
Try with this:
class PostDetailView(UpdateView):
model = Post
form = CommentForm
def get_form_kwargs(self):
kwargs = super(PostDetailView, self).get_form_kwargs()
# Remove the post object as instance,
# since we are working with a comment
kwargs.pop('instance', None)
kwargs['user'] = self.request.user.username
return kwargs
def form_valid(self, form):
instance = form.save(commit=False)
instance.user = self.request.user
instance.post = self.get_object() # returns the post
instance.save()
return redirect(reverse("post-detail", args=[post.pk]))
or if you don't want to use UpdateView (not recommended), you can just explicitly call get_form_kwargs when you build your form. You cannot call super().get_form_kwargs() though, since as discussed the parent class doesn't have this method:
class PostDetailView(DetailView):
model = Post
form = CommentForm
def get_form_kwargs(self):
kwargs = {'user': self.request.user.username}
return kwargs
def post(self, request, *args, **kwargs):
form = CommentForm(request.POST, **self.get_form_kwargs())
if form.is_valid():
post = self.get_object()
form.instance.user = request.user
form.instance.post = post
form.save()
return redirect(reverse("post-detail", args=[post.pk]))
Lastly, do consider renaming this class, since we are working with adding a comment to post, and not really about "post detail", so something like PostAddCommentView?
I want to display DetailView and independent form to send API request to the other website server. I made views.py but only i get is empty page. I'm trying to figure out how to adjust it for over past fiew days and still don't have any clue how to do this. Hope you will help me with this
views.py
class DetailPostDisplay(DetailView):
model = EveryPost
template_name = 'post/detailpost.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['form'] = DictForm()
return context
class DictWindowForm(SingleObjectMixin, FormView):
template_name = 'post/detailpost.html'
form_class = DictForm
model = EveryPost
def post(self, request, *args, **kwargs):
self.object = self.get_object()
return super().post(request, *args, **kwargs)
def get_success_url(self):
return reverse('detailpost', kwargs={'slug': self.object.slug})
class DetailPostList(View):
def get(self, request, *args, **kwargs):
view = DetailPostDisplay.as_view()
return view(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
view = DictWindowForm.as_view()
return view(request, *args, **kwargs)
HTML
I'm not sure whether action should be empty or include url DetailPostDisplay(require to pass slug, which i don't have how to get)
<form method="POST" action="">
{% csrf_token %}
{{ form }}
<input type="submit" class="btn btn-dark float-right mt-2" value="Tłumacz">
</form>
urls.py
from django.urls import path
from . import views
from .views import PostListPl, PostListRu, DetailPostDisplay
urlpatterns = [
path('', PostListPl.as_view(), name='index_pl'),
path('ru/', PostListRu.as_view(), name='index_ru'),
path('about/', views.about, name='about'),
path('<slug:slug>/', DetailPostDisplay.as_view(), name='detailpost'),
]
For the future generations, i mixed and overthinked it. If you want to just put form into DetailView, create def post and put logic there. Code below:
views.py
class DetailPostDisplay(DetailView):
model = EveryPost
template_name = 'post/detailpost.html'
def get_context_data(self, **kwargs):
context = super(DetailPostDisplay, self).get_context_data(**kwargs)
context['form'] = DictForm
return context
def post(self, request, *args, **kwargs):
form = DictForm(request.POST)
if form.is_valid():
self.object = self.get_object()
And later code to pass variables into template from form
context = super(DetailPostDisplay, self).get_context_data(**kwargs)
context['form'] = DictForm
context['word'] = request.POST.get('word')
return self.render_to_response(context=context)
I've been trying to render HTML using form I made in forms.py, however this is not working, just fail to loaded without error message. Also, there is no message in console too, so I'm having hard time to fix this. Please take a look and tell me which part is wrong.
This is urls.py
url(r'^profile/(?P<username>[-\w.]+)/$', views.profile, name='profile'),
url(r'^password_change/(?P<username>[-\w.]+)/$', views.password_change, name='password_change'),
url(r'^password_change_done/$', views.password_change_done, name='password_change_done'),
forms.py
class PasswordChangeForm(forms.Form):
oldpassword = forms.CharField(max_length = 20, widget=forms.TextInput(attrs={'type':'password', 'placeholder':'your old Password', 'class' : 'span'}))
newpassword1 = forms.CharField(max_length = 20, widget=forms.TextInput(attrs={'type':'password', 'placeholder':'New Password', 'class' : 'span'}))
newpassword2 = forms.CharField(max_length = 20, widget=forms.TextInput(attrs={'type':'password', 'placeholder':'Confirm New Password', 'class' : 'span'}))
class Meta:
model = User
fields = ("username",)
field_classes = {'username': UsernameField}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self._meta.model.USERNAME_FIELD in self.fields:
self.fields[self._meta.model.USERNAME_FIELD].widget.attrs.update({'autofocus': True})
def clean(self):
if 'newpassword1' in self.cleaned_data and 'newpassword2' in self.cleaned_data:
if self.cleaned_data['newpassword1'] != self.cleaned_data['newpassword2']:
raise forms.ValidationError(_("The two password fields did not match."))
return self.cleaned_data
def save(self, commit=True):
user = super().save(commit=False)
user.set_password(self.cleaned_data["password1"])
if commit:
user.save()
return user
views.py
#login_required
def password_change(request, username):
if request.method == 'POST':
form = PasswordChangeForm(data=request.POST, user=request.user)
print("username is "+username)
if form.is_valid():
#form.save()
update_session_auth_hash(request, form.user)
form.save()
print("A")
return HttpResponseRedirect('/blog/password_change_done/')
else:
update_session_auth_hash(request, form.user)
form.save()
print("B")
return redirect(reverse('blog:profile', args=[form.user.get_username()]))
else:
print("C")
form = PasswordChangeForm(user=request.user)#unbound
return redirect(reverse('blog:profile', args=[form.user.get_username()]))
profile.html (where password_change should be loaded.)
<h2>password change</h2>
{% if error_message %}
<p><strong>{{ error_message }}</strong></p>
{% else %}
{% endif %}
<form class="form-horizontal" role="form" action="{% url 'blog:password_change' user.username %}" method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="button-primary">password change</button></div>
</form>
Here is one of the examples how change_password view can be implemented
from django.contrib import messages
from django.contrib.auth import update_session_auth_hash
from django.contrib.auth.forms import PasswordChangeForm
from django.shortcuts import render, redirect
def change_password(request):
if request.method == 'POST':
form = PasswordChangeForm(request.user, request.POST)
if form.is_valid():
user = form.save()
update_session_auth_hash(request, user) # Important!
messages.success(request, 'Your password was successfully updated!')
return redirect('accounts:change_password')
else:
messages.error(request, 'Please correct the error below.')
else:
form = PasswordChangeForm(request.user)
return render(request, 'accounts/change_password.html', {
'form': form
})
Have a look at the part related to GET request. There you have to create the form and render the page with context. You should provide form to the context.
In your case you create the form and redirect to the profile page straight after that. That is why you see empty form page.
I'm trying to make register possible on the homepage, so I don't have a seperate URL to handle registration. I'm trying to send the form through get_context_data, however it's not working. Here's my code:
forms.py
class UserRegistrationForm(forms.ModelForm):
password = forms.CharField(widget=forms.PasswordInput)
class Meta:
model = User
fields = [
'username',
'password',
]
views.py
class BoxesView(ListView):
template_name = 'polls.html'
def get_context_data(self):
context = super(BoxesView, self).get_context_data()
# login
if self.request.method == 'POST':
form = UserRegistrationForm(self.request.POST or None)
context['form'] = form
if form.is_valid():
username = form.cleaned_data['username']
password = form.cleaned_data['password']
user = User.objects.create_user(username=username, password=password)
user.save()
return redirect('/')
else:
print(form.errors) #doesn't print anything
print(form.non_field_errors()) #doesn't print anything
print('Errors') #doesn't print anything
else:
form = UserRegistrationForm()
context['form'] = form
return context
def get_queryset(self):
pass
base.html
<form action="" enctype="multipart/form-data" method="post">{% csrf_token %}
<div class="registerBox">
{{ form.username }}
{{ form.password }}
<input type="submit" value="register"/>
</div>
</form>
So when I submit the form it gives this error: Method Not Allowed (POST): "POST / HTTP/1.1" 405 0
And it isn't creating a new User. Any idea what the problem is?
EDIT: Tried FormMixin, got this error: The view app.views.BoxesView didn't return an HttpResponse object. It returned None instead.
class BoxesView(ListView):
template_name = 'polls.html'
form_class = UserRegistrationForm
def post(self, request, *args, **kwargs):
form = self.get_form()
if form.is_valid():
username = form.cleaned_data['username']
password = form.cleaned_data['password']
user = User.objects.create_user(username=username, password=password)
user.save()
return redirect('/')
def get_context_data(self):
context = super(BoxesView, self).get_context_data()
context['form'] = self.get_form()
return context
def get_queryset(self):
pass
Ok I see the issue fix the indentation, your if statement should be inside the get_context_data function not outside ;)
You need to add post() method and FormMixin to your CBV like this:
class BoxesView(FormMixin, ListView):
template_name = 'polls.html'
form_class = UserRegistrationForm
# ...
def post(self, request, *args, **kwargs):
form = self.get_form()
if form.is_valid():
# ...
else:
# ...
return render(request, self.template_name, {'data': some_data})
I have an input field <input type="hidden" name="next" value="{{ next }}" />, but it wont replace {{ next }} with the value of the GET parameter. I can't find how to replace the value.
My view
class LoginView(FormView):
form_class = AuthenticationForm
redirect_field_name = REDIRECT_FIELD_NAME
template_name = 'user/login.html'
#method_decorator(csrf_protect)
#method_decorator(never_cache)
def dispatch(self, *args, **kwargs):
return super(LoginView, self).dispatch(*args, **kwargs)
def form_valid(self, form):
login(self.request, form.get_user())
return super(LoginView, self).form_valid(form)
def get_success_url(self):
if self.success_url:
redirect_to = self.success_url
else:
redirect_to = self.request.REQUEST.get(self.redirect_field_name, '')
if not redirect_to:
redirect_to = resolve_url(settings.LOGIN_REDIRECT_URL)
return redirect_to
Override method get_context_data on class LoginView.
def get_context_data(self, **kwargs):
context = super(LoginView, self).get_context_data(**kwargs)
context['next'] = self.request.REQUEST.get('next')
return context
This assumes that the GET parameter will have the next url where user should be redirected in param next. So, request should be of form localhost.com/login?next=/accounts/