SUMMARY
How do I use a custom User model and a custom authentication backend (to allow for email / password authentication) with Django + MongoEngine? (Is a custom backend even necessary for that? ...i.e., to use an email for username when authenticating with MongoEngine.)
Is there any documentation with a straight-forward (and complete!) example of using a custom user object while using Mongo as the primary datastore when authenticating in Django? (Postgres has such clearer and more comprehensive docs...)
DETAIL
MongoEngine seems to give you just two flavors of authentication--the "Classic" (aka 'mongoengine.django.auth.MongoEngineBackend') way...OR...the "Custom User Model" (aka 'django.contrib.auth.backends.ModelBackend') way--both of which more or less succinctly outlined in Nicolas Cortot's answer to a different question here:
Python-Social-Auth fails with mongoEngine (Django)
Both of these authentication techniques give you access to an authenticate() method similar to Django's AbstractBaseUser class--a method which relies on a check_password function. However, the minute you use the so-called "Custom User Model" flavor of authentication (as outlined in the above link)...and then pair that with a custom backend (in order to use emails for usernames)...you run into trouble due to the absence of access to the typical authenticate() function.
For example, like so...
accounts.models.py
# ...with postgres, I'd subclass AbstractBaseUser...but with Mongo...(?) from django.conf import settings from mongoengine.fields import EmailField, BooleanField
from mongoengine.django.auth import User class MyUser(User): email = EmailField(max_length=254, unique=True) is_active = BooleanField(default=True) is_admin = BooleanField(default=False) USERNAME_FIELD = 'email' REQUIRED_FIELDS = '' ...
my_custom_backend.py
# ...is a custom backend even necessary to use email for authentication instead of username? from django.conf import settings from django.contrib.auth.models import check_password #from mongoengine.django.auth import check_password #from django.contrib.auth.hashers import check_password from models import MyUser class EmailAuthBackend(object): def authenticate(self, email=None, password=None): # ...uh oh, since I'm NOT using one of the usual backends with a pre-existing authenticate() # method, there ain't a native check_password() function available. Means I have to hash the # password, etc.
So, seemingly, I'm obliged to write my own check_password function. To get all of the goodness inherent with the AbstractBaseUser class typically found with a PostgreSQL authentication, I'd have to totally inflate my custom User model, which seems hacky and can't be very DRY.
Am I getting totally confused here? ...i.e., is it actually totally unnecessary to use a custom backend if I want to use emails instead of usernames for authentication when using MongoEngine?
I feel like I may have a fundamental misunderstanding of how Django works with MongoEngine in regard to authentication, and with regard to how I've modeled and called upon custom user object / my particular subclassing of MongoEngine's user object during that process...
Because--as it is right now--I'm getting an "'AnonymousUser' object has no attribute 'backend'" error message in the browser. I also noted that this problem sometimes exists for unexpected reasons--namely: perhaps, the authenticate() method expects a hashed password, or because the login (email) is too long...? For more instances where this latter circumstance might be the case, see:
Django Register Form 'AnonymousUser' object has no attribute 'backend'
settings.py
INSTALLED_APPS = ( 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.sites', 'django.contrib.messages', 'django.contrib.staticfiles', 'django.contrib.admin', 'mongoengine.django.mongo_auth', 'accounts', ) AUTHENTICATION_BACKENDS = ( 'mongoengine.django.auth.MongoEngineBackend', #'accounts.my_custom_backend.EmailAuthBackend', #'django.contrib.auth.backends.ModelBackend', ) AUTH_USER_MODEL = 'mongo_auth.MongoUser' MONGOENGINE_USER_DOCUMENT = 'accounts.models.User'
accounts.views.py
from django.contrib.auth import login as django_login from my_custom_backend import EmailAuthBackend from forms import AuthenticationForm def login(request): form = AuthenticationForm(data=request.POST) if form.is_valid(): try: backend = EmailAuthBackend() user = backend.authenticate(email=request.POST['email'], password=request.POST['password']) django_login(request, user) return redirect('/') except DoesNotExist: return HttpResponse('user does not exist') else: form = AuthenticationForm() return render_to_response('accounts/login.html', { 'form': form }, context_instance=RequestContext(request))
Well, looks like the best course of action isn't to hand over Django's User to Mongo for authentication to begin with... Got this golden nugget via Twitter:
@blogblimp my short answer: try to avoid replacing Django user models with MongoDB. You lose all the Django power and lose MongoDB's speed. Seriously, user relates to everything and MongoDB isn't relational.
— Daniel Roy Greenfeld (@pydanny) January 20, 2014So: I'll just leverage PostgreSQL for authentication, and Mongo for other objects. That means naming / connecting to two databases in the Django settings. In retrospect, I guess the moral is: never use Mongo just because it's cool. Mongo is still a second-class citizen in the Django world.