48

I'm writing a mixin which will allow my Models to be easily translated into a deep dict of values (kind of like .values(), but traversing relationships). The cleanest place to do the definitions of these seems to be in the models themselves, a la:

class Person(models.Model, DeepValues):
    name = models.CharField(blank=True, max_length=100)
    tribe = models.ForeignKey('Tribes')

    class Meta:
        schema = {
            'name' : str,
            'tribe' : {
                'name' : str
            }
        }

Person.objects.all().deep_values() => {
    'name' : 'Andrey Fedorov',
    'tribe' : {
        'name' : 'Mohicans'
    }
}

However, Django complains about my including this in class Meta with:

TypeError: 'class Meta' got invalid attribute(s): schema

(entire stack trace here)

Now, I suppose I could elaborately override this in my mixin, but is there a more elegant way of storing this information?

4 Answers 4

66

I don't know about elegant, but one pragmatic way is:

import django.db.models.options as options

options.DEFAULT_NAMES = options.DEFAULT_NAMES + ('schema',)

Obviously, this would break if Django ever added a 'schema' attribute of its own. But hey, it's a thought...you could always pick an attribute name which is less likely to clash.

Sign up to request clarification or add additional context in comments.

3 Comments

For posterity's sake, that doesn't work, but this did: options.DEFAULT_NAMES = options.DEFAULT_NAMES + ('default_values',)
Oh... it's presumably because my solution converts DEFAULT_NAMES to a list, whereas your refinement keeps it as a tuple.
This works well for me options.DEFAULT_NAMES = (*options.DEFAULT_NAMES, 'my_custom_attr')
5

Not a direct answer, but I did not like the idea of adding it in every model where I need it to the options, so I did:

class MyModel(models.Model):
    
    class Meta:
        ordering = ["myfield"]

    class MyPrefixMeta:
        my_value = "Abc"

You could even put this to a abstract model and validate the set class properties in __init__ function or do things like adding a _myprefix_meta property to the model. So you had your own meta class.

Comments

3

This works for me for setting extra fields in meta for a Django model.

class Vendor(CustomModel):

    def __init__(self, *args, **kwargs):
        cls = self.__class__
        meta = getattr(cls, '_meta', None)
        setattr(meta, 'exclude_logging', ["otp", "is_otp_verified"])
        super().__init__(*args, **kwargs)

This exclude_logging field can be accessed as below.

class CustomModel(models.Model):

    objects = UpdateManager()

    class Meta:
        abstract = True

    def save(self, *args, **kwargs):
        print(self._meta.exclude_logging)

I hope this solves the problem.

1 Comment

The detail about _meta being present saved me - any fields I added using the above method didn't materialize when accessing model.Meta directly, but looking under _meta (the actual instance) solved it! Thank you!
0

To add to the above I have found another way to add custom attributes to a meta-class by simply prefixing the attribute with an underscore (_). So the above example would work as follows:

class Person(models.Model, DeepValues):
    name = models.CharField(blank=True, max_length=100)
    tribe = models.ForeignKey('Tribes')

    class Meta:
        _schema = {
            'name' : str,
            'tribe' : {
                'name' : str
            }
        }

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.