Calling a model function in django-admin - django

is there any method I can call a model function in admin.py for eg suppose I have a model say
class A(models.Model):
first = models.IntegerField()
second = models.IntegerField()
total = models.IntegerField()
def Total_amount(self):
self.total+=first
self.total+=second
Now I want that whenever I do something from admin side into first and second it automatically must reflect into total's value.How can I do that I mean I can do something in ModelAdmin class but its not working from my side.

When you save a model in the admin, it calls the full_clean method on your model's instance. I suggest you override the clean() method on that model to get the behavior you want, e.g.:
def clean(self):
super(MyModel, self).clean()
self.Total_amount()

Related

Accessing parent model instance within model admin to provide custom queryset

I want to provide a custom queryset within a model admin class that inherits from TabluarInline, but I want to provide this queryset by calling a method of current instance of the model object.
I have two models. One for tracks belonging to an album, and one for the Album itself. Some tracks can be hidden and I have a method in Album to return only the visible tracks.
class Track(models.Model):
name = models.CharField()
length = models.IntegerField()
album = ForeignKey(Album)
hidden = BooleanField()
class Album(models.Model):
name = models.CharField()
def get_visible_tracks_queryset(self):
return self.track_set.filter(hidden=False)
And I have a tracks inline admin which is included on the django admin page for an album. I want to re-use the get_visible_tracks_queryset to define the queryset for this inline admin, I don't want to repeat the logic again. I can't figure out how to do it. I could do something like the following, however I'm using a simplified example here, I actually have more complex logic and I don't want to be repeating the logic in multiple places.
class TracksInlineAdmin(admin.TabularInline):
fields = ("name", "length")
model = Track
def get_queryset(self, request):
qs = super(TracksInlineAdmin, self).get_queryset(request)
return qs.filter(hidden=False)
Ideally I could do something like:
class TracksInlineAdmin(admin.TabularInline):
fields = ("name", "length")
model = Track
def get_queryset(self, request, parent_model_instance):
return parent_model_instance.get_visible_tracks_queryset()
Any thoughts on how to achieve this?
The cleanest way is to define a custom QuerySet class for your model in which you can define any complex filters for re-use in various places:
class Track(models.Model):
# fields defined here
objects = TrackManager()
class TrackManager(models.Manager):
def get_queryset(self):
return TrackQuerySet(self.model, using=self._db)
class TrackQuerySet(models.QuerySet):
def visible(self):
return self.filter(hidden=False)
Now, anywhere in code, when you have a queryset of tracks (e.g. Track.objects.filter(name="my movie")) you can add .visible() to filter further. Also on a related set:
album.track_set.all().visible()

Best practice when updating Django model fields? Is this what manager classes are for?

I am very new to Django and I am wondering what the best practice is for updating a field. Here is my model:
class Website(models.Model):
id = models.AutoField(primary_key=True)
url = models.TextField()
is_awesome = models.BooleanField(default=False)
Right now I have a separate helper file, WebsiteHelper.py, with many other functions not related to the database in it, but also this function for updating a specific field in the DB:
def __mark_needs_redone(Website):
Website.update(is_awesome=True)
Is there a cleaner place for functions such as these to live, such as:
class WebsiteManager(models.Manager)
#Execute function here
Is this how managers are supposed to be used? If not, what is the best practice here?
If the field is on the model the form is handling, You can override the save() method you access to the actual instance.
class Website(models.Model):
id = models.AutoField(primary_key=True)
url = models.TextField()
is_awesome = models.BooleanField(default=False)
def save(self, commit=True):
self.instance.is_awesome = True
return super().save(commit)
Don't forget the super().save(commit) call after because the parent takes care of the saving logic
Your update() is calling the wrong way, you should call it to the model queryset, not the instance.
If You need to call Your method for every save(), check the pre_save signal, but if You don't, use Manager.
class WebsiteManager(models.Manager):
def mark_needs_redone(self, pk):
self.get(pk=pk).update(is_awesome = True)
To your model Website add the Manager:
class Website(models.Model):
id = models.AutoField(primary_key=True)
url = models.TextField()
is_awesome = models.BooleanField(default=False)
objects = WebsiteManager()
And the usage is:
Website.objects.mark_needs_redone(pk=1)
That code will mark is_awesome as True for Website with pk=1

Django create instances from class attributes

Suppose such a Topic Model Table
class Topic(models.Model):
"""A topic the user is learning about."""
text = models.CharField(max_length=200)
date_added = models.DateTimeField(auto_now_add=True)
owner = models.ForeignKey(User)
def __str__(self):
"""Return a string representation of the model."""
return self.text
As the structure indicates, text, data_added, owner are class level attribute.
However, Django is capable of creating instance from the class attributes
In [21]: Topic.objects.create(text='Celery', owner_id=1)
Out[21]: <Topic: Celery>
In [34]: celery = Topic.objects.get(pk=22)
In [35]: isinstance(celery, Topic)
Out[35]: True
I assume there should be initiating process def __init__()
How Django accomplish such an amazing task?
__init__ is defined on the Model class here in the Django source.
Since you derive from Model, you're getting that __init__ from the superclass.
When you call QuerySet.get(), it eventually (through some hoops within QuerySet) calls Model.from_db() which calls the __init__ method (line 496).
Also, to elaborate: Even if text, date_added and owner look like class attributes, they end up not being present on the final model subclass, since the ModelBase metaclass gathers them up into the new model subclass's _meta class (which is an instance of Options).

Overriding QuerySet.delete() in Django

I have a Django model that holds settings core to the function of an app. You should never delete this model. I'm trying to enforce this application-wide. I've disabled the delete function in the admin, and also disabled the delete method on the model, but QuerySet has it's own delete method. Example:
MyModel.objects.all()[0].delete() # Overridden, does nothing
MyModel.objects.all().delete() # POOF!
Ironically, the Django docs say has this to say about why delete() is a method on QuerySet and not Manager:
This is a safety mechanism to prevent you from accidentally requesting Entry.objects.delete(), and deleting all the entries.
How having to include .all() is a "safety mechanism" is questionable to say the least. Instead, this effectively creates a backdoor that can't be closed by conventional means (overriding the manager).
Anyone have a clue how to override this method on something as core as QuerySet without monkey-patching the source?
You can override a Manager's default QuerySet by overriding the Manager.get_query_set() method.
Example:
class MyQuerySet(models.query.QuerySet):
def delete(self):
pass # you can throw an exception
class NoDeleteManager(models.Manager):
def get_query_set(self):
return MyQuerySet(self.model, using=self._db)
class MyModel(models.Model)
field1 = ..
field2 = ..
objects = NoDeleteManager()
Now, MyModel.objects.all().delete() will do nothing.
For more informations: Modifying initial Manager QuerySets
mixin approach
https://gist.github.com/dnozay/373571d8a276e6b2af1a
use a similar recipe as #manji posted,
class DeactivateQuerySet(models.query.QuerySet):
'''
QuerySet whose delete() does not delete items, but instead marks the
rows as not active, and updates the timestamps
'''
def delete(self):
self.deactivate()
def deactivate(self):
deleted = now()
self.update(active=False, deleted=deleted)
def active(self):
return self.filter(active=True)
class DeactivateManager(models.Manager):
'''
Manager that returns a DeactivateQuerySet,
to prevent object deletion.
'''
def get_query_set(self):
return DeactivateQuerySet(self.model, using=self._db)
def active(self):
return self.get_query_set().active()
and create a mixin:
class DeactivateMixin(models.Model):
'''
abstract class for models whose rows should not be deleted but
items should be 'deactivated' instead.
note: needs to be the first abstract class for the default objects
manager to be replaced on the subclass.
'''
active = models.BooleanField(default=True, editable=False, db_index=True)
deleted = models.DateTimeField(default=None, editable=False, null=True)
objects = DeactivateManager()
class Meta:
abstract = True
other interesting stuff
http://datahackermd.com/2013/django-soft-deletion/
https://github.com/hearsaycorp/django-livefield

Django: models last mod date and mod count

I have a django model called Blog.
I'd like to add a field to my current model that is for last_modified_date. I know how to set a default value, but I would like somehow for it to get automatically updated anytime I modify the blog entry via the admin interface.
Is there some way to force this value to the current time on each admin site save?
Also would there be some way to add a mod_count field and have it automatically calculated on each modify of the admin site blog entry?
Create a DateTimeField in your model. Have it update whenever it is saved. This requires you to use the auto_now_add option:
class DateTimeField([auto_now=False, auto_now_add=False, **options])
DateTimeField.auto_now_add¶
Automatically set the field to now every time the object is saved. Useful
for "last-modified" timestamps. Note
that the current date is always used;
it's not just a default value that you
can override.
It should look something like this:
class Message(models.Model):
message = models.TextField()
active = models.BooleanField(default=True)
created_at = models.DateTimeField(auto_now_add=True)
Model field reference
For the second part, I think you have to overload
ModelAdmin.save_model(self, request, obj, form, change)
As James Bennett describes here. It will look something like this:
class EntryAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
if change:
obj.change_count += 1
obj.save()
The accepted answer is no longer correct.
For newer django versions, you will have to use the auto_now=True parameter rather than the auto_now_add=True, which will only set the field value when the object is initially created.
From the documentation:
DateField.auto_now_add¶
Automatically set the field to now when the
object is first created. Useful for creation of timestamps.
The desired functionality is now implemented by auto_now:
DateField.auto_now¶
Automatically set the field to now every time the
object is saved.
So to achieve self-updating timestamps a model should be created like this:
class Message(models.Model):
message = models.TextField()
active = models.BooleanField(default=True)
created_at = models.DateTimeField(auto_now=True)
mod_count = models.IntegerField(default=0)
To increment mod_count everytime this model is modified overload the model's save() method:
def save(self, *args, **kwargs):
self.mod_count +=1
return super(Message,self).save(*args, **kwargs)
There's a number of ways you can increase the edit count each time it's saved.
The model itself has a save() method, and the admin model has a model_save() method.
So for example, let's say you wanted it to increment when it was edited with the admin tool....
models.py:
class MyModel(models.Model):
edit_count = models.IntegerField()
# ... rest of model code here...
admin.py:
class MyModelAdmin(admin.ModelAdmin)
def save_model(self, request, obj, form, change):
if not change:
obj.edit_count = 1
else:
obj.edit_count += 1
obj.save()
You could do similar code off of the model save() event as well.
Something else you may be interested in is django-command-extensions. It adds 2 fields which may be helpful to you:
CreationDateTimeField - DateTimeField that will automatically set it's date when the object is first saved to the database.
ModificationDateTimeField - DateTimeField that will automatically set it's date when an object is saved to the database.
You can also use a middleware solution found here: https://bitbucket.org/amenasse/django-current-user/src/7c3c90c8f5e854fedcb04479d912c1b9f6f2a5b9/current_user?at=default
settings.py
....
MIDDLEWARE_CLASSES = (
....
'current_user.middleware.CurrentUserMiddleware',
'current_user.middleware.CreateUserMiddleware',
)
....
INSTALLED_APPS = (
'current_user',
....
....
)
models.py
class ExampleModel(models.Model):
foo = models.IntegerField()
last_user = CurrentUserField(related_name="+")
created_by = CreateUserField(related_name="+")