Facebook, GitHub, and Twitter all place user profile URLs at the root of their URL hierarchies, e.g., http://twitter.com/jack.
This must be done so that other "system" URLs, like http://twitter.com/search are resolved first, so a user named #search can't hijack part of the site.
And if no system URL exists, and no such user profile is found, they must throw a 404.
What's the best way to achieve this using Django's URL routing? My current solution is:
urlpatterns = [
url(r'^admin/', admin.site.urls),
# etc, ..., then this last:
url(r'^(?P<username>.+)$', views.view_profile),
]
def view_profile(request, username):
try:
user = User.objects.get(username=username)
except User.DoesNotExist:
raise Http404('User does not exist')
return HttpResponse(username + ' exists!')
In this way, the view_profile view is a catch-all for any URL that isn't handled elsewhere. However, it doesn't seem ideal that it is responsible for throwing a 404 exception if no user exists, I'd rather raise some "wrong route" signal that tells Django's URL router to resume attempting to route the request. This way the 404 is generated by Django the same way as if I did not have the catch-all route.
This question asks essentially the same thing, but the solutions involved creating a subpath for the user profile.
I would actually recommend a more RESTfull way of addressing the users profile. A nice organisation or a users profile rout would be /users/{pk}/ or if you want it easier for the client (we are doing it like that) /users/me/ so the client doesn't need to rememer it's id and the user is choosen from the session or auth/bearer token or whatever authorisation system you prefer.
Related
I'm looking for ideas on the most elegant way to force a user to define a username immediately after they verify their email and log in for the first time. Alternatively, they click the verification email, are not logged in, and must enter a username first to be able to log in for the first time.
My requirements are that the username not be in the registration form, but instead be on its own template page immediately after the user logs in for the first time. Once they define a username, they would not see this page again.
I'm using class-based views so I think that rules out decorators.
I've researched:
User-level Permissions (can't view any page until you provide a username)
Using the is_active boolean (user is not considered active until they provide a username)
PermissionRequiredMixin (add to every class that a logged-in user could potentially see)
UserPassesTestMixin (add to every class that a logged-in user could potentially see)
AccessMixin (add to every class that a logged-in user could potentially see)
Add my own boolean field to my custom User model
In every view, check if username is null, if it is, redirect to username form page (doesn't seem like an elegant approach)
user_logged_in signal (couldn't someone still bypass the username form page)
Middleware somehow?
My Concern
User verifies email, logs in for the first time, lands on the username page. I want them to create a username on this page before being allowed to go on to any other page. I don't want them logging in, and potentially pasting a different URL in the address bar and side-stepping this step.
I'd like to avoid...
Adding validation to every single view that an authenticated user would have access to.
What I'm trying to do is similar to forcing someone to agree to a "Terms of Service" before continuing to use a website. I need them to choose a username.
Just hoping that someone experienced with this would lend some advice. I'd like to avoid a discussion of "why don't you just add the username field to the registration form". The answer is, it's not what I want.
I fully realize this question is broad and asking for suggestions, not code-specific. I usually ask detailed code-specific questions but this one, I just don't know the best way to approach. Sorry in advance.
The answer was definitely middleware. It basically looked like this. I made a new Python file named middleware.py in my app, made this class, then modified my settings.py MIDDLEWARE area to include this class.
from django.shortcuts import redirect, reverse
class SimpleMiddleware:
def __init__(self, get_response):
self.get_response = get_response
# One-time configuration and initialization.
def __call__(self, request):
# Code to be executed for each request before
# the view (and later middleware) are called.
response = self.get_response(request)
if request.user.is_authenticated:
if request.user.username is None:
print(request.path)
if not request.path == reverse('choose_username'):
return redirect(reverse('choose_username'))
return response
I want to allow a user to delete his account and upon deletion, I want the user to be logged out and see a html page account_deleted.html for confirmation.
students/views.py:
def delete_account(request):
user = User.objects.get(username=request.user)
user.delete()
context = {
"deleted_msg": "Account has been deleted",
}
return render(request, "students/account_deleted.html", context) and redirect("students:logout")
For logout, I'm using the built-in LogoutView function. The logout redirect URL in my settings.py is set to my home page.
students/urls.py:
path('logout/', LogoutView.as_view(), name='logout'),
In case of an account deletion, how can I make the delete_account(request) function return a render and redirect at the same time? Is that even possible?
Thanks!
You can log the user out before deleting the account by placing logout(request) before your user.delete() line. You will need to import it with from django.contrib.auth import logout.
As Bruno said in his answer, using a redirect instead of render is preferable after a POST request, and you should update your view to only respond to POST requests.
If you are having trouble with your website crashing after a user is deleted, make sure you are using the proper access control in all your views, eg by using the #login_required decorator or the equivalent mixin on all views that require a user to be logged in. If you do this the user will just be redirected to the login page if he or she is not logged in instead of crashing your site.
First things firsts: your view should 1/ only accept logged in users and 2/ only accept POST requests (you definitely dont want a GET request to delete anything from your database). Also, this:
User.objects.filter(username=request.user)
is useless - you already have the current user in request.user - and potentially dangerous if your auth backend allows for duplicated usernames.
and this:
return render(request, "students/account_deleted.html", context) and redirect("students:logout")
is of course plain wrong. A view returns one single HTTP response, you can't return two (it wouldn't make any sense), and you can't "and" two responses together (well, you can but the result is certainly not what you expect - read the FineManual about the and operator).
The proper solution is to 1/ manually log out the user (cf voodoo-burger's answer), 2/ use the messages framework to inform the user her accont has been deleted, and 3/ redirect to the home page (you ALWAYS want to redirect after a successful post, cf https://en.wikipedia.org/wiki/Post/Redirect/Get for the why).
I've looked for an approach to add specific urls to a certain app. And restrict the urls for being used on other apps in Django.
I use Mezzanine and when a user goes to sub.domain.com he will see templates that are specific to that site. But when the user tries to go to the url on sub.domain.com/example he will see the url that is intended to be on domain.com/example. I want that to be a 404 for the user instead on the app.
Sorry for my bad english, hope you understand what I'm talking about.
You can get the current domain name (that the user is accessing) from request.META['HTTP_HOST']. Then based on that, raise 404 when the url is visited on certain domains.
from django.http import Http404
def my_restricted_view(request):
domain = request.META['HTTP_HOST']
if domain == 'sub.domain.com':
return render(request, "template_name", {})
else:
raise Http404
Currently I wrote a method which authenticates a user using my company list of groups by getting the user using
request.meta['REMOTE_USER']
and comparing it to list of users in the group
My question is that how would I tie this in to my Django application such that it would check the users only for all urls except for a whitelist. I was thinking of calling this method in my views.py for the urls I needed but that seems to be maintenance nightmare
You could implement a middleware that would check a user against the url except the whitelist:
class MyAuthMiddleware(object):
def process_request(self, request):
if request.path in self.whitelist:
return
# Do the user checking otherwise
I'm trying to set up a custom backend that queries another database, for which I have created a model in the system. It uses its own rules (email instead of username, and a differently salted/hashed password) so I can't use built in authentication. I've set up a custom authentication backend like so:
class BlahBlahBackend:
def check_password():
# check password code here
return true
def authenticate(self, email=None, password=None):
import myapp.models.loginmodel
try:
person = myapp.models.loginmodel.People.objects.get(email=email)
if check_password(password, person.password):
try:
user = User.objects.get(email=email)
except User.DoesNotExist:
username=person.first_name + person.last_name
name_count = User.objects.filter(username__startswith = username).count()
if name_count:
username = '%s%s'%(username, name_count + 1)
user = User.objects.create_user(username,email)
else:
user = User.objects.create_user(username,email)
except People.DoesNotExist:
return None
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
I've added BlahBlahBackend as an authentication backend:
AUTHENTICATION_BACKENDS = ('django.contrib.auth.backends.ModelBackend',
'socialauth.auth_backends.OpenIdBackend',
'socialauth.auth_backends.TwitterBackend',
'socialauth.auth_backends.FacebookBackend',
'socialauth.auth_backends.BlahBlahBackend',
)
As you can see, I'm also using some pre-existing auth backends that are also in socialauth.
I have a submission form that points to the following view:
def blahblah_login_complete(request):
email = request.POST.get('email')
password = request.POST.get('password')
user = authenticate(email,password)
# if user is authenticated then login user
if user:
login(request, user)
else:
return HttpResponseRedirect(reverse('socialauth_login_page'))
However, when I try to login in this way, it seems like one or more of the other backends are acting as if I'm trying to log in using their method.
I read that backends are cached and so ran
Session.objects.all().delete()
to clear out the backends cache.
My main questions are:
Does the order in which items are listed in AUTHENTICATION_BACKENDS
How does the system decide/know which Backend to use? This was never made clear by any of the documentation, and I find it a bit confusing.
Is there any way to force the use of a specific authorization based on the request. In other words, if someone submits a form, is there a way to force them to use the form-login-based authentication as opposed to the login via openid or Twitter?
Update:
It works! This is very cool, thanks. I guess it just seemed like the django doc was saying "You don't have to do anything else, it just sort of works like magic" and it turns out this is absolutely the case. So long as the backend is there and the credentials are set up correctly, the authentication will work. As it turns out the real problem was a misconfiguration in the urls.py file that wasn't sending the post from the login form to the correct handler, which is why it kept trying to use another authentication method.
You're supposed to use keyword arguments to django.contrib.auth.authenticate() The names should match the names of the arguments in your backend's authenticate method. The default backend handles the names 'username' & 'password'.
Your backend can use a different name for the keyword arguments e.g.: blahblah_email and blahblah_password, and then call authenticate(blahblah_email=..., blahblah_password=...).
It's clearly described here -
django tries each backend in order
defined, if first fails to
authenticate it goes to second etc.
I believe you can load backend class dynamically and authenticate
directly through it. Look at django authenticate() function sources on how to do that.
I guess django-cas will be a good reference for you :)
And yes, the order of AUTHENTICATION_BACKENDS matters.
Django loops over the backends list and stop at the first backend that has a authenticate method accepting the credential parameters you passed to it.