Monkey patching a Django form class? - python

Given a form class (somewhere deep in your giant Django app)..
class ContactForm(forms.Form):
name = ...
surname = ...
And considering you want to add another field to this form without extending or modifying the form class itself, why does not the following approach work?
ContactForm.another_field = forms.CharField(...)
(My first guess is that the metaclass hackery that Django uses applies only the first time the form class is constructed. If so, would there be a way to redeclare the class to overcome this?)

Some pertinent definitions occur in django/forms/forms.py. They are:
class BaseForm
class Form
class DeclarativeFieldsMetaclass
def get_declared_fields
get_declared_fields is called from DeclarativeFieldsMetaclass and constructs a list with the field instances sorted by their creation counter. It then prepends fields from the base classes to this list and returns the result as an OrderedDict instance with the field name serving as the keys. DeclarativeFieldsMetaclass then sticks this value in the attribute base_fields and calls to type to construct the class. It then passes the class to the media_property function in widgets.py and attaches the return value to the media attribute on the new class.
media_property returns a property method that reconstructs the media declarations on every access. My feeling is that it wont be relevant here but I could be wrong.
At any rate, if you are not declaring a Media attribute (and none of the base classes do) then it only returns a fresh Media instance with no arguments to the constructor and I think that monkeypatching a new field on should be as simple as manually inserting the field into base_fields.
ContactForm.another_field = forms.CharField(...)
ContactForm.base_fields['another_field'] = ContactForm.another_field
Each form instance then gets a deepcopy of base_fields that becomes form_instance.fields in the __init__ method of BaseForm. HTH.

Related

Override instance attribute

In views.py I have:
my_computer = Computer.objects.get(pk=some_value)
The computer object has a field called projects that's a ManyRelatedManager.
Calling
my_projects = my_computer.projects.all()
will set the value of my_projects to a list of three project objects.
What I'm trying to achive is to set the value of my_computer.projects to the above list of projects instead of the ManyRelatedManager.
I have tried:
my_computer.projects = my_projects
but that doesn't work, although it doesn't raise an error either. The value of my_computer.projects is still the ManyRelatedManager.
Manager objects implement __set__ - they behave as descriptors.
This means you cannot change the object by assigning it (as long as its attribute of another object - __set__ is only called in the context of __setattr__ on the parent object - parent regarding composition relationships, and not inheritance relationships).
You can assign any list-like (actually: iterable) value to a manager if such iterable value yields models of the expected type. However this means:
When you query my_computer.projects, you will get again a manager object, with the objects you assigned.
When you save the object my_computer, only the specified objects will belong to the relationship - previous object in the relationship will not be related anymore to the current object.
There are three scenarios you could have which led you to this issue:
You need to hold a volatile list - this data is not stored, in any way, but used temporarily. You have to create a normal attribute in the class:
class Computer(models.Model):
#normal database fields here
def __init__(self, *args, **kwargs):
super(Computer, self).__init__(*args, **kwargs)
#ENSURE this attribute name does not collide with any field
#I'm assuming the Many manager name is projects.
self.my_projects = []
You need another representation of the exact same relationship - in this way, you want a comfortable way to access the object, instead of calling a strange .all(), e.g. to do a [k.foo for k in mycomputer.my_projects]. You have to create a property like this:
class Computer(models.Model):
#Normal database fields here
#I'm assuming the Many manager name is projects.
#property
def my_projects(self):
#remember: my_projects is another name.
#it CANNOT collide, so I have another
#name - cannot use projects as name.
return list(self.projects.all())
#my_projects.setter
def my_projects(self, value):
#this only abstracts the name, to match
#the getter.
self.projects = value
You need another relationship (so it's not volatile data): Create ANOTHER relationship in your model, pointing to the same mode, using the same through if applicable, but using a different related_name= (you must explicitly set related_name for at least one of the multiple relationships to the same model, from the same model)
You can't do that. Your best bet is to simply use another attribute name.
my_computer.related_projects = list(my_computer.projects.all())

Django using self on __init__ when not saved in database

Is it possible to use self as a reference in the __init__ method when the object is not instantiated yet?
What I'm trying to do is :
class MyClass(models.Model)
__init__(self):
some_attributes = AnotherClass.objects.filter(foreignkey=self)
The thing is that as the instance of MyClass is not registered in db yet, I have an exception like "MyClass has not attribute id"
I tried to add
if self.pk:
but it doesn't work. Is there a method like
if self.is_saved_in_db():
#some code
or do I have to created this one ?
EDIT
To be more specific, I'll give an example. I have a generic class which I try to hydrate with attributes from another Model.
class MyClass(models.Model)
_init__(self):
self.hydrate()
def hydrate(self):
# Retrieving the related objects
attributes = Information.objects.filter(...)
for attr in attributes:
attribute_id = attr.name.lower().replace(" ","_")
setattr(self,attribute_id,attr)
By doing so, I can access to attributes with MyClass.my_attribute.
For a small example, if we replace MyClass by Recipe and Information with Ingredients I can do :
pasta_recipe.pasta
pasta_recipie.tomato
pasta_recipie.onions
It's a simple parsing from a foreign_key to an attribute
By writing it, I realise that it's a bit useless because I can directly use ForeignKey relationships. I think I'll do that but for my own culture, is it possible do the filter with self as attribute before database saving ?
Thanks!
This is a very strange thing to do. I strongly recommend you do not try to do it.
(That said, the self.pk check is the correct one: you need to provide more details than "it doesn't work".)

Django - create a new instance of a model

Answer
As Sergey pointed out, class Model(**kwargs) is invalid, and is a typo in Django documentation.
The "class" part comes from the markup they used when they wrote it.
So, what they actually meant in the Django documentation is:
Creating objects
To create a new instance of a model, just instantiate it like any
other Python class:
Model(**kwargs)
The keyword arguments are simply the names of the fields you’ve
defined on your model. Note that instantiating a model in no way
touches your database; for that, you need to save().
Original question
I found the following while reading the Django Docs about Model instances:
Creating objects
To create a new instance of a model, just instantiate it like any
other Python class:
class Model(**kwargs)
The keyword arguments are simply the names of the fields you’ve
defined on your model. Note that instantiating a model in no way
touches your database; for that, you need to save().
What is the difference between these two codes?
class Model(**kwargs)
new_model = Model(**kwargs)
I know the second one creates a new instance of the class Model, with kwargs.
Is the first one different from it? To me, it seems like it rather redefines the Model class.
class Model(**kwargs) is not a valid Python syntax, the latter would look like
class Model(SomeBaseClass):
pass
Judging by the formatting (the line looks like a subheading), this must be a mistake in the Django documentation.
If you look at the Sphinx source of the page, you'll see that the "class" thing is actually a part of Sphinx markup. What they meant is
To create a new instance of a model, just instantiate it like any
other Python class:
Model(**kwargs)
The keyword arguments are simply the names of the fields you've
defined on your model.
The first line defines a class.
The second line defines an instance of a class.

How does Django's ORM manage to fetch Foreign objects when they are accessed

Been trying to figure this out for a couple of hours now and have gotten nowhere.
class other(models.Model):
user = models.ForeignKey(User)
others = other.objects.all()
o = others[0]
At this point the ORM has not asked for the o.user object, but if I do ANYTHING that touches that object, it loads it from the database.
type(o.user)
will cause a load from the database.
What I want to understand is HOW they do this magic. What is the pythonic pixie dust that causes it to happen. Yes, I have looked at the source, I'm stumped.
Django uses a metaclass (django.db.models.base.ModelBase) to customize the creation of model classes. For each object defined as a class attribute on the model (user is the one we care about here), Django first looks to see if it defines a contribute_to_class method. If the method is defined, Django calls it, allowing the object to customize the model class as it's being created. If the object doesn't define contribute_to_class, it is simply assigned to the class using setattr.
Since ForeignKey is a Django model field, it defines contribute_to_class. When the ModelBase metaclass calls ForeignKey.contribute_to_class, the value assigned to ModelClass.user is an instance of django.db.models.fields.related.ReverseSingleRelatedObjectDescriptor.
ReverseSingleRelatedObjectDescriptor is an object that implements Python's descriptor protocol in order to customize what happens when an instance of the class is accessed as an attribute of another class. In this case, the descriptor is used to lazily load and return the related model instance from the database the first time it is accessed.
# make a user and an instance of our model
>>> user = User(username="example")
>>> my_instance = MyModel(user=user)
# user is a ReverseSingleRelatedObjectDescriptor
>>> MyModel.user
<django.db.models.fields.related.ReverseSingleRelatedObjectDescriptor object>
# user hasn't been loaded, yet
>>> my_instance._user_cache
AttributeError: 'MyModel' object has no attribute '_user_cache'
# ReverseSingleRelatedObjectDescriptor.__get__ loads the user
>>> my_instance.user
<User: example>
# now the user is cached and won't be looked up again
>>> my_instance._user_cache
<User: example>
The ReverseSingleRelatedObjectDescriptor.__get__ method is called every time the user attribute is accessed on the model instance, but it's smart enough to only look up the related object once and then return a cached version on subsequent calls.
This will not explain how exactly Django goes about it, but what you are seeing is Lazy Loading in action. Lazy Loading is a well known design pattern to defer the initialization of objects right up until the point they are needed. In your case until either of o = others[0] or type(o.user) is executed. This Wikipedia article may give you some insights into the process.
Properties can be used to implement this behaviour. Basically, your class definition will generate a class similar to the following:
class other(models.Model):
def _get_user(self):
## o.users being accessed
return User.objects.get(other_id=self.id)
def _set_user(self, v):
## ...
user = property(_get_user, _set_user)
The query on User will not be performed until you access the .user of an 'other' instance.

Exposing a "dumbed-down", read-only instance of a Model in GAE

Does anyone know a clever way, in Google App Engine, to return a wrapped Model instance that only exposes a few of the original properties, and does not allow saving the instance back to the datastore?
I'm not looking for ways of actually enforcing these rules, obviously it'll still be possible to change the instance by digging through its __dict__ etc. I just want a way to avoid accidental exposure/changing of data.
My initial thought was to do this (I want to do this for a public version of a User model):
class PublicUser(db.Model):
display_name = db.StringProperty()
#classmethod
def kind(cls):
return 'User'
def put(self):
raise SomeError()
Unfortunately, GAE maps the kind to a class early on, so if I do PublicUser.get_by_id(1) I will actually get a User instance back, not a PublicUser instance.
Also, the idea is that it should at least appear to be a Model instance so that I can pass it around to code that does not know about the fact that it is a "dumbed-down" version. Ultimately I want to do this so that I can use my generic data exposure functions on the read-only version, so that they only expose public information about the user.
Update
I went with icio's solution. Here's the code I wrote for copying the properties from the User instance over to a PublicUser instance:
class User(db.Model):
# ...
# code
# ...
def as_public(self):
"""Returns a PublicUser version of this object.
"""
props = self.properties()
pu = PublicUser()
for prop in pu.properties().values():
# Only copy properties that exist for both the PublicUser model and
# the User model.
if prop.name in props:
# This line of code sets the property of the PublicUser
# instance to the value of the same property on the User
# instance.
prop.__set__(pu, props[prop.name].__get__(self, type(self)))
return pu
Please comment if this isn't a good way of doing it.
Could you not create a method within your User class which instantiates a ReadOnlyUser object and copies the values of member variables over as appropriate? Your call would be something like User.get_by_id(1).readonly() with the readonly method defined in the following form:
class User(db.Model):
def readonly(self):
return ReadOnlyUser(self.name, self.id);
Or you could perhaps have your User class extend another class with methods to do this automatically based on some static vars listing properties to copy over, or something.
P.S. I don't code in Python

Categories

Resources