您的位置:首页 > 编程语言 > Go语言

Django笔记 如何扩展User表的字段

2016-04-20 12:18 453 查看
django 自带的权限框架,其中auth_user表的字段,很难满足正常的需求,因此需要扩展,至于扩展,一般有如下几种选择:

1. 直接修改django 源码,修改User class 的定义,以及各种方法等,然后把数据库auth_user表里的字段扩展到与自己需求一致.(源代码在:django.contrib.auth.models import User),这种方式,每次升级django都得很小心.

2. 把django 的user以及认证部分的源代码拷贝到自己的app下面,然后修改,配置,这样就不需要改动django的代码了.但如果你要升级django ,就可能有麻烦

3. 继承User,做扩展.

4. django 官方推荐的方法,profile 方式扩展.

方法一

比较一下这几种方式,发现,1,2种方法很黄很暴力.所以不是在非常特殊的情况下,建议不用。第3中方法,我目前测试下来是可以,但扩展之后,输入密码的时候,居然是明文的,而且保存进数据库是没有经过加密的密码,目前还在找原因,所以这里介绍第4种方法.

在models.py 中增加如下扩展user的类:

#==================扩展用户====================================
class UserProfile(models.Model):
user = models.OneToOneField(User)
major = models.TextField(default='', blank=True)
address = models.CharField(max_length=200,default='',blank=True)

def __unicode__(self):
return self.user.username

def create_user_profile(sender, instance, created, **kwargs):
"""Create the UserProfile when a new User is saved"""
if created:
profile = UserProfile()
profile.user = instance
profile.save()

#post_save.connect(create_user_profile, sender=User)
""" 不明白的是,我一定注释掉上面这一行,才不会出错,否则会有Duplicate entry '2' for key 'user_id'") ,看意思是,重复了,但不明白为什么重复,注释掉上面的之后,一切正常,但与官方文档又有差异了,迷惑中"""
#==================扩展用户结束================================


还需要修改admin.py

"""用户模块扩展"""
class ProfileInline(admin.StackedInline):
model = UserProfile
#fk_name = 'user'
max_num = 1
can_delete = False

class CustomUserAdmin(UserAdmin):
inlines = [ProfileInline,]

admin.site.unregister(User)
admin.site.register(User, CustomUserAdmin)
"""用户模块扩展"""


修改settings.py 的配置,增加

"""用户模块扩展部分"""
AUTH_PROFILE_MODULE = 'djangoadmin.myadmin.UserProfile'
"""用户模块扩展完成"""


按照官方的解释,这里是app label加上扩展类的名字. 应该也就是创建的app的名字,官方推荐的方式就两个部分用”.”连接起来,我这里有三个部分,也没有报错。

然后运行 python manage.py syncdb ,这是会在数据库中创建响应的表,并且有user_id这个外键字段.

最后,我们来运行一下程序,并进入到增加用户界面中,你会发现,你扩展的字段都显示出来了

每次增加用户,都会在扩展的表中增加相应的数据,修改的时候,也会修改响相应的数据,通过 user_id 来关联,这样就完成了user model的扩展。

如果要获取扩展表中的内容,可以通过 request.user.get_profile().address 这种方式来获取. 得到 User对象后,就能很方便的得到扩展的类.

方法二

如果想通过继承User的方式来做的,可以用如下方法,转载自网上:

1.创建 user profile 类,直接继承于 User

程序代码 程序代码

from django.contrib.auth.models import User, UserManager
class CustomUser(User):
timezone = models.CharField(max_length=50, default='Asia/shanghai')
objects = UserManager()


用这种方式 可以直接用user = CustomUser.objects.create(…)方式创建新对象. 而且如果用request.user得到的就是你扩展的这个对象,可以直接取属性的。不需要get_profile.

2.扩展认证机制

程序代码 程序代码

from django.conf import settings
from django.contrib.auth.backends import ModelBackend
from django.core.exceptions import ImproperlyConfigured
from django.db.models import get_model

class CustomUserModelBackend(ModelBackend):
def authenticate(self, username=None, password=None):
try:
user = self.user_class.objects.get(username=username)
if user.check_password(password):
return user
except self.user_class.DoesNotExist:
return None

def get_user(self, user_id):
try:
return self.user_class.objects.get(pk=user_id)
except self.user_class.DoesNotExist:
return None

@property
def user_class(self):
if not hasattr(self, '_user_class'):
self._user_class = get_model(*settings.CUSTOM_USER_MODEL.split('.', 2))
if not self._user_class:
raise ImproperlyConfigured('Could not get custom user model')
return self._user_class


配置settings.py

程序代码 程序代码

AUTHENTICATION_BACKENDS = (
'myproject.auth_backends.CustomUserModelBackend',
)


运行 python manage.py syncdb

同样也会生成一个数据库,不过表的字段与前面一种方法有所不用。

然后运行,同样测试,可以达到同样的效果,但界面上密码输入哪里我始终不满意,所以就没采用。

方法三

Django的用户模型:

Django和其他很多Web框架不同的一点就是它自带了一个现成的User Model,因为Web开发中99%会涉及到用户这个概念,自带一个User Model,不但解决了每次开发一个新的项目的时候,都不得不为新建一个用户表所做的重复工作。而且最大的好处是由于Django Model特有的处理方式,只有通过django.contrib.auth.models 导入 User Model,那么不管你是来自哪个app,各个app之间引用的都是同一个User Model,而那些域通过ForeignKey,ManyToManyField,OneToOneField指向User Model后在数据库中所产生的真正外键都是参照 auth_user 表的 id,这对于很多相互独立但都需要一个用户模型的app来说是不可缺少的机制,因为如果django不内置User Model,那个每个app就得各自为政,独立定义User Model,最后的结果就是当想把这些app组合起来使用的时候,由于User Model不统一而得花大量时间来修改这些app的User Model相关的代码。

这个优点表现最明显的例子就是 Pinax Project (http://pinaxproject.com/),这是个将大量开源app组合起来经过简单配置组装而成的一个SNS工程。

Django用户模型的问题:

虽然 Django 自带了User Model方便了很多app的组合,但是由于每个app肯定都需要再增加一些信息到用户模型中,而Django自带的User Model 中所定义的field都是一些很常规的信息,肯定无法满足app本身的需求。为此就需要增加一个profile,放用户额外的信息。这主要由以下几种方式:

1、Django官方指导的方式:

见文档 http://docs.djangoproject.com/en/dev/topics/auth/#storing-additional-information-about-users

大概过程就是这样:定义一个Profile Model,里面包含一个unique=True的ForeignKey ,在settings.py 添加一个选项 AUTH_PROFILE_MODULE 指向该Profile Model,那么自带User Model的实例就可以通过get_profile()来获取相应的profile的信息了,这个方法虽然看上去很方便,但是有一个很明显的缺点就是profile是在一张另外的表中,虽然get_profile()缓存了第一次取的的profile的结果,但是每个http请求之间都得为此做一个查询,当用户数量很多的时候,对性能的影响就比较大了。另外一个问题是当需要定义像friends这样的指向 ‘self’ 的 ManyToManyField时该怎么办?定义在profile中就显得很不合理了,一个用户和另一个用户做朋友,而不是和ta的profile做朋友,更何况定义在profile中的话,当做friends的反向查询的时候,取得的queryset是profiles,这时候又想取得这些profile指向的user的信息的时候就得再做大量查询了。

2、修改Django的源代码直接在User Model上加字段了:

这种方法很直接效果也很明显,如果用得像是1.0.2-final这样的最终发布版,并且以后也不打算更新Django版本的话这样也是可以的。但是如果是用SVN development版的话,这种修改源码的方式就显得有点暴力了。

3、直接舍弃Django的User Model,自己重头定义一个User Model,包括自己管理auth,写auth backends:

我见过几个项目都是这样做的了,虽然通过继承原有User Model,ModelBackend等类,可以只需要写很少的代码就可以实现,这种方式其实修改Django源码没什么大的区别,都是减少了一张profile表,只不过扩展后的User Model一个是放在Django源码里,一个是放在自己的源码里,而且这种方式比修改源码更糟的是,这破坏了开头提到的与其他app组合使用的可能性。

不修改django源码也不增加profile表的扩展用户模型:

上面提到的各种方式主要都是围绕修改django源码和增加一张profile表之间的权衡利弊展开。那么有没有一种两全其美的方式呢,既不修改源码,也不增加profile表。答案是有的,而且在很早的django版本中,这甚至是很常用的方式,只是后来django修改了Model部分的源码,阻止了这种方式,因为这看上去太magic了。

以前的django model中的inner Meta中可以定义一个 replaces_module 的参数,用来指定你想将该Model替换另外一个Model,看上去很奇怪也很神奇,但是管理起来也显得很混乱,所以最后这个特性被取消了。

虽然上面这种方式被阻止了,但是还是可以通过其他方式动态的修改User Model类,说了这么多,还是直接看源码来的更清楚些吧:

from django.db import models
from django.contrib.auth.models import User
from django.contrib.auth.admin import UserAdmin
import datetime
class ProfileBase(type):
def __new__(cls, name, bases, attrs):
module = attrs.pop('__module__')
parents = [b for b in bases if isinstance(b, ProfileBase)]
if parents:
fields = []
for obj_name, obj in attrs.items():
if isinstance(obj, models.Field): fields.append(obj_name)
User.add_to_class(obj_name, obj)
UserAdmin.fieldsets = list(UserAdmin.fieldsets)
UserAdmin.fieldsets.append((name, {'fields': fields}))
return super(ProfileBase, cls).__new__(cls, name, bases, attrs)

class Profile(object):
__metaclass__ = ProfileBase

class MyProfile(Profile):
nickname = models.CharField(max_length = 255)
birthday = models.DateTimeField(null = True, blank = True)
city = models.CharField(max_length = 30, blank = True)
university = models.CharField(max_length = 255, blank = True)

def is_today_birthday(self):
return self.birthday.date() == datetime.date.today()


上面的代码中定义了一个ProfileBase的元类,然后定义了一个作为Profile基类的Profile类,并且它的元类为ProfileBase。

元类的作用简单来讲,就是创建其他类的类。也就是元类的实例是普通类,而普通类的实例就是普通的实例。

如果你不理解这些也没关系,只要知道,上面的代码中,当解释器看到你在定义一个Profile类的子类,而Profile类的元类是ProfileBase,所以MyProfilede的元类也是ProfileBase,也就是在定义任何Profile的子类的时候,它就会执行元类ProfileBase中的new中代码,并且将正在定义的类的(名字,基类,类属性)作为参数传递给new

MyProfilede中的field采用了和普通Model相同的声明语法,所以这些field和那个自定义方法is_today_birthday会作为attrs中的属性传递过去,然后元类中将这些信息提取出来通过

User.add_to_class(obj_name, obj) 加入到User类中,add_to_class也是普通Model中定义field时采用的方式,因为普通Model也有一个元类在做相似的事情,只不过这里定义的这个元类专门往User Model中加东西。

在添加完这些field和自定义方法后,前面识别出哪些属性是field,然后加入到UserAdmin.fieldsets中,那样在admin中就可以和其他User fields 一起编辑这些新加的field了。

如果你有其他app也想往User Model中加field或方法,都只要通过子类Profile类,然后使用声明语法进行定义即可,所有其他工作都有元类帮你完成。

通过这种方式添加到User Model中的field和方法的效果就和直接修改Django User Model的源码一样,只不过现在这些修改不用再像修改源码这么暴力了,而且想添的时候随便可以添。

需要注意的地方:

1、以上元类的定义包括MyProfile的定义,最好就和普通Model一样,放在app的models.py文件中, 以便django可以方便的碰到这些定义,否则django没看到这些代码也就没效果了,而且放在其他地方非常容易引起导入的依赖问题。

2、如果你已经通过syncdb同步了数据库,再使用此方法扩展用户模型,那么Django虽然知道你定义了这些field,但是再次运行syncdb时,不会再为这些field自动创建数据库字段,因为django的syncdb只负责创建新表的字段。这与你已经定义了一个Model并运行syncdb,然后往那个Model上又加了fields是同一种情况。

参考:

http://www.yihaomen.com/article/python/334.htm

http://www.opscoder.info/extend_user.html

http://www.it610.com/article/3191881.htm
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  django 数据库 扩展