How do I access auth User's User.objects.create_user(...) in a south migration?
Instead of using django's auth module I've used my own and already regret it a lot.
In an effort to rectify the situation, I'm trying to migrate the data from my User model to django.auth.models.User.
I've created a data migration as follows:
def forwards(self, orm):
"""Migrate user information from mooi User model to auth User model."""
OldUser = orm['mooi.User']
User = orm['auth.User']
Profile = orm['mooi.Profile']
oldUsers = OldUser.objects.all()
for oldUser in oldUsers:
newUser = User.objects.create_user(username=oldUser.id, email=oldUser.email, password=oldUser.password)
# ...more irrelevant code follows...
When I run the migration, I get this error (traceback):
#...irrelevant traceback precedes...
File "[projdir]/mooi/migrations/0005_from_mooi_users_create_auth_users_with_profiles.py", line 18, in forwards
newUser = User.objects.create_user(username=oldUser.id, email=oldUser.email, password=oldUser.password)
File "[virtual_env_dir]lib/python2.6/site-packages/south/orm.py", line 397, in __getattr__
return getattr(self.real, name)
AttributeError: 'Manager' object has no attribute 'create_user'
Upon further investigation, I discovered that the Manager
that was being referred to was of time south.orm.NoDryRunManager
which explains the error.
Now, the reason I even need create_user
is to create a password hash that django.contrib.auth
will understand.
Having said all that, how do I go about doing this? What's the most elegant solution given the hole I'm in?!
Thanks in advance.
Update 1
As suggested by stevejalim, I tried to use User
's set_password(...)
as follows:
newUser.set_password(raw_password=oldUser.password)
newUser.save()
However that failed with this error:
File "[projdir]/mooi/migrations/0005_from_mooi_users_create_auth_users_with_profiles.py", line 21, in forwards
newUser.set_password(raw_password=oldUser.password)
AttributeError: 'User' object has no attribute 'set_password'
I did find a hint in the south documentation which states that:
South doesn’t freeze every aspect of a model; for example, it doesn’t preserve new managers, or custom model methods, as these would require serialising the python code that runs those method (and the code that depends on, and so forth).
If you want custom methods in your migration, you’ll have to copy the code in, including any im开发者_JS百科ports it relies on to work. Remember, however, for every import that you add, you’re promising to keep that import valid for the life for the migration.
I guess the question remains, what's the best/safest way of doing this? Copy the set_password(...)
method over? Create a function that hashes the password for me? Any other ideas?
Why you don't just import what you need?.
I had the same problem and what I did was:
from django.contrib.auth.hashers import make_password
class Migration(DataMigration):
...
def forwards(self, orm):
user = orm['auth.User'].objects....
user.password = make_password('123')
...
Ok, it turns out that South doesn't freeze methods at all, so calling any model methods is of no use.
The way I solved this was by coping and modifying the code in contrib.auth that generates passwords.
Here's how the final migration looks like:
# encoding: utf-8
import datetime
from south.db import db
from south.v2 import DataMigration
from django.db import models
class Promise(object):
"""
This is just a base class for the proxy class created in
the closure of the lazy function. It can be used to recognize
promises in code.
"""
pass
def lazy(func, *resultclasses):
"""
Turns any callable into a lazy evaluated callable. You need to give result
classes or types -- at least one is needed so that the automatic forcing of
the lazy evaluation code is triggered. Results are not memoized; the
function is evaluated on every access.
"""
class __proxy__(Promise):
"""
Encapsulate a function call and act as a proxy for methods that are
called on the result of that function. The function is not evaluated
until one of the methods on the result is called.
"""
__dispatch = None
def __init__(self, args, kw):
self.__func = func
self.__args = args
self.__kw = kw
if self.__dispatch is None:
self.__prepare_class__()
def __reduce__(self):
return (
_lazy_proxy_unpickle,
(self.__func, self.__args, self.__kw) + resultclasses
)
def __prepare_class__(cls):
cls.__dispatch = {}
for resultclass in resultclasses:
cls.__dispatch[resultclass] = {}
for (k, v) in resultclass.__dict__.items():
# All __promise__ return the same wrapper method, but they
# also do setup, inserting the method into the dispatch
# dict.
meth = cls.__promise__(resultclass, k, v)
if hasattr(cls, k):
continue
setattr(cls, k, meth)
cls._delegate_str = str in resultclasses
cls._delegate_unicode = unicode in resultclasses
assert not (cls._delegate_str and cls._delegate_unicode), "Cannot call lazy() with both str and unicode return types."
if cls._delegate_unicode:
cls.__unicode__ = cls.__unicode_cast
elif cls._delegate_str:
cls.__str__ = cls.__str_cast
__prepare_class__ = classmethod(__prepare_class__)
def __promise__(cls, klass, funcname, func):
# Builds a wrapper around some magic method and registers that magic
# method for the given type and method name.
def __wrapper__(self, *args, **kw):
# Automatically triggers the evaluation of a lazy value and
# applies the given magic method of the result type.
res = self.__func(*self.__args, **self.__kw)
for t in type(res).mro():
if t in self.__dispatch:
return self.__dispatch[t][funcname](res, *args, **kw)
raise TypeError("Lazy object returned unexpected type.")
if klass not in cls.__dispatch:
cls.__dispatch[klass] = {}
cls.__dispatch[klass][funcname] = func
return __wrapper__
__promise__ = classmethod(__promise__)
def __unicode_cast(self):
return self.__func(*self.__args, **self.__kw)
def __str_cast(self):
return str(self.__func(*self.__args, **self.__kw))
def __cmp__(self, rhs):
if self._delegate_str:
s = str(self.__func(*self.__args, **self.__kw))
elif self._delegate_unicode:
s = unicode(self.__func(*self.__args, **self.__kw))
else:
s = self.__func(*self.__args, **self.__kw)
if isinstance(rhs, Promise):
return -cmp(rhs, s)
else:
return cmp(s, rhs)
def __mod__(self, rhs):
if self._delegate_str:
return str(self) % rhs
elif self._delegate_unicode:
return unicode(self) % rhs
else:
raise AssertionError('__mod__ not supported for non-string types')
def __deepcopy__(self, memo):
# Instances of this class are effectively immutable. It's just a
# collection of functions. So we don't need to do anything
# complicated for copying.
memo[id(self)] = self
return self
def __wrapper__(*args, **kw):
# Creates the proxy object, instead of the actual value.
return __proxy__(args, kw)
return wraps(func)(__wrapper__)
# code to encrypt passwords borrowed from django 1.2.1:
def smart_str(s, encoding='utf-8', strings_only=False, errors='strict'):
"""
Returns a bytestring version of 's', encoded as specified in 'encoding'.
If strings_only is True, don't convert (some) non-string-like objects.
"""
if strings_only and isinstance(s, (types.NoneType, int)):
return s
if isinstance(s, Promise):
return unicode(s).encode(encoding, errors)
elif not isinstance(s, basestring):
try:
return str(s)
except UnicodeEncodeError:
if isinstance(s, Exception):
# An Exception subclass containing non-ASCII data that doesn't
# know how to print itself properly. We shouldn't raise a
# further exception.
return ' '.join([smart_str(arg, encoding, strings_only,
errors) for arg in s])
return unicode(s).encode(encoding, errors)
elif isinstance(s, unicode):
return s.encode(encoding, errors)
elif s and encoding != 'utf-8':
return s.decode('utf-8', errors).encode(encoding, errors)
else:
return s
def get_hexdigest(algorithm, salt, raw_password):
"""
Returns a string of the hexdigest of the given plaintext password and salt
using the given algorithm ('md5', 'sha1' or 'crypt').
"""
raw_password, salt = smart_str(raw_password), smart_str(salt)
if algorithm == 'crypt':
try:
import crypt
except ImportError:
raise ValueError('"crypt" password algorithm not supported in this environment')
return crypt.crypt(raw_password, salt)
# The rest of the supported algorithms are supported by hashlib, but
# hashlib is only available in Python 2.5.
try:
import hashlib
except ImportError:
if algorithm == 'md5':
import md5
return md5.new(salt + raw_password).hexdigest()
elif algorithm == 'sha1':
import sha
return sha.new(salt + raw_password).hexdigest()
else:
if algorithm == 'md5':
return hashlib.md5(salt + raw_password).hexdigest()
elif algorithm == 'sha1':
return hashlib.sha1(salt + raw_password).hexdigest()
raise ValueError("Got unknown password algorithm type in password.")
def get_encrypted_password(raw_password):
import random
algo = 'sha1'
salt = get_hexdigest(algo, str(random.random()), str(random.random()))[:5]
hsh = get_hexdigest(algo, salt, raw_password)
return '%s$%s$%s' % (algo, salt, hsh)
class Migration(DataMigration):
def forwards(self, orm):
"""Migrate user information from mooi User model to auth User model."""
OldUser = orm['mooi.User']
User = orm['auth.User']
Profile = orm['mooi.Profile']
oldUsers = OldUser.objects.all()
for oldUser in oldUsers:
newUser = User(username=oldUser.id, email=oldUser.email)
newUser.first_name = oldUser.name
newUser.save()
newUser.password = get_encrypted_password(oldUser.password)
newUser.save()
newUserProfile = Profile(user=newUser)
newUserProfile.phone = oldUser.phone
newUserProfile.credits = oldUser.credits
newUserProfile.transaction_status = oldUser.transaction_status
newUserProfile.location = oldUser.location
newUserProfile.save()
assert oldUser.id == newUser.username, \
"Old user: %s, is not equal to: %s" % (oldUser.id, newUser.username)
assert oldUser.name == newUser.first_name, \
"Names don't match, old: %s, new: %s" % (oldUser.name, newUser.first_name)
assert oldUser.email == newUser.email, \
"Emails don't match, old: %s, new: %s" % (oldUser.email, newUser.email)
assert oldUser.phone == newUserProfile.phone, \
"Phones don't match, old: %s, new: %s" % (oldUser.phone, newUserProfile.phone)
assert oldUser.credits == newUserProfile.credits, \
"Credits don't match, old: %s, new: %s" % (oldUser.credits, newUserProfile.credits)
assert oldUser.transaction_status == newUserProfile.transaction_status, \
"Trans. status don't match, old: %s, new: %s" % (oldUser.transaction_status, newUserProfile.transaction_status)
assert oldUser.location == newUserProfile.location, \
"Locations don't match: old: %s, new: %s" % (oldUser.location == newUserProfile.location)
The whole point behind using the frozen ORM in migrations is to ensure that new changes don't interfere with old implementations. The auth app is part of django.contrib
, and I doubt the functionality you're looking for has changed much in the past few releases, or is planned to change anytime soon. Aside from that, you're not going to be modifying the (auth) app or its models (right? right??). So it's pretty safe to say you don't need to use South's frozen version of auth.User
; just import it normally and use it that way.
Why not make the User
manually, then set the password after it has been save()
d with newUser.set_password()
? Yes, you'll need to hit the DB twice, but that's no great shakes.
精彩评论