I create base model and inherit that in all of my models. This is my BaseModel:
class BaseModel(models.Model):
create_date = models.DateTimeField(auto_now_add=True)
update_date = models.DateTimeField(auto_now=True)
created_by = models.ForeignKey('UserManager.User', default=1, on_delete=models.SET_DEFAULT,related_name='created_%(class)ss')
updated_by = models.ForeignKey('UserManager.User', default=1, on_delete=models.SET_DEFAULT,related_name='updated_%(class)ss')
class Meta:
abstract = True
ordering = ['create_date']
def save(self, *args, **kwargs):
self.user = kwargs.pop('user', None)
if self.user:
if self.user.pk is None:
self.created_by = self.user
self.updated_by = self.user
super(BaseModel, self).save(*args, **kwargs)
Now, I want to add some operations to save method of one of child models like this:
class Child(BaseModel):
# Some fields go here.
def save(self, *args, **kwargs):
# Some operations must run here.
But save method of child model does n't run anymore!
How can I use save method of child model with save method of abastract=True model?
If you inherit ChildModel from BaseModel, when you get to the save method in BaseModel 'self.class' is still ChildModel. So it finds the super of Child, which is BaseModel, so calls the save in BaseModel.
So just call ,
super(ChildModel, self).save(*args, **kwargs)
I have rather simple problem i guess. But i cant find a solution. It's been a while since i was writing in python/django...
My simple problem is, when im trying to add new Plain by admin interface.
TypeError: int() argument must be a string or a number, not 'Plain'
Site with form is rendering correctly, everything is fine till adding...
This is code of the models:
class Locomotion(models.Model):
transportation_firm_name = models.CharField(max_length=200)
transportation_number = models.CharField(max_length=200)
departure_date_time = models.DateTimeField()
arrival_date_time = models.DateTimeField()
class Meta:
abstract = True
def __str__(self):
return self.transportation_name
class Plain(Locomotion):
seat_number = models.CharField(max_length=200)
class_section = models.CharField(max_length=200)
def __init__(self, *args, **kwargs):
super(Locomotion, self).__init__(self, *args, **kwargs)
def __str__(self):
return "plain"
class Train(Locomotion):
seat_number = models.CharField(max_length=200)
section_numbers = models.CharField(max_length=200)
def __init__(self, *args, **kwargs):
super(Locomotion, self).__init__(self, *args, **kwargs)
And the same is happening when im trying to add Train or any other element of class extending Locomotion.
When you call super, you don't need to pass self:
super(Plain, self).__init__(*args, **kwargs)
Also, note that usually, you want to call super passing the class that you are calling it from, Plain in this case.
How does Django translate this <bound method Child.parent_identity of <Child: >> object in a string object, and displays it as such in my django-admin "inline" Child class idparent field ?
What does Django do ?
I have the following application structure:
##========================models.py
...
from django_extensions.db.fields import UUIDField
class Parent(models.Model):
id = UUIDField(primary_key=True)
class Child(models.Model):
parent = models.ForeignKey(Parent)
idparent = models.CharField(max_length=100)
def parent_identity(self):
return self.parent_id
#========================admin.py
class ChildForm(forms.ModelForm):
class Meta:
model = Child
exclude = []
def __init__(self, *args, **kwargs):
super(ChildForm, self).__init__(*args, **kwargs)
#print self.instance.parent_identity
self.initial['idparent'] = self.instance.parent_identity
class ChildInline(admin.TabularInline):
model = Child
extra = 1
form = ChildForm
class ParentAdmin(admin.ModelAdmin):
exclude = []
inlines = [ChildInline]
#list_display, etc
admin.site.register(Parent,ParentAdmin)
My inline idparent field displays the Parent id field CORRECTLY in the admin inline interface. Being a newbie, it's magic for me, because self.instance.parent_identity is initially not a string object.
print self.instance.parent_identity
#it prints : <bound method Child.parent_identity of <Child: >>
But how to explictly print the string content as follows
>>print self.instance.parent_identity
#would print : fffeee29-7ac6-42eb-8a8d-eb212d2365ff
That is, how to get it so as to deal with it in the ChildForm class ?
UPDATE
I do not mind specifically about "UUID in the form when the instance hasn't been created yet"
and i do not want to provide an initial value myself.
I want my still empty (extra) Child fields (one field in my example code: idparent) to contain by default something which is Parent variable.
Is it possible ?
Django templates automatically call any object that is callable; e.g. the callable() function returns True when you pass the object in. From the Variables section in the template documentation:
If the resulting value is callable, it is called with no arguments. The result of the call becomes the template value.
Bound methods are callable, so instead of using self.instance.parent_identity, the template uses the output of self.instance.parent_identity().
In your own code, you generally already know that something is a method and you call it explicitly:
def __init__(self, *args, **kwargs):
super(ChildForm, self).__init__(*args, **kwargs)
self.initial['idparent'] = self.instance.parent_identity()
You can treat the parent_identity method as an attribute; have Python call it automatically without you having to call it explicitly. If you never have to pass in an argument, then that might make sense. You do this by decorating the method with the #property decorator:
class Child(models.Model):
parent = models.ForeignKey(Parent)
idparent = models.CharField(max_length=100)
#property
def parent_identity(self):
return self.parent_id
at which point self.instance.parent_identity will give you the return value of that method.
Take into account that the UUIDField only is given a value on pre-save; it'll be None until the object is saved in a database.
If you really wanted to UUID in the form when the instance hasn't been created yet, you'll have to provide an initial value yourself:
import uuid
class ParentAdmin(admin.ModelAdmin):
exclude = []
inlines = [ChildInline]
def __init__(self, *args, **kwargs):
super(ParentAdmin, self).__init__(*args, **kwargs)
self.fields['id'].initial = uuid.uuid4
You are calling a function, which means you need to use it as such:
self.initial['idparent'] = self.instance.parent_identity()
Alternately you could wrap it with the #property decorator and continue using it as you are, notice that you need to use self.parent.id if you want to access the parent's id:
class Child(models.Model):
parent = models.ForeignKey(Parent)
idparent = models.CharField(max_length=100)
#property
def parent_identity(self):
return self.parent.id
I'm working with instances of database models where I need to construct the object from data in-memory (using Python-style o = Object() as opposed to ModelClass.objects.create(). Whether or not the data will be saved in the database is to be decided later, when a call to o.save() would be used.
These models have a ManyToManyField and own a number of child objects. Problem is, I can't add() to the ManyToManyField until the child objects are actually saved. How can I construct these objects in such a way that save() can be called later? Every potential solution I've found to this problem does not actually do what I want.
Here's some example code showing what I'm trying to do:
class Author:
# ...
#classmethod
def create(cls, data):
# ...
pass
class Book(models.Model):
title = models.CharField(max_length=128)
pages = models.PositiveIntegerField()
authors = models.ManyToManyField(Author)
#classmethod
#transaction.atomic
def create(cls, data):
try:
with transaction.atomic():
b = cls(title=data["title"],
pages=data["pages"])
# This works, but has an unwanted side effect: authors are saved to the database
# as they're created here while the Book is not saved.
b.authors = Author.objects.bulk_create([Author.create(a) for a in data["authors"]])
return b
except Exception:
# ...
raise
################### Later on...
# This data is NOT static - it's formed from JSON which comes from an API. Just is here as an example.
data = {
"title": 1,
"pages": 934,
"authors": [
{
"name": "John Smith",
# ...
}
]
}
# We're going to use this now, but we're unsure if we want to actually save
# the object to the database.
b = Book.create(data)
# Save the data to the database if we want to.
b.save()
The only solution I can think off is queuing the operations and perform them when you call save().
class PostponedOpMixin(models.Model):
def __init__(self, *args, **kwargs):
self._postponed_ops = []
super(PostponedOpMixin, self).__init__(*args, **kwargs)
def _getattr(self, attr):
result = self
for part in attr.split('.'):
result = getattr(result, part)
return result
def postpone(self, op, *args, **kwargs):
if self.pk: # execute now if self already has a pk
return self._getattr(op)(*args, **kwargs)
self._postponed_ops.append((op, *args, **kwargs))
def save(self, *args, *kwargs):
super(PostponedOpMixin, self).save(*args, **kwargs)
while self._postponed_ops:
op, args, kwargs = self._postponed_ops.pop(0):
self._getattr(op)(*args, **kwargs)
def Meta:
abstract = True
This way you can do:
class Book(PostponedOpMixin):
...
authors = models.ManyToManyField(Author)
...
instance = Book()
instance.title = "Romeo and Juliet"
instance.postpone('authors.add', shakespeare)
...
# and some time later:
instance.save()
This code is untested and intended as a start point. Any bug is left as an exercise for the reader.
Maybe you could postpone the actual book's authors adding to the moment you are sure you want to save that book into the database.
In the meantime, you could store a list of processed (but not yet saved) author objects for each book object.
class Book(models.Model):
title = models.CharField(max_length=128)
pages = models.PositiveIntegerField()
authors = models.ManyToManyField(Author)
#classmethod
#transaction.atomic
def create(cls, data):
try:
b = cls(title=data["title"],
pages=data["pages"])
b.author_list = list()
for a in data["authors"]:
b.authors_list.append(Author.create(a))
return b
except Exception:
# ...
raise
Then, when you are sure you want to save the object, you have to save all the authors in that list, and then add them to the 'authors' field in the corresponding book object.
How do I have actions occur when a field gets changed in one of my models? In this particular case, I have this model:
class Game(models.Model):
STATE_CHOICES = (
('S', 'Setup'),
('A', 'Active'),
('P', 'Paused'),
('F', 'Finished')
)
name = models.CharField(max_length=100)
owner = models.ForeignKey(User)
created = models.DateTimeField(auto_now_add=True)
started = models.DateTimeField(null=True)
state = models.CharField(max_length=1, choices=STATE_CHOICES, default='S')
and I would like to have Units created, and the 'started' field populated with the current datetime (among other things), when the state goes from Setup to Active.
I suspect that a model instance method is needed, but the docs don't seem to have much to say about using them in this manner.
Update: I've added the following to my Game class:
def __init__(self, *args, **kwargs):
super(Game, self).__init__(*args, **kwargs)
self.old_state = self.state
def save(self, force_insert=False, force_update=False):
if self.old_state == 'S' and self.state == 'A':
self.started = datetime.datetime.now()
super(Game, self).save(force_insert, force_update)
self.old_state = self.state
It has been answered, but here's an example of using signals, post_init and post_save.
from django.db.models.signals import post_save, post_init
class MyModel(models.Model):
state = models.IntegerField()
previous_state = None
#staticmethod
def post_save(sender, instance, created, **kwargs):
if instance.previous_state != instance.state or created:
do_something_with_state_change()
#staticmethod
def remember_state(sender, instance, **kwargs):
instance.previous_state = instance.state
post_save.connect(MyModel.post_save, sender=MyModel)
post_init.connect(MyModel.remember_state, sender=MyModel)
Basically, you need to override the save method, check if the state field was changed, set started if needed and then let the model base class finish persisting to the database.
The tricky part is figuring out if the field was changed. Check out the mixins and other solutions in this question to help you out with this:
Dirty fields in django
Django has a nifty feature called signals, which are effectively triggers that are set off at specific times:
Before/after a model's save method is called
Before/after a model's delete method is called
Before/after an HTTP request is made
Read the docs for full info, but all you need to do is create a receiver function and register it as a signal. This is usually done in models.py.
from django.core.signals import request_finished
def my_callback(sender, **kwargs):
print "Request finished!"
request_finished.connect(my_callback)
Simple, eh?
One way is to add a setter for the state. It's just a normal method, nothing special.
class Game(models.Model):
# ... other code
def set_state(self, newstate):
if self.state != newstate:
oldstate = self.state
self.state = newstate
if oldstate == 'S' and newstate == 'A':
self.started = datetime.now()
# create units, etc.
Update: If you want this to be triggered whenever a change is made to a model instance, you can (instead of set_state above) use a __setattr__ method in Game which is something like this:
def __setattr__(self, name, value):
if name != "state":
object.__setattr__(self, name, value)
else:
if self.state != value:
oldstate = self.state
object.__setattr__(self, name, value) # use base class setter
if oldstate == 'S' and value == 'A':
self.started = datetime.now()
# create units, etc.
Note that you wouldn't especially find this in the Django docs, as it (__setattr__) is a standard Python feature, documented here, and is not Django-specific.
note: Don't know about versions of django older than 1.2, but this code using __setattr__ won't work, it'll fail just after the second if, when trying to access self.state.
I tried something similar, and I tried to fix this problem by forcing the initialization of state (first in __init__ then ) in __new__ but this will lead to nasty unexpected behaviour.
I'm editing instead of commenting for obvious reasons, also: I'm not deleting this piece of code since maybe it could work with older (or future?) versions of django, and there may be another workaround to the self.state problem that i'm unaware of
#dcramer came up with a more elegant solution (in my opinion) for this issue.
https://gist.github.com/730765
from django.db.models.signals import post_init
def track_data(*fields):
"""
Tracks property changes on a model instance.
The changed list of properties is refreshed on model initialization
and save.
>>> #track_data('name')
>>> class Post(models.Model):
>>> name = models.CharField(...)
>>>
>>> #classmethod
>>> def post_save(cls, sender, instance, created, **kwargs):
>>> if instance.has_changed('name'):
>>> print "Hooray!"
"""
UNSAVED = dict()
def _store(self):
"Updates a local copy of attributes values"
if self.id:
self.__data = dict((f, getattr(self, f)) for f in fields)
else:
self.__data = UNSAVED
def inner(cls):
# contains a local copy of the previous values of attributes
cls.__data = {}
def has_changed(self, field):
"Returns ``True`` if ``field`` has changed since initialization."
if self.__data is UNSAVED:
return False
return self.__data.get(field) != getattr(self, field)
cls.has_changed = has_changed
def old_value(self, field):
"Returns the previous value of ``field``"
return self.__data.get(field)
cls.old_value = old_value
def whats_changed(self):
"Returns a list of changed attributes."
changed = {}
if self.__data is UNSAVED:
return changed
for k, v in self.__data.iteritems():
if v != getattr(self, k):
changed[k] = v
return changed
cls.whats_changed = whats_changed
# Ensure we are updating local attributes on model init
def _post_init(sender, instance, **kwargs):
_store(instance)
post_init.connect(_post_init, sender=cls, weak=False)
# Ensure we are updating local attributes on model save
def save(self, *args, **kwargs):
save._original(self, *args, **kwargs)
_store(self)
save._original = cls.save
cls.save = save
return cls
return inner
My solution is to put the following code to app's __init__.py:
from django.db.models import signals
from django.dispatch import receiver
#receiver(signals.pre_save)
def models_pre_save(sender, instance, **_):
if not sender.__module__.startswith('myproj.myapp.models'):
# ignore models of other apps
return
if instance.pk:
old = sender.objects.get(pk=instance.pk)
fields = sender._meta.local_fields
for field in fields:
try:
func = getattr(sender, field.name + '_changed', None) # class function or static function
if func and callable(func) and getattr(old, field.name, None) != getattr(instance, field.name, None):
# field has changed
func(old, instance)
except:
pass
and add <field_name>_changed static method to my model class:
class Product(models.Model):
sold = models.BooleanField(default=False, verbose_name=_('Product|sold'))
sold_dt = models.DateTimeField(null=True, blank=True, verbose_name=_('Product|sold datetime'))
#staticmethod
def sold_changed(old_obj, new_obj):
if new_obj.sold is True:
new_obj.sold_dt = timezone.now()
else:
new_obj.sold_dt = None
then the sold_dt field will change when sold field changes.
Any changes of any field defined in the model will trigger the <field_name>_changed method, with old and new object as parameters.
Using Dirty to detect changes and over-writing save method
dirty field
My prev ans: Actions triggered by field change in Django
class Game(DirtyFieldsMixin, models.Model):
STATE_CHOICES = (
('S', 'Setup'),
('A', 'Active'),
('P', 'Paused'),
('F', 'Finished')
)
state = models.CharField(max_length=1, choices=STATE_CHOICES, default='S')
def save(self, *args, **kwargs):
if self.is_dirty():
dirty_fields = self.get_dirty_fields()
if 'state' in dirty_fields:
Do_some_action()
super().save(*args, **kwargs)
If you use PostgreSQL you can create a trigger:
https://www.postgresql.org/docs/current/sql-createtrigger.html
Example:
CREATE TRIGGER check_update
BEFORE UPDATE ON accounts
FOR EACH ROW
WHEN (OLD.balance IS DISTINCT FROM NEW.balance)
EXECUTE FUNCTION check_account_update();