pythondjangodjango-modelsdjango-admin

How to to refer to a model that has not yet been defined, how to add attribute in to class that depend on class that created after first


I have some classes in models.py, and after adding one more I need to add one more attribute in to the already created class, I found in internet that the best way to do this is to use setattr() function, so I made all and.. It seems work, no bugs, it is showing in the Admin site as <django.db.models.fields.related.OneToOneField> , but there are no way to chose it when I trying to create or change this field, there aren't , is it a bug or I doing smth wrong? Here is my code:

class PlayerInstance(models.Model):
    #...
class Competition(models.Model):
    #...
setattr(PlayerInstance, 'choice_to_competition', models.OneToOneField(
    Competition,
    default = 'for_nothoing',
    on_delete=models.RESTRICT, 
    help_text='which competition is it taking part?',
))

And no, I didn't forget to add 'choice_to_competition' in to the list_display. Here 2 photos of Admin site: I made them, but I can't add them here, can't understand reason, but it is not really necessary.


Solution

  • I found in internet that the best way to do this is to use setattr() function, so I made all and.

    No. For "ordinary" classes that might work. But Django's Model has some advanced meta-logic, meaning attributes not passed when constructing the class, might end up not in the model.

    What you need is to use the .contribute_to_class(…) method, like:

    models.OneToOneField(
        Competition,
        default='for_nothoing',
        on_delete=models.RESTRICT,
        help_text='which competition is it taking part?',
    ).contribute_to_class(PlayerInstance, 'choice_to_competition')

    But, you don't need to do this trick to refer to a model not yet created. Indeed, for a ForeignKey [Django-doc], OneToOneField model field [Django-doc] and ManyToManyField model field [Django-doc], you can use a string literal to refer to a model that has not yet been defined, with:

    class PlayerInstance(models.Model):
        choice_to_competition = models.OneToOneField(
            'app_name.Competition',
            default='for_nothoing',
            on_delete=models.RESTRICT,
            help_text='which competition is it taking part?',
        )
        # …
    
    
    class Competition(models.Model):
        # …

    If the model has the same app label, you can even drop the app_label:

    class PlayerInstance(models.Model):
        choice_to_competition = models.OneToOneField(
            'Competition',
            default='for_nothoing',
            on_delete=models.RESTRICT,
            help_text='which competition is it taking part?',
        )
        # …
    
    
    class Competition(models.Model):
        # …

    Note: Models normally have no Instance suffix. An object from a class is always called an "instance", by naming the class …Instance, one gets the impression that the variable is an instance of a class.