i have custom user model with email as unique identifier for auth instead of username. i want register a user by email or mobile number. if user enter email address, then register user by activation link, and if user enter phone number then register by SMS OTP.
something like instagram registration:
https://www.instagram.com/accounts/emailsignup/
i found this topic but the answer is not explained well.
A username is required for a User object. But, you can make that username their email too, so it's not a problem (at least in django 2.x, not sure about 1.x). You didn't describe what the app you're making was for, it's purpose, etc., so before you skip to the code, read the following warnings and thought process of why I am giving you a better option.
It's a very bad idea to force the person to have the username equal to their email, because in the future you might want to add some other functionality.
For example: Maybe you'd want to make a message board so people can talk to each other. But because of bad planning from the beginning, everyone would see each other by their email. Technically, you could asign them all a bunch of random usernames they didn't make up, but that's not a good idea cause they're less likely to remember it, and they might not like it. The only good use of a name like sPaRkLe_DaNcEr12 or Poothtaste, is for making people rage quit in video games.
So if you wanted a future ability for users to talk to each other, it would be better to only show their username to other users, but ALLOW people to log in with their email or phone number if they wanted to. This way, now they can login with their (username) or (email) or (phone_number), and they only have to remember one of those. I will show you how soon.
Some downsides of this: It makes more queries to your database, which can make it slower if you have tons of users, but that's your call. Personally, I say it's worth it because it's negligible, and easier for users, and it's them you should cater to. So pay for a faster server, or no??? Ultimately, you always ought to design around having as few queries as possible, while caching certain pages that are heavy on the database so it doesn't have to do the same thing X number of times.
Let's begin:
Remember that the following is an example of what I would do for django 2.x, with a better functionality than you're asking for. If you're using 1.x, just use url() instead of path(), and any other requirements.
Assume we have an app called accounts_app.
Also assume that we put path('accounts/', include('accounts_app.urls')), inside our project level urls.py.
I'm also going to assume you know how to use templates... Now, create a urls.py inside that app:
accounts_app/urls.py:
app_name = 'accounts_app'
urlpatterns = [
...
path('signup/', views.signup, name='signup'),
]
accounts_app/models.py:
The user attribute below enables you to extend the User model, so you can have their phone_number too. In this example, I allowed that be blank in case they don't want to give it. But if they did, you would have make a separate view for it. To make this whole thing much more simple, I'm not going to include that, nor tell you how to overwrite the whole User model. I'm only going to show you how to log in with an email with the regular User model. After that, doing so with a phone number shouldn't be hard at all. A reminder on what you will need is at the very end of this post.
class ExtendedUserExample(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
created = models.DateTimeField(auto_now_add=True)
phone_number = models.IntegerField(blank=True)
settings.py.... NOTE THAT THIS IS IN THE PROJECT LEVEL FOLDER
The order of these backends matters. Always do ModelBackend first, or it will break.
AUTHENTICATION_BACKENDS = [
'django.contrib.auth.backends.ModelBackend',
'accounts_app.authentication.EmailAuthBackend', # to be able to login with email, described next
]
accounts_app/authentication.py:
This is an example to be able to login by their email. If you wanted someone to login by their phone number too, the principle is the same, but in this file, you would also need to import the ExtendedUserExample model above, add that to the above settings AUTHENTICATION_BACKENDS at the bottom of it, and make a new class for PhoneAuthBackend, that searches ExtendedUserExample for the phone_number. Again, to keep this more simple, I am not completely overwriting the User model, rather I only extend it, so if a user made an account and wanted to login by phone number later on, they would have to sign up with a username and email first, and once in, they could add a phone number (with this example, you will need another view for that).
So try this email example first until you get the hang of it. You also don't need to import this file anywhere else because the settings.py file takes care of it.
Here's what's happening: On your template for logging in, it will first search for the username field inside your User model, because your settings.py file has the AUTHENTICATION_BACKENDS variable to check the ModelBackend first.
But let's say a user entered their username as aaa#aaa.com. Now since you didn't allow anyone to sign up with an email as their username, when aaa#aaa.com is not found as a username in the User model, your settings file now says to go check the same User object a second time, but search their input by the email field / column instead. If their input exists in that column, authenticate() logs them in by their email if the password is right.
from django.contrib.auth.models import User
class EmailAuthBackend(object):
""" Authenticate using an email address """
def authenticate(self, request, username=None, password=None):
try:
user = User.objects.get(email=username) # gets the email by the 'username' they entered
if user.check_password(password):
return user
return None
except User.DoesNotExist:
return None
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
acounts_app/views.py:
I'll assume you know how to make django forms. If not, check here for model forms.
from .forms import ExtendedUserForm
def signup(request):
form = ExtendedUserForm(request.POST or None)
if request.method == 'POST':
if request.POST['password1'] == request.POST['password2']:
potential_user = request.POST.get('username', False).lower()
try:
user = User.objects.get(username=potential_user)
return render(request, 'accounts_app/signup.html', {'error': 'Username has already been taken. Please try another'})
except User.DoesNotExist:
if form.is_valid():
new_user = User.objects.create_user(username=potential_user, email=request.POST['email'], password=request.POST['password1'])
# backend argument required cause we are making the ability to LOGIN by email.
# Remember, I only extended the User model.
auth.login(request, new_user, backend='django.contrib.auth.backends.ModelBackend')
return redirect('some_app:some_view')
else:
return render(request, 'accounts_app/signup.html', {'error': "Password's must match."})
return render(request, 'accounts_app/signup.html', {'form': form})
Now that your login by email should be working, it's not that hard to follow these principles to create the same ability for logging in by phone. If you continue with this example, you will need to make a new view to save a phone number to ExtendedUserExample.phone_number. After that, add another line at the bottom of AUTHENTICATION_BACKENDS, write a new class in authentication.py, and you'd be set... So long as you have <input type="text" name="username" required> when they use your login view.
Related
I have created unique user pages for each person who registers to this social app by using the following:
#app.route("/profile/<username>", methods=["GET", "POST"])
def profile(username):
user = session["user"] or None
# grab the session user's username from db
username = mongo.db.users.find_one(
{"username": session["user"]})["username"]
user_profile = mongo.db.users.find_one({"username": user})
if session["user"]:
return render_template("profile.html", username=username, profile=user_profile)
return redirect(url_for("login"))
This creates a unique username profile built up from edited data that the user puts in. But I want to be able to view that profile from another user's login and then add them as a friend (if I choose).
Because I already have a URL created /profile/ do I need to create a new #app.route to something such as view_profile/<username>?
From there, I would use the Post and Get methods so if someone wanted to add that user as a friend they could click on that. From there, I think I would need the other user to accept the friend request? Not quite sure how to get to that just yet. But one step at a time I guess.
I started with the idea of a view_profile #app.route but now I am not 100% sure which direction to take it that will make the next stages also easy.
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 make users active by sending them an activation email to click. I guess it is currently not incorporated in Django 1.6.The user-registration app coded in Django seems to serve this purpose. But I have some doubts with regard to the DefaultForm it provides in forms.py. I want to have more fields included in it. How can I achieve that in class RegistrationForm(forms.Form) implemented there. If I install this app, is it a good idea to change include more fields directly there, is there a better way to achieve the same.
In the views.py, I see some methods such as the following are not implemented. I dont have a clear picture of what these methods need to do. should I redirect the url here to the pages?
def register(self, request, **cleaned_data):
raise NotImplementedError
def activate(self, request, *args, **kwargs):
raise NotImplementedError
def get_success_url(self, request, user):
raise NotImplementedError
You need to first let them sign up and mark them as is_active=False for the time being. Something like this:
from django.contrib.auth.models import User
from django.core.mail import send_mail
from django.http import HttpResponseRedirect
def signup(request):
# form to sign up is valid
user = User.objects.create_user('username', 'email', 'password')
user.is_active=False
user.save()
# now send them an email with a link in order to activate their user account
# you can also use an html django email template to send the email instead
# if you want
send_mail('subject', 'msg [include activation link to View here to activate account]', 'from_email', ['to_email'], fail_silently=False)
return HttpResponseRedirect('register_success_view')
Then once they click the link in the email it takes them to the next view (note: you need to put a link in the email so that you know which user it is. This may be 16-digit salt or something. The below view uses the user.pk:
def activate_view(request, pk):
user = User.objects.get(pk=pk)
user.is_active=True
user.save()
return HttpResponseRedirect('activation_success_view')
Hope that helps. Good Luck!
Basically you can use django's user model(https://docs.djangoproject.com/en/1.9/ref/contrib/auth/). However, in user model email is not a required field. You need to modify the model to make email an required field.
In your views, you might need the following method:
1) Sign up: after sign up, set user.is_active=False and call function send_email to include an activation link in the email. In the link, you might want to include the user's information (for example, user.id) so when the user click the link, you know which user to activate.
2) send_email: send a link to user's email address for verification. The link includes user's id. For example:
http://127.0.0.1:8000/activation/?id=4
3) Activate: get the id information from the URL using id=request.GET.get('id'). Query user=user whose id is id. set user.is_active=True.
Actually I implemented an reusable application like your request, check this(https://github.com/JunyiJ/django-register-activate) if you are interested.
Hope that helps. Good luck!
check this out... i Hope it helps out with not only the solution u need but also the explanation.. Because i think django-registration app is meant for default Django User. So if u want to have extra fields in your registration form, Start thinking of customizing ur Django User and Its authentication yourself. You dont need the django-registration app here..
Here are some tutorials thats will help
http://www.caktusgroup.com/blog/2013/08/07/migrating-custom-user-model-django/
and many more...
I am trying to make a website, where people only put their email addresses and they are logged in with cookies and all. At a later stage, i will ask them provide password and names, but NO username will be used. I am trying to do this with django-registraition, but i get errors and i have a few problems.
First to disable usernames as a login feature, i put str(time()) instead of username - i was looking for something that will change every time.
However, when I skip the authentication (which i currently don't need) i get error:
'RegistrationProfile' object has no attribute 'backend'
Alternatively, i can leave the authentication but then i don't know how to authenticate it only with email and no password. Also, i don't know how to make the next line work:
auth.login(request, ProfileUser)
If anyone can get me out of here, it would be awesome. Here is some code:
my form Class:
class RegistrationFormCustom(forms.Form):
email = forms.EmailField()
def do_save(self):
new_u = User(username=str(time()),email= self.cleaned_data.get('email'),)
new_u.save()
new_p = Profile.objects.create_profile(new_u)
new_p.save()
return new_p
my view:
def registerCustom(request, backend, success_url=None, form_class=None,
disallowed_url='registration_disallowed',
template_name='registration/registration_form.html',
extra_context=None,
initial={}):
form = RegistrationFormCustom(initial=initial)
if request.method == 'POST':
form = RegistrationFormCustom(initial=initial, data=request.POST)
if form.is_valid():
profile = form.do_save()
profile = auth.authenticate(username = profile.user.email, password = form.cleaned_data.get('pass1'))
print(profile)
auth.login(request, profile)
return redirect('/')
else:
pass
return render_jinja(request, 'registration/registration_form.html',
type="register",
form = form
)
and i will post any other snipped required happily
You're getting the 'RegistrationProfile' object has no attribute 'backend' error because the user is not yet authenticated. To log someone in, you have to call the authenticate method first, which requires a password. So, what you can do instead, is this:
from django.contrib.auth import load_backend, login, logout
from django.conf import settings
def _login_user(request, user):
"""
Log in a user without requiring credentials (using ``login`` from
``django.contrib.auth``, first finding a matching backend).
"""
if not hasattr(user, 'backend'):
for backend in settings.AUTHENTICATION_BACKENDS:
if user == load_backend(backend).get_user(user.pk):
user.backend = backend
break
if hasattr(user, 'backend'):
return login(request, user)
Then, to log someone in, just call the _login_user function with the request and User model. (This will be profile.user in your case, probably) Do this instead of calling auth.login. I'm not sure on how you're going to determine whether this is a valid user or not, without a password or username, but I'll leave that to you. If you still have trouble, let me know.
Short Explanation:
What basically happens here is that Django requires a user to be authenticated in order to be logged in via the login function. That authentication is usually done by the authenticate function, which requires a username and password, and checks whether the supplied password matches the hashed version in the database. If it does, it adds an authentication backend to the User model.
So, since you don't have a password and username, you just have to write your own method for adding the authentication backend to the User model. And that's what my _login_user) function does - if the user is already authenticated, it just calls login, otherwise, it first adds the default backend to the User model, without checking for a correct username and password (like authenticate does).
For others reading this thread, I got a similar error message when I was using User.objects.create() instead of User.objects.create_user(). Basically, the first method was setting a clear password whereas create_user encrypts the password. Clear passwords will fail to authenticate. Check your database, if you have passwords set in the clear, then it's likely you need to use create_user() instead.
The author's request could be fixed by simply setting a default user and password using create_user() instead of just user.save().
You can create a known password (put it in settings.py ) and use that as though the user entered it. Create the user with this and authenticate the user with this.
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.