Search code examples
pythondjangoormdatabase-normalization

should admin-only fields be extracted into a dedicated model which has a OneToOne relation to the original model?


I have the following model

class User(AbstractUser):
    # other fields...
    billing_active = models.BooleanField(
        _('Billing active'),
        default=True,
        help_text=_(
            'designates whether the user should be charged'
        )
    )
    billing_start = models.DateField(_('Billing cycle start'))
    billing_end = models.DateField(_('Billing cycle end'))
    billing_charge = models.DecimalField(
        _('Charge'),
        max_digits=5,
        decimal_places=2,
        help_text='amount to be charged'
    )

by looking at those prefixes billing_* it seems that fields should belong to a new object, it seems OK since it's just a database representation. But billing_active and billing_charge are admin fields and can't be changed by user, would it be good to create a new model UserBillingSettings which contains all those fields? then I could use django's builtin permissions system:

class User(AbstractUser):
    # fields ...

class UserBillingSettings(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='billing_settings')
    # billing fields
    class Meta:
        permissions = (('can_change', 'Change user billing settings'),)

Solution

  • You are absolutely right, you always need to separate such details -Not directly related to the Users table, into their own table.

    class BillingInfo(models.Model):
        billing_active = models.BooleanField(
            _('Billing active'),
            default=True,
            help_text=_(
                'designates whether the user should be charged'
            )
        )
        billing_start = models.DateField(_('Billing cycle start'))
        billing_end = models.DateField(_('Billing cycle end'))
        billing_charge = models.DecimalField(
            _('Charge'),
            max_digits=5,
            decimal_places=2,
            help_text='amount to be charged'
        )
        ...
        class Meta:
            permissions = (('can_change', 'Change user billing settings'),)
    
    class User(AbstractUser):
        ... user info
        billing_info = models.OneToOneField(BillingInfo, on_delete=models.CASCADE)