1

I have AbstractProfile model with predefined PRIVACY_CHOICES:

class AbstractProfile(models.Model):
    PRIVACY_CHOICES = (
        (1, _('all')),
        (2, _('no one')),
    )

    title = models.CharField(_('title'), max_length=30)
    info = models.TextField(_('information'), max_length=500, blank=True)
    info_privacy = models.IntegerField(_('show information to'), default=1, choices=PRIVACY_CHOICES)

    city = models.CharField(_('location'), max_length=30, blank=True)
    city_privacy = models.IntegerField(_('show location to'), default=1, choices=PRIVACY_CHOICES)
    address = models.CharField(_('address'), max_length=30, blank=True)
    address_privacy = models.IntegerField(_('show address to'), default=1, choices=PRIVACY_CHOICES)

    class Meta:
        abstract = True

class UserProfile(AbstractProfile):
    PRIVACY_CHOICES = (
        (1, _('all')),
        (2, _('friends')),
        (3, _('friends of friends')),
        (4, _('only me')),
    )

    title = None

    first_name = models.CharField(_('first name'), max_length=30, blank=True)
    last_name = models.CharField(_('last name'), max_length=30, blank=True)
    names_privacy = models.IntegerField(_('show names to'), default=1, choices=PRIVACY_CHOICES)

    birth_date = models.DateField(_('birth date'), null=True, blank=True)
    birth_date_privacy = models.IntegerField(_('show birth date to'), default=1, choices=PRIVACY_CHOICES)

    avatar = models.ImageField(upload_to='users/avatar', null=True, blank=True)

UserProfile should have fields from AbstractProfile, but with its own PRIVACY_CHOICES. In the current realisation PRIVACY_CHOICES of UserProfile does not override PRIVACY_CHOICES of AbstractProfile. How is it possible to solve? In the future could be other models, which also should have its own PRIVACY_CHOICES

I use Django 1.10

TitanFighter
  • 4,582
  • 3
  • 45
  • 73
  • Instead of defining that privacy field on the abstract, why not just move it to CompanyProfile and not define it at all in the abstract? – Adam Hopkins Aug 24 '16 at 17:28
  • Do you mean to repeat the same fields in many classes as needed? Not a DRY way :) – TitanFighter Aug 24 '16 at 17:37
  • I see two models, and two definitions. Doesn't seem like it to me. And, yes DRY is important, but having an operational solution usually is more important. I guess that's a discussion best left for another venue. – Adam Hopkins Aug 24 '16 at 18:28
  • I had same issue and I gave up DRY. the answer below with "NEW PIECE OF CODE" is too complicated. DRY is important but simplicity is much more important. – kochul Jan 20 '21 at 03:39

1 Answers1

1

Found solution.

models.py:

class AbstractProfile(models.Model):
    PRIVACY_CHOICES = (
        (1, _('all')),
        (2, _('no one')),
    )

    title = models.CharField(_('title'), max_length=30)
    info = models.TextField(_('information'), max_length=500, blank=True)
    info_privacy = models.IntegerField(_('show information to'), default=1, choices=PRIVACY_CHOICES)

    city = models.CharField(_('location'), max_length=30, blank=True)
    city_privacy = models.IntegerField(_('show location to'), default=1, choices=PRIVACY_CHOICES)
    address = models.CharField(_('address'), max_length=30, blank=True)
    address_privacy = models.IntegerField(_('show address to'), default=1, choices=PRIVACY_CHOICES)

    class Meta:
        abstract = True

class UserProfile(AbstractProfile):
    PRIVACY_CHOICES = (
        (1, _('all')),
        (2, _('friends')),
        (3, _('friends of friends')),
        (4, _('only me')),
    )

    # NEW PIECE OF CODE
    def __init__(self, *args, **kwargs):
        def get_class_attrs(cls):
            return re.findall(r'\w+(?=[,\)])', cls.__dict__['__doc__'])

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

        all_fields = get_class_attrs(UserProfile)
        for each_field in all_fields:
            # all fields with '_privacy' in the name have 'choice' option
            if '_privacy' in each_field:
                self._meta.get_field(each_field).choices = self.PRIVACY_CHOICES

                default_privacy_choice = self.PRIVACY_CHOICES[0][0]
                if self._meta.get_field(each_field).default != default_privacy_choice:
                    self._meta.get_field(each_field).default = default_privacy_choice
    # END OF NEW PIECE OF CODE

    title = None

    first_name = models.CharField(_('first name'), max_length=30, blank=True)
    last_name = models.CharField(_('last name'), max_length=30, blank=True)
    names_privacy = models.IntegerField(_('show names to'), default=1, choices=PRIVACY_CHOICES)

    birth_date = models.DateField(_('birth date'), null=True, blank=True)
    birth_date_privacy = models.IntegerField(_('show birth date to'), default=1, choices=PRIVACY_CHOICES)

    avatar = models.ImageField(upload_to='users/avatar', null=True, blank=True)

class CompanyProfile(AbstractProfile):
    pass

class OneMoreClass(AbstractProfile):
    pass

Also is it necessary to modify forms.py:

class UserProfileForm(forms.ModelForm):
    class Meta:
        model = UserProfile()  # old_version was: model = UserProfile
        fields = ('title',
                  'first_name', 'last_name', 'names_privacy',
                  'birth_date', 'birth_date_privacy',
                  'info', 'info_privacy',
                  'city', 'city_privacy', 'address', 'address_privacy',
                  'avatar',)

Unmodified form takes choices from Abstract class. Now it is not required to repeat the same fields in different classes. If all classes have its own versions of choices, then method def __init__ can be copied to those classes with proper modifications (at least to change the class name), or even can be made as a separate function, but this is another story.

TitanFighter
  • 4,582
  • 3
  • 45
  • 73