today a weird problem occurred to me:
I have a model class in Django and added a custom property to it that shall not be saved into the database and therefore is not representative in the model's structure:
class Category(models.Model):
groups = models.ManyToManyField(Group)
title = defaultdict()
Now, when I'm within the shell or writing a test and I do the following:
c1 = Category.objects.create()
c1.title['de'] = 'german title'
print c1.title['de'] # prints "german title"
c2 = Category.objects.create()
print c2.title['de'] # prints "german title" <-- WTF?
It seems that 'title' is kind of global. If I change the title to a simple string it works as expected, so it has to do something with the dict? I also tried setting title as a property:
title = property(_title)
But that did not work, too. So, how can I solve this? Thank you in advance!
EDIT:
Here is the intention of the base problem to provide you with a better look at the whole surrounding environment as requested:
In our model structure, we have a model class that stores translations. This class is unbound from all the other classes that have relations with each other. The translation class stores the translated value, a language key, a translation key and the package and class the translation belongs to. Some model classes can have properties that can be translated into different languages. These properties are not mapped within the Django model structure as this is not truly possible in our eyes. Each of these classes with translatable properties, let's call them translatable, can have one or more of these properties. That's what the translation key is for. E.g. if there is a class Category with a translatable property "title", the model translation will store "module.somewhere.Category" as package/class, "title" as translation key, and e.g. for german the translation value "Kategorie" and the language key "de".
My aim is to ease the access to these properties. So all these model classes inherit from a plain class called "Translatable". It has a method for resolving the module path and name of the class (for the later storing within the translation database table) and a "_propertize" method that takes the name of the property. Properties instantiate a class "Translator" that is unique for each translatable property name. This class does the resolving of the real translation value from the translation model class and some stuff for automatically resolving the translation of the currently chosen language.
Don't do it that way. Your title attribute is completely "global". It's part of the class, not part of each instance.
Do something like this.
class Category(models.Model):
groups = models.ManyToManyField(Group)
#property
def title(self):
return self._title
def save( self, *args, **kw ):
try:
self._title
except AttributeError:
self._title= defaultdict()
super( Category, self ).save( *args, **kw )
If you could define your actual use case, it might be possible to simplify this a great deal.
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'
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!
Before posting this question, I have read through the Official Django Documentation, scouring it for a comprehensive explanation for beginners. I have read the code of the actual Model Class, and searched around on StackOverflow.
When working with databases in Django, you work with classes inheriting from the Model class in the models module. This helps programmers avoid double-typing everything, jumping between database specific syntax and python. As I have read, 'the model class that each model inherits from automatically takes care of translation'.
How does this work? How Does the Model Class convert model attributes to database columns? I suppose some methods inherited from the parent Model Class are able to use the variables specified in each new model, but would like a better explanation if possible!
Also, why write 'models.Model' if the Model class is within models.base?
LINK TO MODEL CLASS: https://docs.djangoproject.com/en/1.11/_modules/django/db/models/base/#Model
EDIT:
Figured out the reason behind why models.Model work.
How Does the Model Class convert model attributes to database columns?
The Model class doesn't really do any conversion itself. You create a subclass of Model that has some column information,
which Django's ORM uses when building the database query corresponding to your Django ORM query. The conversion is done by a database driver when it actually communicates with your specific database.
Here's a toy ORM that behaves a little like Django's Model. You can implement QuerySet for fun if you want:
class Column:
'''
Represents a database column.
This is used to create the underlying table in the database
and to translate database types to Python types.
'''
def __init__(self, type):
self.type = type
class Manager:
'''
Accessed via `YourModel.objects`. This is what constructs
a `QuerySet` object in Django.
'''
def __init__(self, model):
self.model = model
def get(self, id):
'''
Pretend `YourModel.objects.get(id=123)` queries the database directly.
'''
# Create an instance of the model. We only keep track of the model class.
instance = self.model()
# Populate the instance's attributes with the result of the database query
for name in self.model._columns:
# Pretend we load the values from the database
value = 123
setattr(instance, name, value)
# This would be done above if we actually queried the database
instance.id = id
# Finally return the instance of `self.model`
return instance
class ModelBase(type):
def __new__(cls, name, bases, attrs):
new_cls = super().__new__(cls, name, bases, attrs)
# The `Manager` instance is made a class attribute
new_cls.objects = Manager(new_cls)
# Keep track of the columns for conveniece
new_cls._columns = {}
for name, attr in attrs.items():
if isinstance(attr, Column):
new_cls._columns[name] = attr
# The class is now ready
return new_cls
class Model(metaclass=ModelBase):
'''
Django's `Model` is more complex.
This one only uses `ModelBase` as its metaclass so you can just inherit from it
'''
pass
class MyModel(Model):
id = Column(int)
column2 = Column(float)
column3 = Column(str)
if __name__ == '__main__':
print(MyModel._columns)
instance = MyModel.objects.get(id=5)
print(instance.id)
The main functionality is provided by Model having ModelBase as a metaclass. The metaclass's __new__ method is called
when Model or any subclass is created (not an instance, the class itself), which allows the metaclass to modify the class arbitrarily.
Each Model subclass contains information about its own columns and gets a objects class attribute that queries the database for it.
Also, why write 'models.Model' if the Model class is within models.base?
models/__init__.py imports Model from models/base.py so you don't have to write models.base.Model.
When you create a model class and run
python manage.py makemigrations
It creates the corresponding scripts to create a table in your database.
You can find this script in your apps "migrations" folder.
And when you run
python manage.py migrate
These scripts are mapped to the correct commands and are executed on the database by Django.
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
As a Python programmer, I like my code to be reusable, I'm trying to avoid kind name conflicts in my code (where two different models share the same kind name).
Currently I just prepend some meaningful text to the model's class name, but this is awfully unpythonic.
Being able to explicitly set the model's kind will solve my problem, but I can't find out how to do this, does anyone know how?
Just override the kind() method of your class:
class MyModel(db.Model):
#classmethod
def kind(cls):
return 'prefix_%s' % super(MyModel, cls).kind()
You can define a custom baseclass that does this for you:
class ModuleModel(db.Model):
#classmethod
def kind(cls):
return '%s_%s' % (cls.__module__, super(ModuleModel, cls).kind())
Any class that extends ModuleModel will have the name of the module it's defined in prefixed to the kind name.