I'm creating a video sharing application. On my video page, I have allowed users to post comments and to delete comments.
def video_content(request, video_id):
video = get_object_or_404(Video, pk=video_id)
....
return render(
request,
'video-content.html',
context={
'video': video,
}
)
I'm obviously omitting a lot of things in the code.
I also have a comment handler function
def add_comment(request, video_id):
video = get_object_or_404(Video, pk=video_id)
if request.method == 'POST' and request.user.is_authenticated:
# Get comment and save it
return HttpResponse()
On my video page, I have a form:
<form action="/comment/add/{{video.id}}" method="post">
<input type="text"></input>
<button type="submit">Comment</button>
</form>
All of this works fine. When the user inputs a comment and submits the form, the add_comment function is successfully called just like its supposed to, and the comment is saved. The video page does not reload, which is what I want, but the URL on the top bar changes. How can I prevent that from happening?
All you need to do, is redirect the user to the view that you want.
def add_comment(request, video_id):
video = get_object_or_404(Video, pk=video_id)
if request.method == 'POST' and request.user.is_authenticated:
# Get comment and save it
return redirect("video_content",video.id)
# return HttpResponse() # this, is not correct
return render(request,"template_name.html",{})
Related
I made a research here in Stack and my problem is the opposite of the majority, I saw some ways to make it appear, but my problem is that it's appearing when the user hits the "Register" button / Refresh the register page. So it's an annoying thing that appears wherever the user enter/refresh the page because the form is empty.
View.py
#unauthenticated_user
def register(request):
form_u = CreateUser(request.POST)
form_c = CreateClient(request.POST)
if request.method == 'POST':
form_u = CreateUser(request.POST)
form_c = CreateClient(request.POST)
if form_u.is_valid() and form_c.is_valid():
user = form_u.save()
group = Group.objects.get(name='func')
user.groups.add(group)
client = form_c.save(commit=False)
client.user = user
client.save()
return redirect('login')
else:
form_u = CreateUser()
form_c = CreateClient()
context = {'form_u': form_u, 'form_c': form_c}
return render(request, 'register.html', context)
HTML
<form method="POST" action="" id="ativa">
{% csrf_token %}
...
</form>
{{form_u.errors}}
{{form_c.errors}}
<div class="mt-4">
<div class="d-flex justify-content-center links">
Have an account ? Login
</div>
</div>
Print
P.S: The site is in portuguese, but I can share the form link in heroku
Your logic is opposite of what you want:
Initialize the forms with POST data regardless of whether the request is a POST or a GET request, which will result in the errors if there is no POST data.
Then you initialize empty forms when the form data is invalid.
Instead you'll want to pass POST data only if the request is a POST request, and you should initialize empty forms only if the request is not a POST request:
#unauthenticated_user
def register(request):
# If request is POST, validate forms and add objects.
if request.method == 'POST':
form_u = CreateUser(request.POST)
form_c = CreateClient(request.POST)
if form_u.is_valid() and form_c.is_valid():
user = form_u.save()
group = Group.objects.get(name='func')
user.groups.add(group)
client = form_c.save(commit=False)
client.user = user
client.save()
return redirect('login')
# We can remove the else statement here,
# because the function either redirects or resumes
# normal flow and renders the template
# with the form errors.
else:
# Only initialize empty forms when no POST request was made.
form_u = CreateUser()
form_c = CreateClient()
context = {'form_u': form_u, 'form_c': form_c}
return render(request, 'register.html', context)
Is it possible to have 2 redirect() in the same django view. so when the like button is in the home page, i want it to redirect back to home page, if like button is in detail page, i want to redirect back to detail page?
For instance:
def LikeView(request, slug):
context = {}
post = get_object_or_404(BlogPost, slug=slug)
post.likes.add(request.user)
if in homepage:
return redirect('HomeFeed:detail', slug=slug)
else:
return redirect('HomeFeed:main')
def delete_blog_view(request,slug):
context = {}
user = request.user
#YOU WANT to check if user is authenticated. if not, you need to authenticate! redirect you to that page
if not user.is_authenticated:
return redirect("must_authenticate")
account = Account.objects.get(pk=user_id)
blog_post = get_object_or_404(BlogPost, slug=slug)
blog_post.delete()
return redirect(request.META.get('HTTP_REFERER', 'account:view', kwargs={'user_id': account.pk }))
Pass the redirect URL in a next URL param. Like so:
<!-- In homepage template -->
Like
<!-- in Detail template -->
Like
or simply:
Like
To always pass the current URL as the redirect URL.
And then in your LikeView:
def LikeView(request, slug):
...
next = request.GET.get("next", None)
if next and next != '':
return HttpResponseRedirect(redirect_to=next)
# Then have a default redirect URL in case `next` wasn't passed in URL (Home for Example):
return HttpResponseRedirect(redirect_to="/home")
As mentioned in the Django Docs, this isn't safe (for most apps), so you have to check if URL is safe then redirect to next otherwise just return a default safe in-app URL.
Read on the url_has_allowed_host_and_scheme function to check if URL is safe on this Docs page
I have multiple forms to be shown everywhere in my project and hence I read that having a context_processor was the best way to do it. So, I created one inside my app and it looks something like this:
def forms_processor(request):
name_form = NewNameForm()
work_form = NewWorkForm()
address_form = NewAddressForm()
context = {'name_form': name_form,
'work_form': work_form,
'address_form': work_form,
}
return context
This works great, I can just use {{name_form}} anywhere in my templates and that renders the form.
Now my question is, where do I validate the form? In my views.py or the context_processors.py? Right now my views for name_form looks something like this:
def user_profile(request):
if request.method == 'POST':
name_form = NewNameForm(request.POST)
if name_form.is_valid():
form.save()
else:
ctx = {'title': 'Profile', 'active_tab': 'Profile'}
return render (request, 'user_profile.html', ctx)
This isn't working actually, if I submit an invalid form, it just comes back to the same page and won't show a populated form.
If someone could guide me or redirect me to some docs on this topic, that'd be awesome! Thanks!
The problem is that your processor instantiates the form on each render. Each time you call render, your processor is called, which instantiates a new form and displays THAT form, not the form instance that you created in the view. Therefore, the form being rendered is a blank instance but the form that contains the input and errors was destroyed by garbage collection after finishing your view.
A way I would do this, is passing the form you create in the view back to context before rendering. Pass it in to a context key such as "name_form_filled". Then if that variable is present in the context, don't render "name_form", instead render "name_form_filled".
views.py
def user_profile(request):
ctx = {}
if request.method == 'POST':
name_form = NewNameForm(request.POST)
if name_form.is_valid():
name_form.save() # you named this named_form, not form.
# If you want to redirect to another view when the form is saved successfuly, do it here.
else:
ctx["name_form_filled"] = form
else:
ctx.update({'title': 'Profile', 'active_tab': 'Profile'})
return render (request, 'user_profile.html', ctx)
user_profile.html
<div id="form_container">
{% if name_form_filled %}
<!-- Render form that has input and errors from previous POST. -->
{{ name_form_filled }}
{% else %}
<!-- render empty initial form. User has not attempted to submit yet. -->
{{ name_form }}
{% endif %}
</div>
===========================================================================
Another way you could do this is turn this view into a class based view and inherit a base class based view. This base class will override the get_context_data method and add your three forms. Note that you won't be using the context processor with this methodology so you could get rid of it if wanted in this case.
All views that use your form will extend the base view class. Then, after evaluating your form, if it is invalid, overwrite your name_form context key with the invalid form instance, which will be in your context.
views.py
class BaseView(View):
def get_context_data(self, *args, **kwargs):
context = {
"name_form": NewNameForm(),
"work_form": NewWorkForm(),
"address_form": NewAddressForm()
}
return context
class UserProfileView(BaseView):
def get(self, request, *args, **kwargs):
# Do GET logic here.
ctx = self.get_context_data(*args, **kwargs) # BaseView.get_context_data will be called here unless you override it in this class.
ctx.update({'title': 'Profile', 'active_tab': 'Profile'})
return render (request, 'user_profile.html', ctx)
def post(self, request, *args, **kwargs):
# Do POST logic here.
ctx = self.get_context_data(*args, **kwargs) # BaseView.get_context_data will be called here unless you override it in this class.
name_form = NewNameForm(request.POST)
if name_form.is_valid():
name_form.save()
else:
ctx["name_form"] = name_form # will replace the empty form in context with the form instance created in name_form that has input and errors.
return render (request, 'user_profile.html', ctx)
user_profile.html
<div id="form_container">
<!-- Will render whatever is in name_form. If this is after the
user has submitted an invalid form, this form will be populated with input and errors because we overwrote it in the view. -->
{{ name_form }}
</div>
===========================================================================
I personally think that the first solution is the best but when you start getting more complex, you should probably switch over to the second solution as class based views make complex views way easier.
Direct answer: you validate the form in views.py with is_valid() method. What you need is to populate context with bound form if the form is invalid:
def user_profile(request):
ctx = {'title': 'Profile', 'active_tab': 'Profile'}
if request.method == 'POST':
name_form = NewNameForm(request.POST)
if name_form.is_valid():
form.save()
return redirect(YOUR_REDIRECT_URL) # Always redirect after successful POST
ctx['form'] = form # if form is invalid return it with context
return render (request, 'user_profile.html', ctx)
Read more in documentation.
I have a user sign up and login template set to send information to the same view (detail). They were both working fine before, however now the redirect on user creation is no longer going to the correct URL (http://127.0.0.1:8000/accounts/login/?next=/accounts/21/) the "accounts/login?next=" portion of the URL is being added for some reason and I cannot understand where it came from as it was not there before.
I'm using stronghold which makes every view login_required unless noted otherwise with #public above it.
I have found some posts about LOGIN_URL needs to be set in setting.py or a next key. However this was working fine before so I do not think that is the problem. let me know if you need more code posted and I will put it up.
Thanks,
-the route I want to hit is
url(r'^accounts/(?P<user_id>\d+)/$', views.detail, name='detail')
-my register view is below
#public
def register(request):
if request.method == 'POST':
form = EmailUserCreationForm(request.POST)
if form.is_valid():
new_user = form.save()
playthrough = PlayThrough(user_id=new_user.id)
playthrough.save()
request.session['user_id'] = new_user.id
return HttpResponseRedirect('/accounts/{}/'.format(new_user.id))
else:
form = EmailUserCreationForm()
return render(request, 'dep_server/register.html', {
'form': form,
})
-this is he view that is supposed to render the user info
def detail(request, user_id):
if request.session['user_id'] == int(user_id):
user = EmailUser.objects.get(id=user_id)
module_list = ModuleRef.objects.all()
return render(request, 'dep_server/detail.html', {
'user': user,
'module_list': module_list
})
else:
return HttpResponseRedirect('/accounts/auth/')
I figured out the problem, I was not logging in the user on creation. Which is why the login worked and the sign up did not. below is the code that I added to the register view, which got it to work.
user = authenticate(
email = form.cleaned_data['email'],
password = form.cleaned_data['password2']
)
login(request, user)
I have a class based view in which I process the form and redirect the user on successful submission like so:
views.py
def get(self,request):
form = self.form_class()
return render(request, template_name, { 'form' : form })
def post(self, request, *args, **kwargs):
form = self.form_class(request.POST)
if form.is_valid():
...
return HttpResponseRedirect(reverse('success'))
return render(request, template_name, { 'form' : form })
urls.py
...
url(r'^submit/success', SubmitView.as_view(), name='success'),
...
It is possible to access url directly by typing success/submit. I don't use any authentication on the site and want the user only be able to access the submit/success page after redirection, so that they are not able to access it directly. How do I do it?
If you are using sessions, you can accomplish it like so:
# in the view where form is submitted
if form.is_valid():
request.session['form-submitted'] = True
return HttpResponseRedirect(reverse('success'))
# in the success view
def get(self, request):
if not request.session.get('form-submitted', False):
# handle case where form was not submitted
else:
# render the template
Instead of redirecting, you could POST to the 'success' page.
Then use if request.method == 'POST':
But beware, this is NOT secure, as headers can be spoofed.
Better to just call the success view from within the POST method, I think.
Have you tried something like this:
if form.is_valid():
...
return HttpResponseRedirect(SubmitView.as_view())
Not sure if this works out of the box, but with a few more tricks you might get what you want.
To add to the answer #miki725 posted I would also make sure you change
request.session['form-submitted'] = False
after you have entered the
if not request.session.get('form-submitted', False):
In order to prevent accessing the page directly or using the back and forward on the browser.