Django身份验证 - 创建CustomUser,现在我不能再登录了

时间:2017-11-12 02:11:09

标签: python django authentication

我整理了一个自定义用户模型来存储一些新字段并保持DB / UI模型分离。我可以从python shell创建用户并验证密码:

>python manage.py shell -c "
     from itemdb.models import MyUser;
     user = MyUser.objects.create_user('mypass','AD','Joe','Smith','1233','joe@smith.com');
     print user.check_password('mypass');
 "
 True

但是当我将我的应用指向新的“MyUser”模型时,我无法再通过网络表单登录了:

  

请输入正确的用户ID和密码。请注意,两个字段都可以   区分大小写。

我创建了一个包含必填字段的新“用户”表,并且哈希密码值存储在“密码”字段中。

我写了一些可能覆盖默认函数的函数,比如“get”,“save”等等。我猜这个问题就在那里。我的另一个猜测是,表格不是通过正确的凭证。

我知道最简单,最干净的方法是扩展模型 - 我已阅读所有帖子和教程。但我想保留DB& UI代码尽可能分开。我想我很亲密。少了什么东西?有关为什么验证无法通过网站工作的任何想法?

Postgres 9.6
Python 2.7
Django 1.11

urls.py (截断)

from django.conf.urls import url
from . import views
from django.contrib import admin
from django.contrib.auth import views as auth_views

app_name= 'itemdb'

urlpatterns = [

    # Authentication/Admin
    url(r'^login/$', auth_views.login, {'template_name': 'login.html'}, name='login'),
    url(r'^logout/$', auth_views.logout, {'next_page':'/'}, name='logout'),
    url(r'^passwordreset/$', auth_views.logout, name='passwordreset'),

    # Index (i.e. /itemdb/)
    url(r'^$', views.itemindex, name="itemindex"),

]

的login.html

{% block content %}
<h2>Login</h2>
<form method="post">
    {% csrf_token %}
    {{ form.as_p }}
    <button type="submit">Login</button>
</form>
{% endblock %}

settings.py

...
AUTH_USER_MODEL = 'itemdb.MyUser' # Point to custom user model
...

用户表DDL

CREATE TABLE Users (
    UserId INTEGER NOT NULL,
    UserType CHAR(2) NOT NULL,
    FirstName VARCHAR(100) NOT NULL,
    LastName VARCHAR(100) NOT NULL,
    PhoneNumber VARCHAR(25),
    EmailAddress VARCHAR(250),
    Password VARCHAR(128),
    Last_Login TIMESTAMPTZ,
    PRIMARY KEY(UserId)
);

SP_IGLGetUser

CREATE OR REPLACE FUNCTION $DB_NAME$Views.SP_IGLGetUser(
    pUserId INTEGER
)
RETURNS SETOF $DB_NAME$Views.Users
AS
$$
BEGIN
    RETURN QUERY
    SELECT UserId, UserType, FirstName, LastName, PhoneNumber, EmailAddress, Password, Last_Login
    FROM $DB_NAME$Views.Users
    WHERE (UserId = pUserId OR pUserId IS NULL) -- Return single user (if specified, otherwise return all)
    ;
END;
$$
LANGUAGE 'plpgsql';

models.py (截断)

from __future__ import unicode_literals
from django.db import models
from UsefulFunctions.dbUtils import *
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager
from django.contrib.auth import get_user_model

# Data model managers (i.e. interface between DB and objects)
class MyUserManager(BaseUserManager):

    # Create new user
    def create_user(self, password, usertype = None, firstname = None, lastname = None, phonenumber = None, emailaddress = None):
        user = self.model(
            userid=None,
            usertype=usertype,
            firstname=firstname,
            lastname=lastname,
            phonenumber=phonenumber,
            emailaddress=self.normalize_email(emailaddress)
        )

        # Save hashed password
        user.set_password(password)

        # Save user data and update user object with newly created id
        result = user.save()
        user.userid = result[0]

        return user

    def get_all(self):
        users = getDBData(self, 'SP_IGLGetUser(%s)', (None,))

        # Return list of user objects
        return users

    # Get info for one specific user
    def get(self, userid):
        user = getDBData(self, 'SP_IGLGetUser(%s)', (userid,))
        return user[0] # First and only row of array

    def upsertUser(self, myUser):
        return saveDBData('SP_IGLUpsertUser', 
            (
                myUser.userid,
                myUser.usertype,
                myUser.firstname,
                myUser.lastname,
                myUser.phonenumber,
                myUser.emailaddress,
                myUser.password,
                myUser.last_login,
                None
            )
         )

    def deleteUser(self, myUser):
        return deleteDBData('SP_IGLDeleteUser', (myUser.userid, None))

# Data models (i.e. tables)
# Create custom base user
class MyUser(AbstractBaseUser):

    # Define attributes (inherited class includes password + last_login fields)
    userid = models.IntegerField(primary_key=True) # Specify as PK to prevent Django from creating "id" column and for queryset returns (raw)
    usertype = models.CharField(max_length=2)
    firstname = models.CharField(max_length=100)
    lastname = models.CharField(max_length=100)
    phonenumber = models.CharField(max_length=25)
    emailaddress = models.CharField(max_length=250)

    # Define data manager
    objects = MyUserManager()

    # Create new constructor (must be passed in correct order)
    def __init__(self, password = None, last_login = None, userid = None, usertype = None, firstname = None, lastname = None, phonenumber = None, emailaddress = None):

        # Call parent's init function
        super(get_user_model(), self).__init__()

        # Set properties
        self.userid = userid
        self.usertype = usertype
        self.firstname = firstname
        self.lastname = lastname
        self.phonenumber = phonenumber
        self.emailaddress = emailaddress
        self.last_login = last_login

    # Class info
    class Meta:
        managed = False # Ensure Django doesn't "manage" the table
        db_table = 'users' # Point to actual DB table

    # Required fields
    USERNAME_FIELD = 'userid' # specify how Django recognizes the user
    EMAIL_FIELD = 'emailaddress'
    REQUIRED_FIELDS = ['usertype','firstname','lastname'] # email and password are required by default

    # Required methods
    def get_full_name(self):
        return self.firstname + " " + self.lastname + " (" + self.userid + ")"

    def get_short_name(self):
        return self.userid

    def save(self):
        return MyUser.objects.upsertUser(self)

    def delete(self):
        return MyUser.objects.deleteUser(self)    

dbUtils.py

from django.db import connection
from collections import namedtuple

# Return all rows from a cursor as named tuples (i.e. rows with field names)
def namedtuplefetchall(cursor):
    columns = [col[0] for col in cursor.description]
    nt_result = namedtuple('Result', columns)

    return [
        nt_result(*row) 
        for row in cursor.fetchall()
    ]

def getDBData(myobjects, sp_signature, params):
    objectlist = []

    # Execute raw SQL on object manager and return RawQuerySet object
    objects = myobjects.raw('select * from ' + sp_signature, params)

    # Convert RawQuerySet to list of model instances    
    for myobject in objects:
        objectlist.append(myobject)

    return objectlist

def getRawDBData (sp_name, param_list):
    with connection.cursor() as cursor:        
        cursor.callproc(sp_name, param_list)
        return_data = namedtuplefetchall(cursor) # Create "rows"
        cursor.close()

    return return_data

def saveDBData(sp_name, param_list):
    with connection.cursor() as cursor:        
        cursor.callproc(sp_name, param_list)
        return_data = cursor.fetchone() # Store any output
        cursor.close()

    return return_data

def deleteDBData(sp_name, param_list):
    return saveDBData(sp_name, param_list)  

更新
看起来这个问题与get_by_natural_key有关,它为密码字段返回一个空值。我的测试电话:

>python manage.py shell -c "
   from itemdb.models import MyUser;
   user = MyUser.objects.create_user('mypass','AD','c','k','123','email');
   print user.userid, user.password;
   print user.check_password('mypass'); 

   newuser = get_by_natural_key(user.userid)
   print newuser.check_password('mypass')
   print newuser.firstname, newuser.password, newuser.emailaddress
 "
>94 <password_hash_string>
>True
>False
>c  email

create_user()调用可以很好地存储和验证密码。

然后我尝试使用get_by_natural_key(user.userid)重新检索新创建的用户。此调用创建一个MyUser对象,其中填充了所有值,“password”字段除外。

我直接调用了底层数据库函数,实际上它确实返回带有散列值的“password”字段。

我已经取得了一些进展,但这就是我被困住的地方。我不明白为什么它没有将“密码”值传递给新对象。有什么想法吗?

数据流

get_by_natural_key  
  objects.get()  
    getDBData()  
      objects.raw()  
        SP_IGLGetUser() --> Postgres function call  

1 个答案:

答案 0 :(得分:1)

问题是您无法从.created功能设置密码,您必须使用:

a = User(username='your_username', email='..', ..)
a.set_password('your_pass') # then you use this for create the password

原因是Django必须散列密码才能使用该函数