Pregunta

I have a model which has a nullable foreign key relation with itself (this 'self' can be any model which has this foreign key). This foreign key is a custom class which prevents cyclic relationships.

The call to super() in the __init__ contains null=True and blank=True and therefor need to be included in the inspection rules of South. This makes at least the schemamigration work, but the migrate still fails.

Following code shows: the custom foreign key, the inspection rules and the model using the foreign key.

# Foreign key
class ParentField(models.ForeignKey)
    def __init__(self, verbose_name=None, **kwargs):
        super(ParentField, self).__init__('self', verbose_name=verbose_name, null=True, blank=True, **kwargs)

    @staticmethod
    def checkcyclic(object, attr):
        '''Check for a cyclic relationship'''
        pass

# Introspection rules
add_introspection_rules([
    (
        [ParentField],
        [],
        {
            'null': ['null', {'default': True}],
            'blank': ['blank', {'default': True}],
        }
    )
], ["^test\.models\.fields\.ParentField"])

# Model
class Relation(Model):
    parent = ParentField(related_name='child_set')

Migrating gives the following error:

$ ./manage.py migrate
[..]
super(ParentField, self).__init__('self', verbose_name=verbose_name, null=True, blank=True, **kwargs)
TypeError: __init__() got multiple values for keyword argument 'to'

I've tried addding a rule like below, but that changed nothing.

'to': ['rel.to', {'default': 'test.models.Relation'}],

Can anyone tell me what I'm doing the wrong way or any hints to a solution for this?

¿Fue útil?

Solución

In the end this solution worked for me, credits to Shai over at googlegroups:

South, when it tries to build an instance of your custom field, collects (using the rules) values for the different kwargs of your class. One of these is the "to" argument; you see it specified in the migration code.

Then, it takes the values it collected and passes them to a constructor. So, effectively, you get the call

fields.ParentField(related_name='set', to=orm['module.ModelClass'])

However, inside your __init__ this gets translated to

models.ForeignKey('self', related_name='set', to=orm['module.ModelClass'])

So there are two values for the 'to' argument -- the first is the positional 'self'.

One solution, at least, is to do the same thing I recommended for the null and blank arguments -- add it into kwargs, rather than directly to the call.

kwargs.setdefault('to', 'self') 

Otros consejos

I had similar issue but solved it by calling: kwargs.pop('to', None).

However, there was next error:

File "/.../django/db/models/fields/related.py", line 973, in resolve_related_fields
    to_field = (self.rel.to._meta.pk if to_field_name is None
AttributeError: 'str' object has no attribute '_meta'

This was because I was passing model name as a string not class instance. South doesn't support that. Fixed by importing model and using it instead of string.

Full working example below:

from django.db import models
from django.conf import settings

from django.db.models.loading import get_model

def _get_user_model():
    """Import django.contrib.auth.models.User or other model set in settings.AUTH_USER_MODEL"""
    model_name = getattr(settings, 'AUTH_USER_MODEL', 'auth.User')
    return get_model(*model_name.split('.', 1))

def _set_default_kwargs(kwargs):
    kwargs.setdefault('on_delete', models.SET_NULL)
    kwargs.setdefault('blank', True)
    kwargs.setdefault('null', True)
    kwargs.pop('to', None) # Remove kwargs['to'] parameter set by south migrations
    return kwargs

class ForeignKeyWithUser(models.ForeignKey):
    def __init__(self, **kwargs):
        kwargs = _set_default_kwargs(kwargs)
        User = _get_user_model()
        return super(ForeignKeyWithUser, self).__init__(User, **kwargs)

try:
    from south.modelsinspector import add_introspection_rules
    add_introspection_rules([], ["^toolkit\.models\.fields\.ForeignKeyWithUser"])
except ImportError:
    pass
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top