FEATURE REQUEST: Choices overriding in a classes, which inherit abstract class

736 views
Skip to first unread message

Anton Ponomarenko

unread,
Aug 25, 2016, 12:36:49 PM8/25/16
to Django developers (Contributions to Django itself)
From the doc: "Abstract base classes are useful when you want to put some common information into a number of other models". In my current project I put all common fields in abstact class, but those fields that use 'choice' option may have different choices in different child classes.

Code Example:

class AbstractProfile(models.Model):
   
# Basic choices, which may vary in different classes
    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)
    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')),
   
)

    GENDER_CHOICES
= (
       
(1, _('man')),
       
(2, _('woman')),
   
)

    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)

    gender
= models.IntegerField(_('gender'), null=True, blank=True, choices=GENDER_CHOICES)

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

class CompanyProfile(AbstractProfile):
    PRIVACY_CHOICES
= (*** new choices, which are different to those which are in abstract class ***)

class TeamProfile(AbstractProfile):
   
pass

There is a proposition to add to the 'class Meta' a new attribute, which lets to define, whether to use CHOICES from abstract class or use those which exists in the current class, something like:
class UserProfile(AbstractProfile):
    PRIVACY_CHOICES
= (
       
(1, _('all')),
       
(2, _('friends')),
       
(3, _('friends of friends')),
       
(4, _('only me')),
   
)

    GENDER_CHOICES
= (
       
(1, _('man')),
       
(2, _('woman')),
   
)

    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)

    gender
= models.IntegerField(_('gender'), null=True, blank=True, choices=GENDER_CHOICES)

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

   
class Meta:
        use_choices
= [PRIVACY_CHOICES]

Possible variants:
Variant A: Use CHOICES from abstract class - do not add anything to 'class Meta'
Variant B: Use CHOICES from current class instead of abstract class - add:
class Meta:
    use_choices
= [*** list of choices of current class **]

Variant C: Same as B, but change format a bit:
class Meta:
    use_choices
= ((ABSTRACT_CHOICES_NAME1, CURRENT_CLASS_CHOICES_NAME1), (ABSTRACT_CHOICES_NAME2, CURRENT_CLASS_CHOICES_NAME2),)
just in case, if a developer for some reasons have different names of choices

This feature lets to have development process as DRY as possible, which is the reason why abstract class exists.

In order to solve it, currenty I use the next code:
# models.py
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
   
*** rest fields ***

# forms.py
class UserProfileForm(forms.ModelForm):
   
class Meta:
        model
= UserProfile()  # old_version was: model = UserProfile
        fields
= (*** fields ***)
but I dont like this way.

Thanks

Marc Tamlyn

unread,
Aug 26, 2016, 4:21:34 AM8/26/16
to django-d...@googlegroups.com
Adding a new meta option is very unlikely to happen.

Really, this is a question about dynamic choices, rather than just inheritance. I'd rather see some sort of solution which allows some (optional) hooks to customise choices on a per-class (or maybe even per instance) basis. This is tricky though as choices are generally seen to be a static attribute of the field.

--
You received this message because you are subscribed to the Google Groups "Django developers (Contributions to Django itself)" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-developers+unsubscribe@googlegroups.com.
To post to this group, send email to django-developers@googlegroups.com.
Visit this group at https://groups.google.com/group/django-developers.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/bc4b5ecb-0276-413f-b18f-1d26cd7bf6b4%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply all
Reply to author
Forward
0 new messages