nemesifier
nemesifier

Reputation: 8539

Dynamically set class attributes (model fields) while iterating a dictionary

I'm trying to define the fields of a model dynamically depending on the key, value pairs contained in a dictionary.

I've attempted two ways:

the dictionary is:

NOTIFICATION_TYPES = {
    'friend_request_received': 0,
    'friend_request_accepted': 1,
    # eccetera
}

the very wrong (generate an exception because self is not defined):

class EmailNotification(models.Model):
    """
    User Email Notification Model
    Takes care of tracking the user's email notification preferences
    """
    user = models.OneToOneField(User, verbose_name=_('user'))

    for key, value in NOTIFICATION_TYPES.items():
        setattr(self, key, models.BooleanField(_('notify new matches'), default=True))

    class Meta:
        db_table = 'profile_email_notification'

the apparently less wrong but does not create the model fields:

class EmailNotification(models.Model):
    """
    User Email Notification Model
    Takes care of tracking the user's email notification preferences
    """
    user = models.OneToOneField(User, verbose_name=_('user'))

    def __init__(self, *args, **kwargs):

        for key, value in NOTIFICATION_TYPES.items():
            setattr(self.__class__, key, models.BooleanField(_(key), default=True))

        super(EmailNotification, self).__init__(*args, **kwargs)

    class Meta:
        db_table = 'profile_email_notification'

Is it possible to do what i'm trying to do? I'm sure it is!

Upvotes: 0

Views: 225

Answers (3)

nemesifier
nemesifier

Reputation: 8539

Thanks to the suggestions in the answers, but there was a key missing concept.

Two things must be done:

setattr(myclass, key, field)
myclass.add_to_class(key, field)

A working solution here:

https://github.com/nemesisdesign/nodeshot/blob/7798f81d558791499039afab1c860483fe30dd77/nodeshot/community/notifications/models/user_settings.py#L6

Upvotes: 0

Martijn Pieters
Martijn Pieters

Reputation: 1125078

You'll need to set those extra attributes after defining the class:

class EmailNotification(models.Model):
    """
    User Email Notification Model
    Takes care of tracking the user's email notification preferences
    """
    user = models.OneToOneField(User, verbose_name=_('user'))

    class Meta:
        db_table = 'profile_email_notification'


for key, value in NOTIFICATION_TYPES.items():
    setattr(EmailNotification, key, models.BooleanField(_('notify new matches'), default=True))

You could use a class decorator to wrap the for loop into a function applied to the class:

def add_notification(cls):
    for key, value in NOTIFICATION_TYPES.items():
        setattr(cls, key, models.BooleanField(_('notify new matches'), default=True))
    return cls

@add_notification
class EmailNotification:
    # ...

I am somewhat worried that the Django metaclass handling wants to process these fields though, and you may need to add additional calls to make the EmailNotification._meta structure aware of the additional fields that you have added.

Upvotes: 1

unutbu
unutbu

Reputation: 880927

You could use a class decorator:

def add_notification(cls):
    for key in NOTIFICATION_TYPES:
        setattr(cls, key, models.BooleanField(_('notify new matches'), default=True))
    return cls

@add_notification
class EmailNotification(models.Model):
    """
    User Email Notification Model
    Takes care of tracking the user's email notification preferences
    """
    user = models.OneToOneField(User, verbose_name=_('user'))

    class Meta:
        db_table = 'profile_email_notification'

Upvotes: 1

Related Questions