42

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?

MPelletier
  • 16,256
  • 15
  • 86
  • 137
Andrey Fedorov
  • 9,148
  • 20
  • 67
  • 99

3 Answers3

61

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.

Carl Meyer
  • 122,012
  • 20
  • 106
  • 116
Vinay Sajip
  • 95,872
  • 14
  • 179
  • 191
  • 5
    For posterity's sake, that doesn't work, but this did: options.DEFAULT_NAMES = options.DEFAULT_NAMES + ('default_values',) – Andrey Fedorov Jul 06 '09 at 19:30
  • 2
    Oh... it's presumably because my solution converts DEFAULT_NAMES to a list, whereas your refinement keeps it as a tuple. – Vinay Sajip Jul 06 '09 at 19:41
3

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.

user1383029
  • 1,685
  • 2
  • 19
  • 37
2

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.

Nihal Sharma
  • 2,397
  • 11
  • 41
  • 57
  • 1
    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! – MatsLindh Jun 19 '23 at 12:39