Why GenericRelation fields does not work in Data M

2019-07-13 20:44发布

问题:

I want to make data migration in order to add user read post in database. There is such code:

def user_read_posts(apps, schema_editor):
    User = apps.get_model("main", "User")
    Post = apps.get_model("main", "Post")
    Comment = apps.get_model("comments", "Comment")

    comments = Comment.objects.all()
    for comment in comments:
        print (comment.content_object.__class__.__name__)
        if isinstance(comment.content_object, Post):
            comment.user.read_posts.add(comment.content_object)

class Migration(migrations.Migration):

    dependencies = [
        ('main', '0039_auto_20160314_0906'),
    ]

    operations = [
        migrations.RunPython(user_read_posts),
    ]

And in line print (comment.content_object.__class__.__name__) django raise error: AttributeError: 'Comment' object has no attribute 'content_object'

Comment model:

class GuidaComment(GenericRelationModel):

    user = models.ForeignKey(GuidaUser)
    text = models.TextField()

So what should I do?

Thanks.

回答1:

In RunPython migration scripts apps.get_models() will get historical version of models, not the latest django models you have in source. These historical models are quite limited. Excerpt from django documentation:

historical models will not have any custom methods that you have defined. They will, however, have the same fields, relationships, managers (limited to those with use_in_migrations = True) and Meta options (also versioned, so they may be different from your current ones).

But that does not mean that you can't use latest and fully functional models by just importing them and using them. In that case you are risking that the migration script won't run correctly in the future - since latest version of model can change (e.g. model method can be renamed, deleted, logic changed etc.). So, it is possible but you need to be aware of the risk using models like this.

Content types framework is pretty old and rarely changed django contrib application and IMHO it is safe enough to use it like this.

Inspired by the answer and solution I made and use, I have made following draft I hope you will find useful:

def user_read_posts(apps, schema_editor):
    User = apps.get_model("main", "User")
    Post = apps.get_model("main", "Post")
    Comment = apps.get_model("comments", "Comment")

    from django.contrib.contenttypes.models import ContentType

    comments = Comment.objects.all()
    for comment in comments:
        try:
            # assuming that Comment has object_id field holding reference to PK
            # of the referenced object 
            ct = ContentType.objects.get(model=comment.content_type.model, 
                                         app_label=comment.content_type.app_label)
            content_object = ct.get_object_for_this_type(pk=comment.object_id)
        except Exception, ex: 
            # TODO: can happen if some content type / model is deleted.
            continue

        print (content_object.__class__.__name__)
        if isinstance(content_object, Post):
            # TODO: maybe you will need to copy/adapt read_posts.add method's logic here
            comment.user.read_posts.add(content_object)