2010-07-20 7 views
13

En lugar de usar el módulo auth de django, he usado el mío y ya lo lamento mucho.¿Cómo accedo a auth Usuario User.objects.create_user (...) en una migración al sur?

En un esfuerzo por rectificar la situación, estoy tratando de migrar los datos de mi modelo de usuario a django.auth.models.User.

He creado una migración de datos de la siguiente manera:

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

Cuando ejecuto la migración, me sale este error (rastreo):

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

Tras realizar investigaciones adicionales, descubrí que la Manager que estaba siendo referido fue de tiempoque explica el error.

Ahora, la razón por la que incluso necesito create_user es crear un hash de contraseña que django.contrib.auth entenderá.

Habiendo dicho todo eso, ¿cómo hago para hacer esto? ¿Cuál es la solución más elegante dado el agujero en el que estoy?

Gracias de antemano.

Actualización 1

Como sugiere stevejalim, He intentado utilizar User 's set_password(...) de la siguiente manera:

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

Sin embargo que ha fallado con este 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 encontró una pista en el south documentation que dice que:

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

Supongo que la pregunta sigue siendo, ¿cuál es la mejor/más segura forma de hacerlo? Copie el método set_password(...)? Crear una función que hashe la contraseña para mí? ¿Alguna otra idea?

Respuesta

4

Ok, resulta que South no congela los métodos en absoluto, por lo que llamar a cualquier método de modelo no sirve de nada.

La manera en que lo resolví fue al hacer frente y modificar el código en contrib.auth que genera contraseñas.

Así es como la migración final se parece a:

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

¿Por qué no hacer el User manualmente, luego configure la contraseña después de que ha sido save() d con newUser.set_password()? Sí, tendrás que golpear el DB dos veces, pero eso no es gran cosa.

+0

No es mala idea, excepto, sigo teniendo un error: AttributeError: objeto 'Usuario' no tiene atributo 'set_password'. – Gezim

+2

En ese caso, levante el código de django.contrib.auth.models.User para set_password() [y get_hexdigest()] - se trata de 30 líneas all-in –

3

El objetivo de utilizar el ORM inmovilizado en migraciones es garantizar que los nuevos cambios no interfieran con las implementaciones anteriores. La aplicación de autenticación forma parte de django.contrib, y dudo que la funcionalidad que estás buscando haya cambiado mucho en las últimas versiones, o está previsto que cambie pronto. Aparte de eso, no vas a modificar la aplicación (auth) o sus modelos (¿verdad? ¿Correcto?). Por lo tanto, es bastante seguro decir que no necesita para usar la versión congelada de South de auth.User; simplemente importelo normalmente y úselo de esa manera.

+0

Los métodos del modelo no se importan, lo que causa el problema, no el modelo en sí – Gezim

+1

Según tengo entendido, el problema es que South no congela los métodos de utilidad 'auth.User', que es lo que buscas, en su orma congelado. ¿'From django.contrib.auth.models import User' y luego usar ese' User' no resuelve el problema? ¿Causa problemas en otro lado? – eternicode

+0

No puede hacer ese tipo de importación en el sur. Publiqué el archivo de migración completo a continuación (a excepción de la parte de descripción del modelo). – Gezim

5

Por qué no acaba de importar lo que necesita ?.
que tenían el mismo problema y lo que hice fue:

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

funciona en v1.5, gracias! – amertkara

Cuestiones relacionadas