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

Вместо того, чтобы использовать модуль авторизации 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...

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

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

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

Ответы [ 4 ]

5 голосов
/ 24 апреля 2014

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

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')
        ...
4 голосов
/ 16 ноября 2010

Хорошо, оказывается, что 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)
3 голосов
/ 09 ноября 2010

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

1 голос
/ 20 июля 2010

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

...