5

I have two custom Django fields, a JSONField and a CompressedField, both of which work well. I would like to also have a CompressedJSONField, and I was rather hoping I could do this:

class CompressedJSONField(JSONField, CompressedField):
    pass

but on import I get:

RuntimeError: maximum recursion depth exceeded while calling a Python object

I can find information about using models with multiple inheritance in Django, but nothing about doing the same with fields. Should this be possible? Or should I just give up at this stage?

edit:

Just to be clear, I don't think this has anything to do with the specifics of my code, as the following code has exactly the same problem:

class CustomField(models.TextField, models.CharField):
    pass

edit 2:

I'm using Python 2.6.6 and Django 1.3 at present. Here is the full code of my stripped-right-down test example:

customfields.py

from django.db import models


class CompressedField(models.TextField):
    """ Standard TextField with automatic compression/decompression. """

    __metaclass__ = models.SubfieldBase
    description = 'Field which compresses stored data.'

    def to_python(self, value):
        return value

    def get_db_prep_value(self, value, **kwargs):
        return super(CompressedField, self)\
                        .get_db_prep_value(value, prepared=True)


class JSONField(models.TextField):
    """ JSONField with automatic serialization/deserialization. """

    __metaclass__ = models.SubfieldBase
    description = 'Field which stores a JSON object'

    def to_python(self, value):
        return value

    def get_db_prep_save(self, value, **kwargs):
        return super(JSONField, self).get_db_prep_save(value, **kwargs)


class CompressedJSONField(JSONField, CompressedField):
    pass

models.py

from django.db import models
from customfields import CompressedField, JSONField, CompressedJSONField

class TestModel(models.Model):

    name = models.CharField(max_length=150)
    compressed_field = CompressedField()
    json_field = JSONField()
    compressed_json_field = CompressedJSONField()

    def __unicode__(self):
        return self.name

as soon as I add the compressed_json_field = CompressedJSONField() line I get errors when initializing Django.

simon
  • 15,344
  • 5
  • 45
  • 67
  • 1
    It is unable to tell what is going on unless you post the code for `JSONField` and `CompressedField` – pajton Apr 02 '11 at 16:25
  • @pajton: I don't think that code is relevant -- see my edit. (I am happy to post it if you really want to see it, however :)) – simon Apr 02 '11 at 16:36
  • Hm, interesting...I have no clue why this is happening – pajton Apr 02 '11 at 20:34
  • is "pass" the actual code that's giving you the error? and where is the customfield saved and imported to? – dting Apr 07 '11 at 07:29
  • @kriegar -- yes, the offending code is as simple as I've shown. I'm sorry but I don't think I understand your other question. The code is in a separate module, and called with `from custom_field import CustomField` at the top of my `models.py`, if that's what you mean... – simon Apr 07 '11 at 15:44
  • @simon I'm unable to replicate your issue. I'm able to use a custom field with multiple inheritance and not get an runtime error. can you post more code? – dting Apr 07 '11 at 23:56
  • What versions of python/django are you using? – James Khoury Apr 08 '11 at 01:01

2 Answers2

3

after doing a few quick tests i found that if you remove the metaclass from the JSON and compressed fields and put it in the compressedJSON field it compiles. if you then need the JSON or Compressed fields then subclass them and jusst add the __metaclass__ = models.SubfieldBase

i have to admit that i didn't do any heavy testing with this:

from django.db import models                                                       


class CompressedField(models.TextField):                                           
    """ Standard TextField with automatic compression/decompression. """           

    description = 'Field which compresses stored data.'                            

    def to_python(self, value):                                                    
        return value                                                               

    def get_db_prep_value(self, value, **kwargs):                                  
        return super(CompressedField, self).get_db_prep_value(value, prepared=True)


class JSONField(models.TextField):                                                 
    """ JSONField with automatic serialization/deserialization. """                

    description = 'Field which stores a JSON object'                               

    def to_python(self, value):                                                    
        return value 

    def get_db_prep_save(self, value, **kwargs):                                   
        return super(JSONField, self).get_db_prep_save(value, **kwargs)            


class CompressedJSONField(JSONField, CompressedField):                             
    __metaclass__ = models.SubfieldBase                                            

class TestModel(models.Model):                                                     

    name = models.CharField(max_length=150)                                        
    #compressed_field = CompressedField()                                          
    #json_field = JSONField()                                                      
    compressed_json_field = CompressedJSONField()                                  

    def __unicode__(self):                                                         
        return self.name

if you then want to uses the JSON and Commpressed fields separately i assume this idea will work:

class JSONFieldSubClass(JSONField):
    __metaclass__ = models.SubfieldBase

Honestly ... I don't really understand any of this.

EDIT base method hack

class CompressedJSONField(JSONField, CompressedField):
    __metaclass__ = models.SubfieldBase

    def to_python(self, value):
        value = JSONField.to_python(self, value)
        value = CompressedField.to_python(self, value)
        return value

the other way is to make the to_python() on the classes have unique names and call them in your inherited classes to_python() methods

or maybe check out this answer

EDIT after some reading if you implement a call to super(class, self).method(args) in the first base to_python() then it will call the second base. If you use super consistently then you shouldn't have any problems. http://docs.python.org/library/functions.html#super is worth checking out and http://www.artima.com/weblogs/viewpost.jsp?thread=237121

class base1(object):                                                               
    def name(self, value):                                                         
        print "base1", value                                                       
        super(base1, self).name(value)                                             

    def to_python(self, value):                                                    
        value = value + " base 1 "                                                 
        if(hasattr(super(base1, self), "to_python")):                              
            value = super(base1, self).to_python(value)                            
        return value                                                               

class base2(object):                                                               
    def name(self, value):                                                         
        print "base2", value                                                       

    def to_python(self, value):                                                    
        value = value + " base 2 "                                                 
        if(hasattr(super(base2, self), "to_python")):                              
            value = super(base2, self).to_python(value)                            
        return value                                                               

class superClass(base1, base2):                                                    
    def name(self, value):                                                         
        super(superClass, self).name(value)                                        
        print "super Class", value    
Community
  • 1
  • 1
James Khoury
  • 21,330
  • 4
  • 34
  • 65
  • this is very helpful, thank you. I spent a certain amount of time fiddling slightly aimlessly with things to see if I could get it to work, but I didn't try this! My concern is, in this case, will each class's `to_python()` method be called properly? ([This, of course, is the main point of these particular fields](http://docs.djangoproject.com/en/dev/howto/custom-model-fields/#the-subfieldbase-metaclass).) I've got something to work with now, though, thanks! – simon Apr 08 '11 at 06:23
  • @simon i have no idea which will get called.. but there is only one way to find out. Let us know what happens! ;) – James Khoury Apr 08 '11 at 07:06
  • @simon [python docs](http://docs.python.org/release/3.2/tutorial/classes.html#multiple-inheritance) seem to suggest the first base class has priority. But as I said this is mostly above my python level of knowledge. – James Khoury Apr 08 '11 at 07:09
  • @james -- my concern was not the order in which they're called, but whether they would get called at all. I've now had chance to test this properly, and, set up this way, only the methods of the first parent are called at all :( so I'm back to square one :( – simon Apr 11 '11 at 03:53
  • @simon there is a hack. I added it into the post so you can read it better. – James Khoury Apr 11 '11 at 04:43
  • @james -- thank you! I've learnt plenty from this, and the links you've provided. My main problem was that I was labouring under the misapprehension that multiple inheritance mean that the methods from both (or all) base classes would be called, not simply the first on found in the MRO. I've implemented the hackish method you suggested, because I realised I need a different order of steps on the way in and on the way out in this case (to JSON -> compress / decompress -> from JSON), and I'm satisfied with the result. Cheers! – simon Apr 11 '11 at 06:45
-2

It is hard to understand when exactly you are getting that error. But looking at DJango code, there is simlar implementation (multiple inheritance)

refer: class ImageFieldFile(ImageFile, FieldFile)
in django/db/models/fields

Narendra Kamma
  • 1,431
  • 1
  • 11
  • 19
  • Are you able to provide more information, and an explanation, rather than just a reference to a link of an example? – Josh Smeaton Apr 07 '11 at 07:56
  • `ImageFieldFile` is a file-like class, not a field class (and so are both `ImageFile` and `FieldFile`). The image field class is called `ImageField`, which has a single inheritance from `FileField` which in turn inherits singly from `Field`. As far as I am aware, all standard field classes have names ending with `Field`. – Blair Apr 11 '11 at 05:14