Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to override the model.Manager.create() method in Django?

Tags:

python

django

I have plenty of Hardware models which have a HardwareType with various characteristics. Like so:

# models.py
from django.db import models

class HardwareType(model.Models):
    name = models.CharField(max_length=32, unique=True)

    # some characteristics of this particular piece of hardware
    weight = models.DecimalField(max_digits=12, decimal_places=3)
    # and more [...]        

class Hardware(models.Model):

    type = models.ForeignKey(HardwareType)

    # some attributes
    is_installed = models.BooleanField()
    location_installed = models.TextField()
    # and more [...]

If I wish to add a new Hardware object, I first have to retrieve the HardwareType every time, which is not very DRY:

tmp_hd_type = HardwareType.objects.get(name='NG35001')
new_hd = Hardware.objects.create(type=tmp_hd_type, is_installed=True, ...)

Therefore, I have tried to override the HardwareManager.create() method to automatically import the type when creating new Hardware like so:

# models.py
from django.db import models

class HardwareType(model.Models):
    name = models.CharField(max_length=32, unique=True)

    # some characteristics of this particular piece of hardware
    weight = models.DecimalField(max_digits=12, decimal_places=3)
    # and more [...] 

class HardwareManager(models.Manager):
    def create(self, *args, **kwargs):
        if 'type' in kwargs and kwargs['type'] is str:
            kwargs['type'] = HardwareType.objects.get(name=kwargs['type'])
        super(HardwareManager, self).create(*args, **kwargs)       

class Hardware(models.Model):
    objects = HardwareManager()

    type = models.ForeignKey(HardwareType)

    # some attributes
    is_installed = models.BooleanField()
    location_installed = models.TextField()
    # and more [...]

# so then I should be able to do:
new_hd = Hardware.objects.create(type='ND35001', is_installed=True, ...)

But I keep getting errors and really strange behaviors from the ORM (I don't have them right here, but I can post them if needed). I've searched in the Django documentation and the SO threads, but mostly I end up on solutions where:

  • the Hardware.save() method is overridden (should I get the HardwareType there ?) or,
  • the manager defines a new create_something method which calls self.create().

I also started digging into the code and saw that the Manager is some special kind of QuerySet but I don't know how to continue from there. I'd really like to replace the create method in place and I can't seem to manage this. What is preventing me from doing what I want to do?

like image 505
achedeuzot Avatar asked Jan 20 '16 12:01

achedeuzot


People also ask

How do I override a Django model?

Whenever one tries to create an instance of a model either from admin interface or django shell, save() function is run. We can override save function before storing the data in the database to apply some constraint or fill some ready only fields like SlugField.

What is def __ str __( self in Django model?

def __str__( self ): return "something" This will display the objects as something always in the admin interface. Most of the time we name the display name which one could understand using self object. For example return self.

How do I create an instance of a Django model?

To create a new instance of a model, instantiate it like any other Python class: class Model (**kwargs) The keyword arguments are the names of the fields you've defined on your model. Note that instantiating a model in no way touches your database; for that, you need to save() .


2 Answers

The insight from Alasdair's answer helped a lot to catch both strings and unicode strings, but what was actually missing was a return statement before the call to super(HardwareManager, self).create(*args, **kwargs) in the HardwareManager.create() method.

The errors I was getting in my tests yesterday evening (being tired when coding is not a good idea :P) were ValueError: Cannot assign None: [...] does not allow null values. because the subsequent usage of new_hd that I had create()d was None because my create() method didn't have a return. What a stupid mistake !

Final corrected code:

class HardwareManager(models.Manager):
    def create(self, *args, **kwargs):
        if 'type' in kwargs and isinstance(kwargs['type'], basestring):
            kwargs['type'] = HardwareType.objects.get(name=kwargs['type'])
        return super(HardwareManager, self).create(*args, **kwargs)   
like image 191
achedeuzot Avatar answered Sep 24 '22 02:09

achedeuzot


Without seeing the traceback, I think the problem is on this line.

    if 'type' in kwargs and kwargs['type'] is str:

This is checking whether kwargs['type'] is the same object as str, which will always be false.

In Python 3, to check whether `kwargs['type'] is a string, you should do:

    if 'type' in kwargs and isinstance(kwargs['type'], str):

If you are using Python 2, you should use basestring, to catch byte strings and unicode strings.

    if 'type' in kwargs and isinstance(kwargs['type'], basestring):
like image 35
Alasdair Avatar answered Sep 22 '22 02:09

Alasdair