I have the following django model:
class Article(models.Model):
filename = models.CharField(max_length=255)
collection = models.ForeignKey(Collection, on_delete=models.CASCADE)
keys = ['filename', 'collection']
class Meta:
constraints = [
models.UniqueConstraint(
fields=['filename', 'collection'],
name='article_key')
]
As you can see I've defined the same list ['filename', 'collection'] in both the base class and the Meta class. I would like to define it once. I can't define it in Meta because then I get 'Meta got an unrecognised attribute 'keys'. So I must define it in the base class and access it from Meta. I don't know how to share data between the two. I've tried doing:
self.keys
in Meta but that gives 'self is not defined'. I've also tried with just 'keys' but that's also not defined. Any tips? Thanks.
EDIT
Thank you to Willem for pointing out that I can define keys in Meta if I just call it '_keys'. If I do this, however, the question is then how do I access _keys from the base class? I've tried 'meta._keys' and 'Meta._keys'. Both not defined.
EDIT 2
For clarity, the reason that I want 'keys' defined in the base class is that I will (a) be accessing it from properties on the base class, and (b) want to be able to access it from the outside.
You can declare it before the class, then reference it from both the model class and it' Meta:
# making it a tuple since you probably don't want
# it to be mutable
_ARTICLE_KEYS = ('filename', 'collection')
class Article(models.Model):
# making it an implementation attribute since you
# probably don't want to be writeable
# (hint: provide a read-only property for access)
_keys = _ARTICLE_KEYS
class Meta:
constraints = [
models.UniqueConstraint(
fields=_ARTICLE_KEYS,
name='article_key')
]
But this is still ugly IMHO and very probably unecessary - the model's methods should be able to access those values thru self._meta.contraints[0].fields or something similar (don't have models with such constraints at hand right now so I can check how this is actually transformed by the models's metaclass but inspecting self._meta in your django shell should give you the answer).
The methods of a nested class cannot directly access the instance attributes of the outer class.
So, in your case, If you won't use the keys list in the Article class, just defined it once in the Meta class. Otherwise, you need to defined twice!
Related
Origin of question I'm recently working with django and became used to of Meta class in models, Serializers, and Forms.
My Understanding so far I learned that meta classes are used for creating classes.
When one class is defined, Python will go inside the class and collect all attributes and methods and store as dictionary, after that it searches for __metaclass__ attribute. If defined, it will use that class to create the defined class else it will use default object.
Object is default class which is inherited to all classes, and this object class must have __metaclass__ which is type by default.
type class have __new__ and __init__ methods which is used to create classes.
My question
What is the flow of creating a class when we declare Meta class inside definition of class
For example
class Transformer(models.Model):
name = models.CharField(max_length=150, unique=True)
class Meta:
ordering = ('name',)
Where and When this Meta class is used?
Edit 1:
Cleared one thing that metaclasses and django Meta are different.
So Meta is just nested class of Transformer Model Class.
Question: Still my quesition is how this Meta class is used by Model Class?
As put in the comments: Python metaclasses are different from django metaclasses: Django just, for historical reasons, use the same terminology for the inner class where one annotates extra parameters about a class, where the primary members of the outer class are meant to correspond to fields in a model or form.
A Python metaclass, on the other hand, are what you are describing in your example, though you have checked some Python 2 documentation. In current Python, the metaclass is determined by passing the keyword argument "metaclas=" in the declaration of a new class, where the base classes go:
class MyClass(Base1, Base2, metaclass=MyMeta):
...
As far as I know it, the Django behavior had origin in which early versions of Django actually used a custom (Python) metaclass to annotate some of the parameters now used in the nested Meta - and in doing so, it took a shortcut of defining the metaclass inline inside the class body: instead of assigning the __metaclass__ name to an externally defined metaclass, as the usual for normal use, it would just define the class inplace: from the point of view of the language runtime, it would find the name __metaclass__ bound to a valid metaclass and use that to build the class.
Later versions, even in Python 2, modified this approach - the inner class was no longer the actual "metaclass" of the Model or Form (as the previous approach was clearly overkill).
Model Meta is basically the inner class of your model class. Model Meta is basically used to change the behavior of your model fields like changing order options,verbose_name_plural, and a lot of other options. It’s completely optional to add a Meta class to your model.
example:
class Category (models.Model):
name = models.CharField(max_length=255)
slug = models.SlugField(max_length=255, unique=True)
def __str__(self):
return self.name
class Meta:
verbose_name_plural= 'Categories'
Hi I have 2 classes a Model and Parent Class
ModelMaster:
myvar: string = "one"
def myfun():
# do stuff
Model(ModelMaster):
first: string = "hello"
second: int = 123
Modelmaster contains functions that all models should use and attributes
When using the Model as purely a model I want to get the Model instance including all of its attributes but I dont want to see anything that was inherited from ModelMaster
Is there someway to get, from instance of Model, a copy of that model with just the attributes of that class that has all parent attributes and methods removed.
It seems like there would be something built into python3 to do this, rather than writing this myself
anyone have any ideas?
thanks,
Simon
You can create a new class with Model.__dict__ passed to the type constructor, but not Model's base class:
PureModel = type('PureModel', (), dict(Model.__dict__))
so that:
print(PureModel().first)
outputs:
hello
and that PureModel().myvar would result in:
AttributeError: 'PureModel' object has no attribute 'myvar'
The best solution I found was to use
annotations = self.__annotations__
keys = {}
for annotation in annotations:
name = annotation
type = annotations[annotation]
data = self.__getattribute__(annotation)
keys.append({"name":name, "type":type, "data":data})
this method can exist in any class in the inheritance tree and it will only return the attributes for the top level class
I'm still hoping that someone might come up with a cleaner version but this works for me
So I want to create a django filters.FilterSet from django-filter module, but I want to dynamically add its attributes. For example, if I wanted to add SubName dynamically:
class UsersInfoFilter(filters.FilterSet):
Name=NumberFilter(lookup_type='gte')
def __new__(self):
self.SubName=NumberFilter(lookup_type='gte')
self.Meta.fields.append('SubName')
class Meta:
model = UsersInfo
fields = ['UserID', 'LanguageID', 'Name']
The problem is that FilterSet is a metaclass that immediately runs once the class has been figured out, so there is nowhere before that point that items can be dynamically added.
I've tried putting a function in as a parameter around filters.FilterSet class UsersInfo(AddObjects(filters.FilterSet)) which returns exactly what is passes, but I cannot reference UsersInfoFilter at that point since it still isn't finished being created.
I also tried making UsersInfoFilter its own base class, and then creating a class RealUsersInfoFilter(UsersInfoFilter, filters.FilterSet) as my actual filter, but then FilterSet just throws warnings about missing attributes named as fields.
There doesn't seem to be any kind of constructor function for classes in python. I'm assuming I have to do some kind of magic with metaclasses, but I've tried every combination I can think of and am at wits end.
You can't change Meta subclass from the __init__ method... there are 2 options to approach your issue...
First one - define "wide" filter on all of the model fields:
class UsersInfoFilter(filters.FilterSet):
class Meta:
model = UsersInfo
It will create default filters for all your model fields.
Second, define dynamic fields:
class UsersInfoFilter(filters.FilterSet):
name = NumberFilter(lookup_type='gte')
def __init__(self):
super(UsersInfoFilter, self).__init__()
base_filters['subname'] = NumberFilter(name='subname', lookup_type='gte')
class Meta:
model = UsersInfo
fields = ['user_id', 'language_id', 'name']
(I do not know if this is something you really want - because despite "dynamic" adding field - it should be declared as static - there are no logic here)
p.s.
why CamelCase on properties and fields? use proper pep-8.
To dynamically choose the fields in the FilterSet, I suggest to create a FilterSet factory like this:
def filterset_factory(model, fields):
meta = type(str('Meta'), (object,), {'model': model, 'fields': fields})
filterset = type(str('%sFilterSet' % model._meta.object_name),
(FilterSet,), {'Meta': meta})
return filterset
And then use it like:
DynamicFilterClass = filterset_factory(model=MyModel, fields=[...])
dynamic_filter = DynamicFilterClass(request.GET, queryset=instances)
some newbie question here, I have a model like so:
from django.db import models
class SomeCommons(object):
# some fields here
# ...
class Meta:
abstract=True
class SomeDjangoModels(SomeCommons,models.Model):
pass
is it the same as the following model :
from django.db import models
class SomeModels(models.Model):
# some fields here
# ...
class Meta:
abstract=True
What I know that when doing like so SomeDjangoModels(SomeCommons,models.Model) the attribute from SomeCommons will be available in SomeDjangoModels, but the question is if the SomeCommons contains django Meta class will the Meta class also available in SomeDjangoModels? if it is, is there a way to prove it (the Meta class does exists)?
thanx
Yes, meta classes are inherited...
Meta inheritance
When an abstract base class is created, Django makes any Meta inner class you declared in the base class available as an attribute. If a child class does not declare its own Meta class, it will inherit the parent’s Meta. If the child wants to extend the parent’s Meta class, it can subclass it.
But in your case it does nothing as absrtact is set to False on the inheriting child class.
Django does make one adjustment to the Meta class of an abstract base class: before installing the Meta attribute, it sets abstract=False.
Asa result SomeModels will be abstract, but SomeDjangoModels will not.
No, those two definitions are not quite the same.
By default, a subclass will inherit its parent's Meta, but it will not inherit the abstract=True property (as the common use case is that subclasses will not be abstract).
If you do wish to inherit that, you must explicitly override the meta class, as shown in the documentation. (It appears from the question that you do wish SomeDjangoModels to also be abstract, but it's not entirely clear.)
If you do want a concrete (cf meta) subclass, then for all practical purposes the definitions are identical.
I'm buildibg some abstract model for about 10 models. I need to make, somehow, that 1 field is not declared in abstract model, but MUST be declared in inheriting models.
How to do that? Is there any way to use NotImplementedError?
I am afraid there isn't an easy way to achieve that, if possible at all, without digging deep into Django.
The main reason is that Field name "hiding" is not permitted in Django. What this means is that if you want to declare an abstract attribute in the base abstract class that is a Field instance, you will not be able to rewrite it in the child classes contrary to the normal Python class inheritance paradigm. To quote from the doc:
In normal Python class inheritance, it is permissible for a child class to override any attribute from the parent class. In Django, this is not permitted for attributes that are Field instances (at least, not at the moment). If a base class has a field called author, you cannot create another model field called author in any class that inherits from that base class.
Overriding fields in a parent model leads to difficulties in areas such as initializing new instances (specifying which field is being initialized in Model.init) and serialization. These are features which normal Python class inheritance doesn't have to deal with in quite the same way, so the difference between Django model inheritance and Python class inheritance isn't arbitrary.
This restriction only applies to attributes which are Field instances. Normal Python attributes can be overridden if you wish. It also only applies to the name of the attribute as Python sees it: if you are manually specifying the database column name, you can have the same column name appearing in both a child and an ancestor model for multi-table inheritance (they are columns in two different database tables).
Django will raise a FieldError if you override any model field in any ancestor model.
However, if the attribute is not a Field instance (very unlikely though), you will be able to achieve exactly what you want by using using #property decorator. Something like this should work:
class Person(models.Model):
def __init__(self, *args, **kwargs):
super(Person, self).__init__(*args, **kwargs)
self.last_name
first_name = models.CharField(max_length=30)
#property
def last_name(self):
raise NotImplementedError
class Meta:
abstract = True
class Student(Person):
home_group = models.CharField(max_length=5)
last_name = "Doe" # "models.CharField()" will not work!
class BadStudent(Person):
home_group = models.CharField(max_length=5)
# "NotImplmentedError" will be raised when instantiating BadStudent()
You may also want to take a look at abc.abstractproperty. I am not sure how it would work with Django's model inheritance though.
Why would you want to do it?? Which are the reasons the common field cannot be declared in the AbstractModel??
If you really want to do it, use the instructions here:
add methods in subclasses within the super class constructor