我整理了一个自定义用户模型来存储一些新字段并保持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
答案 0 :(得分:1)
问题是您无法从.created
功能设置密码,您必须使用:
a = User(username='your_username', email='..', ..)
a.set_password('your_pass') # then you use this for create the password
原因是Django必须散列密码才能使用该函数