Django下的用户管理

1、数据库表

Django中有User模型 (django.contrib.auth.models.User),不过其中仅包含了有限的 7个固定字段 (username, first_name, last_name, email, is_staff, is_active, date_joined),其中is_staff(models.BooleanField)表示是 否允许此用户进入admin页面,is_active(models.BooleanField)表示该用户的帐号 是否为有效帐号,比如用户将自己注销(删除)了,其状态就可以由is_active=False表示。
扩展用户属性可以通过model的继承来实现,示例代码如下:
class CustomUser(User):
    age = models.PositiveIntegerField(blank=True)
    job = models.CharField(max_length=300, blank=True)
    location = models.CharField(max_length=300, blank=True)

    class Meta(User.Meta):
        db_table = 'customuser'
如上,CustomUser表示自定义用户,在基础字段上增加了age, job, location三个字段。如果执行python manage.py  syncdb,数据库会自动建好一下6个用户直接相关的表:
  • auth_group:用户分组表,初始为空,即默认没有分组
  • auth_group_permissions:用户分组权限表,初始为空
  • auth_permission:用户权限表,初始内容为对各个model的增、删、改的权限
  • auth_user:用户表,这里只包含7个固定字段,不包含我们自定义的字段
  • auth_user_groups:用户分组关系表,初始为空
  • auth_user_user_permissions:用户权限分配表
可见,我们期望的customuser并没有出现,看来我们需要手动建立了,建表语句如下所示:
CREATE TABLE auth_info
(
  user_ptr_id integer NOT NULL,
  age integer,
  job character varying,
  location character varying,
  CONSTRAINT auth_info_pkey PRIMARY KEY (user_ptr_id),
  CONSTRAINT auth_info_user_ptr_id_fkey FOREIGN KEY (user_ptr_id)
      REFERENCES auth_user (id) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE NO ACTION DEFERRABLE INITIALLY DEFERRED,
  CONSTRAINT auth_info_age_check CHECK (age >= 0)
)
WITH (
  OIDS=FALSE
);
ALTER TABLE auth_info
  OWNER TO postgres;
字段 user_ptr_id挺关键的,别忘了。
这样一来,我们就可以像操作普通model一样来操作CustomUser了,也可以将CustomUser当作User类型(继承产生的多态)在后续的用户模块中毫无障碍的使用。

2、用户注册

包括接下来的密码找回、个人信息修改,我们都将借助Django中的表单实现,Django中的表单还是很好用的。
注册表单
class UserCreateForm(UserCreationForm):
    '''
    创建新用户
    '''
    email = forms.EmailField()  #邮箱
    job = forms.CharField(max_length=300, required=False)   #职业
    age = forms.IntegerField(required=False)  #年龄 
    location = forms.CharField(max_length=300, required=False) #工作地点

    class Meta:
        model = User
        fields = (
           "username", "email", "password1", "password2", 
           "job", "age", "location"
        )
    
    # 判断年龄是否为负数
    def clean_age(self):
        age = self.cleaned_data['age']
        if age < 1:
            raise forms.ValidationError("年龄不能小于1岁")
        return age
    
    # 判断该email是否已被注册
    def clean_email(self):
        email = self.cleaned_data["email"]
        try:
            User._default_manager.get(email=email)
        except User.DoesNotExist:
            return email
        raise forms.ValidationError(
            "the email already exists.Plase change one or login",
            code='duplicate_email',
        )
            
    def save(self, commit=True):
        user = super(UserCreateForm, self).save(commit=False)
        user.job = self.cleaned_data["job"]
        user.age = self.cleaned_data["age"]
        user.email = self.cleaned_data["email"]
        user.location = self.cleaned_data["location"]
                
        if commit:
            user.save()
        return user
不一一解释了,代码自身应该能将自身解释的很清楚了,需要注意的是,如果某个字段是非必须的,需注明: required=False,默认的都是必须的。
注册页面
考虑到灵活性,我们没有采用Django代码中默认的方式,那样的话使用弹出框真的是很不方便。
<form method="post" id="register-form">
	<ul class="soil-user-signup-fields">
		<li>
			<p>
				<span class="soil-user-signup-label">用户名</span>
				<input value="1" type="text" name="username" class="form-control soil-user-signup-field" placeholder="必填">
			</p>
			<p style="display:none">
				<span id="username-error-info" class="error-msg"></span>
			</p>
		</li>
		
		<li>
			<p>
				<span class="soil-user-signup-label">密码</span>
				<input value="123" type="password" name="password1" class="form-control soil-user-signup-field" placeholder="必填">
			</p>
		</li>
		
		<li>
			<p>
				<span class="soil-user-signup-label">密码确认</span>
				<input value="123" type="password" name="password2" class="form-control soil-user-signup-field" placeholder="必填">
			</p>
			<p style="display:none">
				<span id="password-error-info" class="error-msg"></span>
			</p>
		</li>
		
		<li>
			<p>
				<span class="soil-user-signup-label">邮箱</span>
				<input value="2@c.cn" type="text" name="email" class="form-control soil-user-signup-field" placeholder="必填">
			</p>
			<p style="display:none">
				<span id="email-error-info" class="error-msg"></span>
			</p>
		</li>
		
		<li>
			<p>
				<span class="soil-user-signup-label">职业</span>
				<input value="123" type="text" name="job" class="form-control soil-user-signup-field">
			</p>
		</li>
		
		<li>
			<p>
				<span class="soil-user-signup-label">单位</span>
				<input value="123" type="text" name="location" class="form-control soil-user-signup-field">
			</p>
		</li>
		
		<li>
			<p>
				<span class="soil-user-signup-label">年龄</span>
				<input value=23 type="text" name="age" class="form-control soil-user-signup-field">
			</p>
		</li>
		
	</ul>   
</form>   

<script type="text/javascript">
	
	// 提交注册信息
	var signup_submit = function(){
		$("#register-form").ajaxSubmit({
			url: "/accounts/sign_up",
			success: function(){
				$("#signup-panel").modal('hide');
				$("#signup-success-panel").modal();
			},
			error: function(msg){
				dialogs.error("注册失败!请修改错误信息后重新注册!");
			}
		});
	};
</script>
上面省略了,用户名,邮箱唯一性、有效性检查的代码,这部分可自行添加。为防止页面在提交后跳转,我们使用了异步提交(ajaxSubmit, 来自jquery.form.js)
注册代码
def sign_up(request):
    '''
    注册
    '''
    if request.method == "POST":
        form = UserCreateForm(request.POST)
        if form.is_valid():
            usr = form.save()
        else:
            raise HttpInternalError()

3、用户登录

表单:django.contrib.auth.forms.AuthenticationForm
登录页面
<form method="post" id="signin-form" action="/accounts/sign_in">
	<ul class="soil-user-signin-fields">
	
		<li>
			<p>
				<span class="soil-user-signin-label">用户名</span>
				<input type="text" name="username" class="form-control soil-user-signin-field">
			</p>
			<p style="display:none">
				<span id="username-signin-error-info" class="error-msg"></span>
			</p>
		</li>
		
		<li>
			<p>
				<span class="soil-user-signin-label">密码</span>
				<input type="password" name="password" class="form-control soil-user-signin-field">
			</p>
		</li>
		
		<li>
			<p style="padding-right: 25px;font-size: 12px;height: 15px;">
				<a href="javascript:void(0)" class="forget-pwd">忘记密码?</a>
				<span style="float:right">自动登录</span>
				<input name="remember-me" type="checkbox" style="margin-top: 2px;float:right">
			</p>
		</li>	      		
	</ul>  
	<input type="hidden" name="next" value="/"> 
</form>  

<script>
	var signin_submit = function(){
		$("#signin-form").ajaxSubmit({
			url: "/accounts/sign_in",
			dataType:"JSON",
			success: function(data){
				$("#signin-panel").modal('hide');
				$(".signup-btn,.signin-btn").hide();
				$(".logout-btn,.pwd-btn").show();
				$("#username>a").attr("href","/accounts/profile");
				$("#username>a").html(data['username']);
			},
			error: function(msg){
				dialogs.error("登录失败!密码错误!");
			}
		});
	};
</script>
上面的
<input type="hidden" name="next" value="/"> 表示用户登录后的跳转页面,这里让其跳到首页。
登录代码
def sign_in(request):
    '''
    登录
    '''
    params = utils.get_params(request)
    redirect_to = utils.get_dict_value(params, 'next', '')
    if request.method == "POST":
        form = AuthenticationForm(request, data=request.POST)
        if form.is_valid():
            login(request, form.get_user())
            rememberme = utils.get_dict_value(params, "remember-me")
            if rememberme == "on":
                request.session.set_expiry(7*24*3600)
            else:
                request.session.set_expiry(0)
            
            return utils.dumps_json({"next":redirect_to,"username": utils.get_param(params, "username")})
     
    raise HttpInternalError()
这里需要说明一下用户登录有效期的问题,默认有效期是 2周,但如果在代码里将session的过期设为了浏览器关闭即过期,那么以后用户再登录如果不进行如何设置的话,默认的就是浏览器关闭即过期了,所以这里当用户选择了“自动登录”,还需要显式的设置一次过期时间。

3、信息修改

表单
class UserProfileForm(UserChangeForm):
    '''
    个人信息修改
    '''
    class Meta:
        model = User
        fields = ('username','email','password','age', 'job', 'location')
        password = ReadOnlyPasswordHashField()

    def __init__(self, *args, **kwargs):
        super(UserProfileForm, self).__init__(*args, **kwargs)

    def clean_password(self):
        return self.initial["password"]

修改页面与上面大同小异,也是form+ajaxSubmit的组合,不再举例。
修改代码
def update(request):
    '''
    修改个人信息
    '''
    if request.method == 'POST':
        form = UserProfileForm(request.POST, instance=request.user.customuser)
        if form.is_valid():
            form.save()
            if 'username' in request.REQUEST:
                usr = CustomUser.objects.get(username = request.POST['username'])
                return str(usr)
            elif 'email' in request.REQUEST:
                usr = CustomUser.objects.get(email = request.POST['email'])
                return str(usr)
    raise HttpInternalError()

4、密码找回

在密码找回里使用了注册码(django-simple-captcha)防止用户恶意提交。基本流程是:用户点击“忘记密码”,弹出密码重置对话框(包括邮箱和注册码两个输入项),用户输入相关信息,提交,如果没问题,向其填写的邮箱中发送密码重置链接,如果有问题,停留在当前对话框,提示相关错误,等待用户重新输入相关信息。
表单
class CaptchaForm(forms.Form):
    email = forms.EmailField() 
    captcha = CaptchaField()
注册码模块
urls.py:
urlpatterns += patterns('',
    url(r'^captcha/', include('captcha.urls')),
)
settings.py
邮箱配置:
EMAIL_HOST = 'smtp.issas.ac.cn'             #SMTP地址
EMAIL_PORT = 25                         #SMTP端口
EMAIL_HOST_USER = 'xxx@xxx.xxx'  #邮箱地址
EMAIL_HOST_PASSWORD = '******'        #邮箱密码

DEFAULT_FROM_EMAIL = <span style="font-family: Arial, Helvetica, sans-serif;">EMAIL_HOST_USER</span>


views.py
def reset_password_dialog(request):
    '''
    重置密码对话框
    '''
    form = CaptchaForm()
    return render_to_response("accounts/inc/password_reset_dialog.html", locals(),
                                      context_instance=RequestContext(request))
accounts/inc/password_reset_dialog.html
<script>
	
	var exchange = function(a, b){
		var n = a.next(), p = b.prev();
		b.insertBefore(n);
    a.insertAfter(p);
	};

	$(function(){
		
		exchange($('.captcha'), $('#id_captcha_1'));
		
		$('.captcha').click(function(){
			$.get("accounts/get_new_sn", function(result){
				$('.captcha').attr("src",result);
				$('#id_captcha_0').attr("value",result.split('/')[3]);
			});
			return false;
		});
		
		$("#id_captcha_1").on("focus", function(){
			$("#sn-error-msg").html("");
		});
		
	});
</script>

<form method="POST" id="captcha-form">
	<p>
		<span class="soil-user-fw-field">邮箱</span>
		{{form.email}}
	</p>
	<p style="margin-bottom:3px">
		<span class="soil-user-fw-field">验证码</span>
		{{form.captcha}}
	</p>
	<span id="sn-error-msg" style="display: inline-block;margin-left: 65px;color: red;"></span>
</form>
如你所见,上述html代码中没有提交代码,这个对话框是ajax方式请求,提交代码在其母页面中。
重置密码模块
前端代码:
<script>
var forget_pwd_submit = function(){
	$("#captcha-form").ajaxSubmit({
		url: "/accounts/reset_password",
		success: function(data){
			if(data == "sn_error"){
				$.get("accounts/get_new_sn", function(result){
					$('.captcha').attr("src",result);
					$('#id_captcha_0').attr("value",result.split('/')[3]);
				});
				$("#sn-error-msg").html("验证码错误!");
				return;
			}
			$("#forget-pwd-panel").modal("hide");
			$("#forget-pwd-done-panel").find(".modal-body").html(data);
			$("#forget-pwd-done-panel").modal();
		},
		error: function(msg){
			$("#forget-pwd-panel").modal("hide");
			dialogs.error("密码重置失败!");
		}
	});
};
</script>
后端代码
urls.py:
urlpatterns += patterns('django.contrib.auth.views',
    url(r'^password/reset/(?P<uidb64>[0-9A-Za-z]+)-(?P<token>.+)/$', 
        'password_reset_confirm', {"template_name":"accounts/inc/password_reset_confirm.html"}, name="password_reset_confirm"),
    url(r'^password/done/$', 'password_reset_complete', {"template_name":"accounts/inc/password_reset_complete.html"}, name="password_reset_complete")
)
views.py
def reset_password(request):
    '''
    重置用户密码
    '''
    if request.method == 'POST':
        
        form_sn = CaptchaForm(request.POST)
        if form_sn.is_valid():
            human = True
        else:
            return "sn_error"   #验证码错误
        
        form_email = PasswordResetForm(request.POST)
        email_template_name = "accounts/inc/password_reset_email.html"
        if form_email.is_valid():
            opts = {
                'use_https': request.is_secure(),
                'token_generator': default_token_generator,
                'from_email': settings.DEFAULT_FROM_EMAIL,
                'email_template_name': email_template_name,
                'request': request,
            }
            #form.save(**opts)
            
            from MapCloud.common.ThreadPool import async_execute
            async_execute(form_email.save, kwds=opts)
            
            return render_to_response("accounts/inc/password_reset_done.html", locals(),
                                      context_instance=RequestContext(request))
                        
    raise HttpInternalError()
这部分代码是参考着django.contrib.auth.forms.PasswordResetForm来写的,是有点麻烦,主要是其中的模板参数不清楚,下面一一解释一下(强烈建议看看django/contrib/admin/templates/registration中的默认模板):
  • password_reset_complete.html:用户通过重置链接重置密码成功后跳转到的页面
示例:
<html>
<body>
<p>密码已重置,您现在可以返回<a href="/">首页</a>重新登录!</p>
</body>
</html>
  • password_reset_confirm.html:这个就是用户重置密码的页面
示例:
<h3>密码重置</h3>


{% if validlink %}

<form action="" method="post">{% csrf_token %}
{{ form.new_password1.errors }}
<p class="aligned wide"><label for="id_new_password1">新的密码:</label>{{ form.new_password1 }}</p>
{{ form.new_password2.errors }}
<p class="aligned wide"><label for="id_new_password2">再输一遍:</label>{{ form.new_password2 }}</p>
<p><input type="submit" value="重置" /></p>
</form>

{% else %}

<h1>密码重置失败,可能这个链接已经被使用过了,您可以重新申请密码重置</h1>

{% endif %}
  • password_reset_done.html:这个是用户输入了正确的邮箱和验证码,系统给出的确认信息。
示例:
<p>我们向您的邮箱里发送了密码重置链接,请按链接中的指示重置密码。</p>
<p>如果没收到相关邮件,请确认您在上一步所填写的邮箱是否时您注册时使用的邮箱。</p>
<p>谢谢!</p>
  • password_reset_email.html:这个是向用户发送的邮件的内容,即用户会在其邮箱看到的内容
示例:
{% autoescape off %}
您收到此封邮件是因为您请求重置 {{ site_name }} 上的帐号密码.

请点击下面的链接并设置一个新的密码:
{% block reset_link %}
{{ protocol }}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %}
{% endblock %}

另:您的用户名为:{{ user.get_username }}

谢谢您对我们的支持!

{% endautoescape %}
OK,这样这部分基本上就可以使用了。
另外,系统再向用户发送邮箱的会有一个比较明显的延迟,为此我们使用线程池构造了一个异步任务执行模块,这个会再另一篇文章了介绍。

5、密码修改

待续。。。











评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值