I’m quite new to Django and I’m trying to implement polymorphism inside a Django model, but I can’t see how to do. Before going on I have to say I’ve already tried django-model-utils and django-polymorphism, but they don’t do exactly what I’m looking for.
I have a model called Player, each player has a Role and each Role has different behaviours (i.e. their methods return different values):
class Player(models.Model):
username=models.TextField()
role=models.ForeignKey(Role) #Role is another model with a field called ’name'
def allow_action(self)
#some stuff
class RoleA():
def allow_action(self):
#some specific stuff
class RoleB():
pass
I want that every time I retrieve any instance of Player (in example through Player.objects.filter(…)) every instances has the allow_action() method overwritten by the custom one defined inside the specific class (RoleA, RoleB, etc…) or use the default method provided in Player if the related subclass has no method called with the same name (RoleA, RoleB, etc... are the same role name stored in Player.role.name).
CONSTRAINTS:
Since subclasses (RolaA, RoleB, etc…) do not add new field but only overwrite methods all data have to be stored inside Player’s table, so I don’t want to use Django Multi-Table Inheritance but something more similar to Proxies.
I don’t want to perform additional JOIN to determine specific subclass type since all informations needed are stored inside Player’s table.
I think that this is a standard polymorphism pattern but I don’t see how to implement it in Django using the same table for all players (I've already implemented this polymorphism but not linked to a Django model). I’ve seen Django has a kind of inheritance called “Proxy” but it doesn’t allow to make queries like Player.objects.filter(…) and get instances with method overwritten by custom ones (or at least this is what I understood).
Thanks in advance.
Disclaimer: I've not used django-polymorphic, and this code is based on 5 minutes spent scanning the docs and is entirely untested but I'll interested to see if it works:
from polymorphic import PolymorphicModel
class Role(PolymorphicModel):
name = models.CharField()
class RoleA(Role):
def allow_action(self):
# Some specific stuff...
class RoleB(Role):
pass
class Player(models.Model):
username=models.TextField()
role=models.ForeignKey(Role) #Role is another model with a field called ’name'
def allow_action(self)
if callable(getattr(self.role, "allow_action", None):
self.role.allow_action()
else:
# default action...
Now I believe you should be able to create an instance of Role, RoleA, or RoleB and have Player point to it in the foreign key. Calling allow_action() on an instance of Player will check to see if the instance of Role (or RoleA, RoleB etc) has a callable attribute allow_action() and if so, it will use that, otherwise it will use the default.
Related
Can we say that Django models are considered dataclasses? I don't see #dataclass annotation on them or on their base class model.Models. However, we do treat them like dataclasses because they don't have constructors and we can create new objects by naming their arguments, for example MyDjangoModel(arg1= ..., arg2=...).
On the other hand, Django models also don't have init methods (constructors) or inherit from NamedTuple class.
What happens under the hood that I create new Django model objects?
A lot of the magic that happens with models, if not nearly all of it, is from its base meta class.
This can be found in django.db.models.ModelBase specifically in the __new__ function.
Regardless of an __init__ method being defined or not (which actually, it is as per Abdul's comment), doesn't mean it can or should be considered a dataclass.
As described very eloquently in this SO post by someone else;
What are data classes and how are they different from common classes?
Despite django models quite clearly and apparently seeming to have some kind of data stored in them, the models are more like an easy to use (and reuse) set of functions which leverage a database backend, which is where the real state of an object is stored, the model just gives access to it.
It's also worth noting that models don't store data, but simply retrieves it.
Take for example this simple model:
class Person(models.Model):
name = models.CharField()
And then we did something like this in a shell:
person = Person.objects.get(...)
print(person.name)
When we access the attribute, django is actually asking the database for the information and this generates a query to get the value.
The value isn't ACTUALLY stored on the model object itself.
With that in mind, inherently, django models ARE NOT dataclasses. They are plain old regular classes.
Django does not work with data classes. You can define a custom model field. But likely this will take some development work.
My implementation of Flask-Login is working well across my site, with the exception of contexts where I need to make additional queries on the User class. For example, when I try to generate a list of User instances representing other users with whom the current_user shares a task, the values stored in current_user are rewritten each time a new instance of User is created. In addition, the attributes of each of the other previously-generated User instances are also overwritten, with the exception of the User.id attribute, which remains unique (and correct).
I am using pyAirtable as an ORM between Flask and an Airtable base containing user data. The ORM is experimental and built using abstract base classes. I wonder whether this issue may be due to a conflict between instance and class variables.
I specifically suspect the _fields variable may be the culprit, because it appears as both a class variable and an instance variable in the source for the base Model. (The issue may also be with the from_id() method, which I use in a list comprehension to generate the aforementioned list of User instances from a list of Airtable record IDs.) However, I'm not experienced enough with ABCs or Python to be able to determine this for sure. I've tried fiddling with the pyairtable package for debugging, to no avail.
What am I missing? Is this an issue with pyairtable, flask-login, an unexpected interaction between the two packages, or just my implementation?
I wrote a quest system for an online game. My quests are serialized into json objects for a JavaScript client that fetches those quests then from a REST backend (I use django RestFramework)
Now I'm wondering on which class or django model I should put the "behaviour" that belongs to the data.
I stored the data that belongs to a quest in several separate models:
A model QuestHistory: with models.Fields like Boolean completed, and Datetime started where I put the information belonging to a specific user (it also as a field user).
Then I have a model QuestTemplate : The part that is always the same, fields like quest_title and quest_description
I also have a model Rewards and model Task and TaskHistory that are linked to a quest with a foreign Key field.
To combine this information back to quest I created a pure python class Quest(object): and defined methods on this class like check_quest_completion. This class is the then later serialized. The Problem with this approach is that It becomes quite verbose, for example when I instantiate this class or when I define the Serializer.
Is there a python or django "shortcut" to put all fields of a django model into another class (my Quest class here), something similar to the dict.update method maybe?
Or should I try to put the methods on the models instead and get rid of the Quest class?
I have some other places in my game that look very similar to the quest system for example the inventory system so I'm hoping for a more elegant solution.
You should put the methods of the Quest class on the model itself and get rid of the Quest class.
Suppose you have to model several classes that should have composite properties like dimensions (width and height) or phone number (prefix, number and extension).
In Java (using JPA 2) I'd create a Dimensions class and annotate it with #Embeddable. This causes Dimension's fields (e.g. width and height) to be embedded into every class that declares a property of type Dimensions.
How do you model these with Django while avoiding code duplication? It doesn't make sense to create a separate Dimensions model and reference it with a ForeignKey field. And the classes do not have enough in common to justify model inheritance.
I think you might be over-thinking inheritance. Inheritance is and is actually the recommended method to composite models. The following is an example of how properly use model inheritance in Django:
class PhoneModelBase(model.Model):
phone = models.CharField(max_length=16)
...
class Meta:
abstract = True
class PhoneModel(PhoneModelBase):
# phone is here without typing it
# the only restriction is that you cannot redefine phone here
# its a Django restriction, not Python restriction
# phone = models.CharField(max_length=12) # <= This will raise exception
pass
So what this does is it creates a model PhoneModelBase, however the key here is that it uses class Meta with abstract=True.
Here is more of behind the scenes of what is going on and some explanation of some of the Python concepts. I assume that you are not aware of them since you mentioned Java in the question. These Python concepts are actually rather confusing concepts so my explanation is probably not full, or even confusing, so if you will not follow, don't mind. All you have to know is to use abstact = True. Here is the official doc: https://docs.djangoproject.com/en/dev/topics/db/models/#abstract-base-classes.
Meta attribute within PhoneModelBase is just that, an attribute. It is the same as any other attribute within the class, except its a class instance (remember that in Python classes and functions are first order members). In addition, Python has this thing called __metaclass__ which you can add to you classes. __metaclass__ defines a way of how an instance of a class is build. More about that here. Django uses these in how it creates model class instances.
So to create the PhoneModelBase class, the following is a rough outline:
When an instance of the PhoneModelBase class (the class itself, not instance of class - PhoneModelBase()) is being created, the __metaclass__ which comes from model.Model due to inheritance takes over the creation process
Within the __metaclass__, Python calls the function which creates the actual class instance and passes to it all of the fields from the class you are trying to create - PhoneModelBase. That will include phone, Meta and any other fields you define
It sees the Meta attribute and then it starts analyzing its attributes. According to the values of these attributes, Django will change the behavior of the model
It sees the abstract attribute and then changes the logic of the class its trying to create - PhoneModelBase by not storing it in db
So then the PhoneModelBase, even though its definition looks very similar to a regular model, its not a regular model. It is just an abstract class which is meant to be used as composite in other models.
When other models inherit from PhoneModelBase, their __metaclass__ will copy the attributes from the base model as if you manually typed those attributes. It will not be a foreign key on anything like that. All of the inherited attributes are going to be a part of the model and will be in the same table.
Hopefully all of this makes some sense. If not, all you have to remember is just to use Meta class with abstract = True.
EDIT
As suggested in the comment, you can also inherit from multiple base classes. So you can have PhoneModelBase, DimensionsModelBase, and then you can inherit from both of these (or more), and all of the attributes from all base classes will be present in your model.
Maybe my question is little childish. A django model is typically defined like this:
class DummyModel(models.Model):
field1 = models.CharField()
field2 = models.CharField()
As per my understanding, field1 and field2 are defined on the class level instead of instance level. So different instances will share the same field value. How can this be possible considering a web application should be thread safe? Am I missing something in my python learning curve?
You are correct that normally attributes declared at the class level will be shared between instances. However, Django uses some clever code involving metaclasses to allow each instance to have different values. If you're interested in how this is possible, Marty Alchin's book Pro Django has a good explanation - or you could just read the code.
Think of the models you define as specifications. You specify the fields that you want, and when Django hands you back an instance, it has used your specifications to build you an entirely different object that looks the same.
For instance,
field1 = models.CharField()
When you assign a value to field1, such as 'I am a field', don't you think it's strange that you can assign a string to a field that is supposed to be a 'CharField'? But when you save that instance, everything still works?
Django looks at the CharField, says "this should be a string", and hands it off to you. When you save it, Django checks the value against the specification you've given, and saves it if it's valid.
This is a very simplistic view of course, but it should highlight the difference between defining a model, and the actual instance you get to work with.