try/except in django, good practices - django

This part of code represents a index of my web page
eg: 127.0.0.1:8000/
def IndexView(request):
try:
profile = request.user.get_profile()
except User.DoesNotExist:
return render_to_response('index.html',
{'request': request,},
context_instance=RequestContext(request))
return render_to_response('index.html',
{'request': request, 'profile' : profile},
context_instance=RequestContext(request))
Why i'm still getting this error on debug?
AttributeError at /
'AnonymousUser' object has no attribute 'get_profile'
thanks in advance

You need to add this check:
from django.shortcuts import render
def index_view(request):
if request.user.is_authenticated():
profile = request.user.get_profile()
return render(request,'index.html',{'profile': profile})
else:
return redirect('login/')
Or, you can use the built-in decorator which ensures that your view is only called with a logged in user:
from django.contrib.auth.decorators import login_required
from django.shortcuts import render
#login_required
def index_view(request):
profile = request.user.get_profile()
return render(request,'index.html',{'profile': profile})
If you use the login_required decorator you need to make sure you have a LOGIN_URL setting that points to the view that handles the login form for your site. By default this is /accounts/login/
I changed the method name to lowercase as CamelCase is usually for classes in Python. Also, you don't need two return statements since you are rendering the same template. Instead, in your index.html template you can do:
{% if profile %}
You have a profile!
{% else %}
You don't
{% endif %}
Tips and tricks with authentication are listed at the authentication entry in the documentation.

Because a user isn't currently logged in. When you call get_profile it gets called on a default user provided by Django called AnonymousUser which doesn't have a get_profile method as it isn't a real user. It doesn't throw an exception I am assuming and hence you get an error.
Try the same thing after logging in and it should be fine.

You are expecting a User.DoesNotExist error, but instead the code is throwing an AttributeError. This is because request.user is a real object -- an AnonymousUser object -- and accessing it does not cause an error, but an AnonymousUser has no profile.
Use the #login_required decorator instead, or catch the AttributeError, or adjust your code so that it actually does throw User.DoesNotExist.

You need to check if the user is authenticated before you try to pull the profile through get_profile. You can also simplify your code quite a bit:
def IndexView(request):
profile = request.user.is_authenticated() and request.user.get_profile()
return render_to_response('index.html',
{'request': request, 'profile': profile},
context_instance=RequestContext(request))
You don't need the except, because the User with always be there.

You are getting this error because this specific user is not logged in.
To prevent the error either use the #login_required decorator just over your view:
from django.contrib.auth.decorators import login_required
#login_required
def IndexView(request):
...
or:
def IndexView(request):
if request.user.is_authenticated(): #check to use if user is logged in
try:
profile = request.user.get_profile()
except User.DoesNotExist:
return render_to_response('index.html',
{'request': request,},
context_instance=RequestContext(request))
return render_to_response('index.html',
{'request': request, 'profile' : profile},
context_instance=RequestContext(request))

Just to let you know, that's not very dry code.
check it:
def index_view(request):
context_data = {'request': request}
try:
context_data.update('profile': request.user.get_profile())
except AttributeError:
pass
return render_to_response('index.html', context_data,
context_instance=RequestContext(request))
Also, I'd double check and make sure request isn't already included in context. You may not have to specifically put it into context here.
EDIT: Forgot to pass context_data to render_to_response, example updated.

Related

How to use different views for a certain url base on authentication?

When a request come for /page-one url, I'd like to use view_a if the user is authenticated and view_b for the guest visitors.
The code should be like this:
def dummy(request):
if request.user.is_authenticated():
print 'authuser'
return view_a(request)
else:
print 'unauth user'
return view_b(request)
How can I achieve this in django?
I've looked at the docs but could not find any relevant guides about this.
You can use is_authenticated
Use this in views
if user.is_authenticated:
return render(request, 'polls/detail.html', {'poll': p})
else:
return render(request, 'polls/another-detail.html', {'poll': p})
To redirect to different views (not just render different templates)
from django.urls import reverse
from django.http import HttpResponseRedirect
...
if user.is_authenticated:
return HttpResponseRedirect(reverse('some_detail', kwargs={'pk': pk}))
else:
return HttpResponseRedirect(reverse('another_detail', kwargs={'pk': pk}))
and in your urls you would have to 'name' the view
path('another_detail/<int:pk>/', views.another_detail, name='another_detail'),

Automatic HTTPRedirect in Django view if user is authenticated?

I have successfully made it so the user must log in to view their profile, however, I only want the user to be able to view their profile and no one else.
Previously, they could visitwww.websitename.com/user/admin
as well as www.websitename.com/user/test and it would bring up the data for the profile each time of the logged in user.
The URL to visit is www.websitename.com/user/usernameofcurrentuser
Profile Page View
def profile_page(request, username):
context = RequestContext(request)
if request.user == username:
if request.user.is_authenticated():
user = User.objects.get(username=username)
taskitems = request.user.taskitem_set.all()
return render_to_response('profile.html', {}, context)
else:
return render_to_response('login.html', {}, context)
else:
return render_to_response('login.html', {}, context)
However, even though the user is logged in, it's redirecting them to the sign in page. I know the user is logged in because it prints their name on the login page, yet it's not redirecting them to the profile associated with the username.
You can try this :
def profile_page(request, username):
if request.user.is_authenticated():
if request.user.username == username:
# removed user since already in request.user and available in template as 'user'
# removed taskitems since directly available in template as 'user.taskitem_set.all'
return render(request, 'profile.html')
else:
return HttpResponseRedirect(reverse('profile_page', args=(request.user.username,)))
else:
return render(request, 'login.html')
Remove the username parameter since each user should only view their own profile. You can also use the login_required decorator to remove the extra conditional:
from django.contrib.auth.decorators import login_required
from django.shortcuts import render
#login_required
def profile_page(request):
return render(request, 'profile.html')
Make sure to set LOGIN_URL in your settings.py so unauthenticated users get redirected to the right spot.

Django 1.6 - reusable view with redirect

I have just made reusable comments app that can be attached to any model, please see view I have problem with ( I didn't include whole logic, only the one relevant to the question).
When there is no request.POST, all is working fine, however when I try to add the comment, after it is saved there is some problem with redirect, I am getting error
dictionary update sequence element #0 has length 0; 2 is required
problematic line is context.update(dictionary). It looks like it is empty when comment is added, but I don't understand why.
My logic is this:
when there is no request.POST, view add_comment will return
{'comment_form': comment_form, 'comments': comments}
when request.method==POST' , context.update(dictionary) shouldn't
be even executed, because of return redirect(node). It should result
in starting code executing in view profile, because that's where
redirect(node) should lead to.
I know I could just use redirect in profile.views.py, but then I will need to do this for every view with added comments, which is extremely unconvenient.
comment.views.py
from django.shortcuts import redirect
from comment.forms import AddCommentForm
from comment.models import Comment
def add_comment(request, node):
if request.user.is_authenticated():
user = request.user
else:
user = None
comment_form = None
comments = Comment.objects.get_comments(node) # custom manager method for getting all comments
if user:
if request.method == 'POST':
comment_form = AddCommentForm(request.POST)
if comment_form.is_valid():
comment_form.save(node=node, user=user) # custom form save method, updating missing fields
return redirect(node) #redirect to node.get_absolute_url()
else:
comment_form = AddCommentForm()
return {'comment_form': comment_form, 'comments': comments}
profile.views.py - another app, I want to reduce the code for adding comment by only referring to view add_comment
from django.shortcuts import render, get_object_or_404
from django.contrib.auth.models import User
from comment.views import add_comment
def profile(request, id):
user = get_object_or_404(User, id=id)
dictionary = add_comment(request, user)
context = {'user': user}
context.update(dictionary) #problematic line
return render(request, 'profile/profile account.html', context)
The problem is that add_comment can return something that isn't a dictionary: that is, a redirect (which is a subclass of HttpResponse). You could always check the type of what is returned before using it:
result = add_comment(request, user)
if not isinstance(result, dict):
return result
else:
context.update(result)

Username in URL for Django User Profile

I have a Django project that uses profiles for user information. Things are somewhat working except for one aspect... Here are code snippets to describe my problem.
In the template:
<li>Profile</li>
In views.py
class UserProfileView(View):
#method_decorator(login_required)
def get(self, request, user):
profile = get_object_or_404(UserProfile, user=request.user)
return render(request, 'accounts/profile.html', {'profile': profile})
In urls.py
url(r'^accounts/(?P<user>.+)/profile/$',
UserProfileView.as_view(),
name='user_profile_view'
),
I've tried variations for the named group, and this is what I found to work. The problem is, I can use any string in between /accounts/ and /profile/ (obviously) and it works. What I want to accomplish is to have only the current user's username be valid in the URL and otherwise throw a 404.
Do you really need the user parameter in the profile URL? If you only want it work for the current user, then why not simply drop the user parameter:
# urls.py
url(r'^accounts/profile/$',
UserProfileView.as_view(),
name='user_profile_view'
),
# views
class UserProfileView(View):
#method_decorator(login_required)
def get(self, request):
profile = get_object_or_404(UserProfile, user=request.user)
return render(request, 'accounts/profile.html', {'profile': profile})
In the code you posted, the UserProfileView.get method was not using the user parameter anyway.
UPDATE
If you want to keep the user parameter and make it work the way you want, you can change the view like this:
from django.http import Http404
class UserProfileView(View):
#method_decorator(login_required)
def get(self, request, user):
if request.user.username == user:
profile = get_object_or_404(UserProfile, user=request.user)
return render(request, 'accounts/profile.html', {'profile': profile})
else:
raise Http404
Btw, since user in the parameter list of the get method is really just the username as opposed to a user object, it would be better to rename it to username, to avoid confusion.

Handling form from different view and passing form validation through session in django

I have a requirement here to build a comment-like app in my django project, the app has a view to receive a submitted form process it and return the errors to where ever it came from. I finally managed to get it to work, but I have doubt for the way am using it might be wrong since am passing the entire validated form in the session.
below is the code
comment/templatetags/comment.py
#register.inclusion_tag('comment/form.html', takes_context=True)
def comment_form(context, model, object_id, next):
"""
comment_form()
is responsible for rendering the comment form
"""
# clear sessions from variable incase it was found
content_type = ContentType.objects.get_for_model(model)
try:
request = context['request']
if request.session.get('comment_form', False):
form = CommentForm(request.session['comment_form'])
form.fields['content_type'].initial = 15
form.fields['object_id'].initial = 2
form.fields['next'].initial = next
else:
form = CommentForm(initial={
'content_type' : content_type.id,
'object_id' : object_id,
'next' : next
})
except Exception as e:
logging.error(str(e))
form = None
return {
'form' : form
}
comment/view.py
def save_comment(request):
"""
save_comment:
"""
if request.method == 'POST':
# clear sessions from variable incase it was found
if request.session.get('comment_form', False):
del request.session['comment_form']
form = CommentForm(request.POST)
if form.is_valid():
obj = form.save(commit=False)
if request.user.is_authenticated():
obj.created_by = request.user
obj.save()
messages.info(request, _('Your comment has been posted.'))
return redirect(form.data.get('next'))
else:
request.session['comment_form'] = request.POST
return redirect(form.data.get('next'))
else:
raise Http404
the usage is by loading the template tag and firing
{% comment_form article article.id article.get_absolute_url %}
my doubt is if am doing the correct approach or not by passing the validated form to the session. Would that be a problem? security risk? performance issues?
Please advise
Update
In response to Pol question. The reason why I went with this approach is because comment form is handled in a separate app. In my scenario, I render objects such as article and all I do is invoke the templatetag to render the form. What would be an alternative approach for my case?
You also shared with me the django comment app, which am aware of but the client am working with requires a lot of complex work to be done in the comment app thats why am working on a new one.
I dont see the problem with security, except situation when you using cookies for stroring session. The performance depends on what kind of session backand you are using as well. But I cant find the point why are you complicating things!
And I dont thing that touching session in template tag is a good idea at all.
And maybe Take a look at django Comments Framework
Update:
Ok. I cant see the problems in this approach except complication. For example in my project, i'm using ajax to send data and validate it right in the comments view, therefore I do not require to redirect to original page. Other thing is that I pass the initialized Form in article view, so i'm not using templatetags.
Can provide you with my approche for example purposes:
from forms import CommentForm
from models import Comment
from django.http import HttpResponseForbidden, HttpResponse
from django.shortcuts import get_object_or_404, redirect, render
from django.utils import simplejson
from etv_persons.person.models import Person
from django.contrib import messages
def create_comment(request,slug):
if request.method != 'POST' or not request.POST or request.user.is_anonymous():
return HttpResponseForbidden('Доступ запрещен')
person = get_object_or_404(Person,slug=slug)
form = CommentForm(data=request.POST)
if form.is_valid():
Comment.objects.create(user_id=request.user.id, person=person,text=form.cleaned_data['text'])
if request.is_ajax():
msg={'msg': 'Cement was send',}
else:
messages.info(request, 'COmment was send.')
else:
if request.is_ajax(): msg={'msg': 'Error.',}
else: messages.info(request, 'Error.')
if request.is_ajax():
return HttpResponse(simplejson.dumps(msg),content_type='application/json')
else:
return redirect('person_details',**{"slug":slug,"ptype":person.type})
And in the article view we just do:
response['comment_form'] = CommentForm()
And yes, I do not validate the comments form. There is no reason. Just one text input.