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

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

Пытаясь исправить ситуацию, я пытаюсь перенести данные из своей модели 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...

Когда я запускаю миграцию, я получаю эту ошибку (трассировка):

#...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, я попытался использовать set_password(...) User следующим образом:

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, который запускает этот метод (и код, который зависит от него, и т. д.).

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

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


person Gezim    schedule 20.07.2010    source источник


Ответы (4)


Почему бы вам просто не импортировать то, что вам нужно?.
У меня была та же проблема, и я сделал следующее:

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')
        ...
person gsoriano    schedule 24.04.2014

Хорошо, получается, что South вообще не замораживает методы, поэтому вызывать какие-либо методы модели бесполезно.

Я решил эту проблему, скопировав и изменив код в 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'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)
person Gezim    schedule 16.11.2010

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

person eternicode    schedule 09.11.2010
comment
Методы модели не импортируются, что вызывает проблему, а не сама модель. - person Gezim; 10.11.2010
comment
Насколько я понимаю, проблема в том, что Юг не замораживает служебные методы auth.User, которые вам нужны, в замороженном виде. Разве from django.contrib.auth.models import User, а затем использование этого User не решает проблему? Вызывает ли это проблемы в другом месте? - person eternicode; 10.11.2010
comment
Вы не можете сделать этот тип импорта на юге. Я разместил полный файл миграции ниже (за исключением части описания модели). - person Gezim; 16.11.2010
comment
Решение Eternicode сработало для меня с использованием Django 1.5.1 и South 0.7.6. Я не знаю, было ли раньше невозможно импортировать модели напрямую в Юг, но теперь это работает и намного проще, чем обходной путь Пилигрима. - person AndrewF; 16.05.2013

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

person Steve Jalim    schedule 20.07.2010
comment
Неплохая идея, за исключением того, что я все еще получаю сообщение об ошибке: AttributeError: объект «Пользователь» не имеет атрибута «set_password». - person Gezim; 20.07.2010
comment
В этом случае поднимите код из django.contrib.auth.models.User для set_password() [и get_hexdigest()] — всего около 30 строк. - person Steve Jalim; 20.07.2010