Как мне получить доступ к пользовательскому User.objects.create_user (...) в южной миграции?

Вместо использования модуля django auth я использовал свои собственные и уже очень сожалею об этом.

Чтобы исправить ситуацию, я пытаюсь перенести данные из моей модели User на django.auth.models.User.

Я создал миграцию данных следующим образом:

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...

Когда я запускаю миграцию, я получаю эту ошибку (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'

После дальнейшего исследования я обнаружил, что Manager, на которое ссылался, был временем south.orm.NoDryRunManager, что объясняет ошибку.

Теперь мне даже нужна create_user, чтобы создать хэш пароля, который будет django.contrib.auth.

Сказав все это, как мне это сделать? Какое самое изящное решение, учитывая отверстие, в котором я нахожусь?!

Спасибо заранее.

Обновление 1

Как было предложено stevejalim, я попытался использовать User set_password(...) следующим образом:

newUser.set_password(raw_password=oldUser.password)
newUser.save()

Однако это не удалось с этой ошибкой:

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'

Я нашел подсказку в южной документации, которая гласит, что:

Юг не замерзает каждый аспект модель; например, он не делает сохранить новых менеджеров или пользовательскую модель методы, поскольку это потребует сериализация кода python, который выполняется этот метод (и код, который зависит от и т.д.).

Если вы хотите, чтобы в вашем миграции, вам придется скопировать код, включая любые импортные данные полагается на работу. Помните, однако, для каждого импорта, который вы добавляете, youre обещая сохранить этот импорт действительным для жизни для миграции.

Я думаю, вопрос остается, какой лучший/безопасный способ сделать это? Скопировать метод set_password(...)? Создать функцию, которая хеширует пароль для меня? Любые другие идеи?

Ответ 1

Хорошо, получается, что Юг вообще не замораживает методы, поэтому вызов любых методов модели бесполезен.

То, как я решил это, - это решить и изменить код в contrib.auth, который генерирует пароли.

Вот как выглядит итоговая миграция:

# 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 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)

Ответ 2

Почему вы не просто импортируете то, что вам нужно?

У меня была та же проблема, и я сделал это:

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')
        ...

Ответ 3

Весь смысл использования замороженной ORM в миграции - обеспечить, чтобы новые изменения не мешали старым реализациям. Приложение auth является частью django.contrib, и я сомневаюсь, что функциональность, которую вы ищете, сильно изменилась в последние несколько выпусков или скоро будет изменена. Кроме того, вы не собираетесь изменять приложение (auth) или его модели (правильно? Правильно?). Так что довольно спокойно сказать, что вам не нужно использовать южную замороженную версию auth.User; просто импортируйте его в обычном режиме и используйте его таким образом.

Ответ 4

Почему бы не сделать User вручную, а затем установить пароль после save() d с newUser.set_password()? Да, вам нужно дважды нажать на БД, но это не потрясает.