Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Custom django foreignfield

Anybody knows how to create a foreignkey field and make it always point to same model, so far I got these.

class PanMachineTimeUnitField(models.ForeignKey):
    def __init__(self, **kwargs):
        to = 'panbas.PanBasTimeUnit'
        kwargs['verbose_name'] = _('Machine Unit')
        kwargs['related_name'] = 'machine_unit'
        super(PanMachineTimeUnitField, self).__init__(to, **kwargs)

But I got errors when on start. I aim to use it like,

machine_unit = PanMachineTimeUnitField()

No further declarations needed.

Edit: I want this because, I will have this foreignkey in quiet a few places. If I want to change the verbose_name of field, I want all of my fields to be affected by this change. Verbose name was an example, it may be an another attribute.

I dont want to use settings py to declare the defaults, either.

like image 606
durdenk Avatar asked Jun 29 '17 23:06

durdenk


Video Answer


1 Answers

I recommend that you use only a simple function to create a similarly pre-configured instance of ForeignKey: (not an instance of subclass of ForeignKey)

def pan_machine_time_unit_field(**kwargs):
    othermodel = 'panbas.PanBasTimeUnit'
    on_delete = models.DO_NOTHING  # or what you need
    kwargs['verbose_name'] = 'Machine Unit'
    kwargs.setdefault('related_name', '+')
    # or: kwargs.setdefault('related_name', "%(app_label)s_%(class)s_related",
    return models.ForeignKey(othermodel, on_delete, **kwargs)

class C(models.Model):
    machine_unit = pan_machine_time_unit_field()
    # or:
    # machine_unit = pan_machine_time_unit_field(related_name='klass_c_children')

The related_name attribute is a name used for backward relation from the target object of othermodel to all objects that reference it. That name must be unique on othermodel ('panbas.PanBasTimeUnit', usually something with app and class name that is unique enough) or that name can be '+' if you don't want to create a backward relationship query set. Both variants are implied in the example. Also remember on_delete.

If you would really need to create a subclass (which makes sense if more methods need be customized), you must also define a deconstruct method for migrations. It would be complicated if you need to modify such subclass later. It can be never removed, renamed etc. due to migrations on a custom field. On the other hand, if you create a simple instance of ForeignKey directly by a function, all about migrations can be ignored.


EDIT

Alternatively you can create an abstract base model with that field and create new models by inheritance or multiple inheritance:

class WithPanBasTimeUnit(models.Model):
    machine_unit = models.ForeignKey(
        'panbas.PanBasTimeUnit',
        models.DO_NOTHING,
        verbose_name=_('Machine Unit'),
        related_name='%(app_label)s_%(class)s_related'
    )

    class Meta:
        abstract = True

class ExampleModel(WithPanBasTimeUnit, ...or more possible base models...):
    ... other fields

This solution (inspired by an invalid soution Ykh) useful if you want to add a method to models with that field or to add more fields together, otherwise the original solution is easier.

like image 117
hynekcer Avatar answered Sep 22 '22 23:09

hynekcer