2

I am working with Django 3.2 and I would like to create an abstract class with 2 fields whose default values (or their blank attribute) could change in each of the child classes.

I'd like not to override the full fields but only the options that change. That way, if I decide to make the icon field unique in the future, I should only change the parent abstract class and not all the child models.

# behaviors.py

from django.db import models
from colorfield.fields import ColorField


class Representable(models.Model):
    color = ColorField(_("color in the app"), blank=True)
    icon = models.CharField(_("icon in the app"), max_length=50, blank=True)

    class Meta:
        abstract = True
# models.py

from django.db import models
from .behaviors import Representable


class Item(Representable, models.Model):
    name = models.CharField(_("name"), max_length=50, blank=False)
    # color couldn't be blank
    # icon couldn't be blank    

    class Meta:
        verbose_name = _("item")
        verbose_name_plural = _("items")


class Tag(Representable, models.Model):
    name = models.CharField(_("name"), max_length=200, blank=False, unique=True)
    # color default value shoud be #00AEC7 (but could be blank)
    # icon default value shoud be mdi-label (but could be blank)    

    class Meta:
        verbose_name = _("tag")
        verbose_name_plural = _("tags")

Any ideas?

pablolmedorado
  • 1,181
  • 2
  • 8
  • 11
  • I'd need something like this https://stackoverflow.com/a/57070366/3333028 But this solution monkey-patches the field and keep the changes made across the entire app. – pablolmedorado Oct 13 '21 at 07:02

3 Answers3

1

You can override fields in subclasses of abstract models and set whatever attributes you want

class Tag(Representable):
    ...
    color = ColorField(_("color in the app"), blank=True, default='#00AEC7')
    icon = models.CharField(_("icon in the app"), max_length=50, blank=True, default='mdi-label')
Iain Shelvington
  • 31,030
  • 3
  • 31
  • 50
  • You're absolutely right. I forgot to clarify that I know the standard way to do it. What I'm looking for is to reuse as much code as possible. Imagine that in a few months I decide that the icon must be unique. I wouldn't like to edit all the fields in all the child models. – pablolmedorado Oct 13 '21 at 06:43
  • I've seen solutions like this one: https://stackoverflow.com/a/57070366/3333028 I can't monkey patch the field, since there will be many child models with different configurations. – pablolmedorado Oct 13 '21 at 06:48
  • 1
    Your wish is to be able to override attributes of inherited fields without re-defining them? – Iain Shelvington Oct 13 '21 at 06:48
  • Exactly. I'd love to keep my code as DRY as possible. I'd like to re-define only those options that change, not the full field. – pablolmedorado Oct 13 '21 at 06:52
1

Not an expert but this can work:

from django.db import models
from .behaviors import Representable


class Item(Representable):
    name = models.CharField(_("name"), max_length=50, blank=False, null=True)
    color = ColorField(_("color in the app"), blank=False)
    icon = models.CharField(_("icon in the app"), max_length=50, blank=False)   

    class Meta:
        verbose_name = _("item")
        verbose_name_plural = _("items")


class Tag(Representable):
    name = models.CharField(_("name"), max_length=200, blank=False, unique=True)
    color = ColorField(_("color in the app"), blank=True,null=True, default='#00AEC7')
    icon = models.CharField(_("icon in the app"), max_length=50, blank=True, null=True, default='mdi-label')   

    class Meta:
        verbose_name = _("tag")
        verbose_name_plural = _("tags")

sunil ghimire
  • 505
  • 4
  • 14
  • You're right, that's the standard way to do it. But, I'm trying not to override the full field, only the options that change. Sorry, I forgot to mention that in the question :( – pablolmedorado Oct 13 '21 at 06:55
0

The below code achieves what you want, and works in my own cursory testing, but I'm unsure whether it's advisable to deepcopy() a field:

# models.py

from copy import deepcopy
from django.db import models
from .behaviors import Representable

ColorRequiredField = deepcopy(Representable._meta.get_field('color'))
ColorRequiredField.blank = False
IconRequiredField = deepcopy(Representable._meta.get_field('icon'))
IconRequiredField.blank = False


class Item(Representable, models.Model):
    name = models.CharField(_("name"), max_length=50, blank=False)
    # color couldn't be blank
    color = ColorRequiredField
    # icon couldn't be blank    
    icon = IconRequiredField


TagColorField = deepcopy(Representable._meta.get_field('color'))
TagColorField.default = '#00AEC7'
TagIconField = deepcopy(Representable._meta.get_field('icon'))
TagIconField.default = 'mdi-label'

class Tag(Representable, models.Model):
    name = models.CharField(_("name"), max_length=200, blank=False, unique=True)
    # color default value shoud be #00AEC7 (but could be blank)
    color = TagColorField
    # icon default value shoud be mdi-label (but could be blank)
    icon = TagIconField
qff
  • 5,524
  • 3
  • 37
  • 62