Where to check if something was changed on Django Admin - python

I have a Model on Django-rest-framework, and I need to check every time a field on that Model was updated in the Django-Admin in order to do a update in another model.
How and where can I check it?
Thanks

#ssice is right you can utilise Django Signals, along with something like django-dirtyfields.
Or
If it's a one time thing, you can roll your own dirty field checker for that model by overriding model's __init__() and save() methods. Something like this (of course it can be much more complex depending on your requirements):
def __init__(self, *args, **kwargs):
super(YOUR_MODEL, self).__init__(*args, **kwargs)
# SAVE THE INITIAL VALUE
self.__original_value = self.value_you_want_to_track
def save(self, *args, **kwargs):
# Compare the initial value with the current value
if self.__original_value != self.value_you_want_to_track:
# DO SOMETHING, MAYBE TRIGGER SIGNAL
super(YOUR_MODEL, self).save(*args, **kwargs)
# Finally update the initial value after the save complete
self.__original_value = self.value_you_want_to_track
CAUTION
These would NOT work if you use model update(), as it does not trigger django's save() or related signals. But you said you want to track the changes made from the admin site, so I'm assuming this is not a problem.

If you only need to watch changes in Django Admin change form, you can hook the save_model() method of your ModelAdmin.
class YourAdmin(ModelAdmin):
def save_model(self, request, obj, form, change):
super().save_model(request, obj, form, change)
# do what you have to do here
You may also want to enclose this in a transaction to ensure the model is not saved if the other operation failed.
class YourAdmin(ModelAdmin):
#transaction.atomic
def save_model(self, request, obj, form, change):
super().save_model(request, obj, form, change)
# do what you have to do here

Related

Test a Django model that uses request.user in save_model

I have a Django model that overrides save_model in order to automatically save the current authenticated user into a field on save.
The question is, how in a unit test can I test the creation of this model, without having to go through a view?
The model:
class SomeClass(models.Model):
def save_model(self, request, obj, form, change):
self.prevent_update()
if not obj.pk:
# Only set added_by during the first save.
obj.created = request.user
super().save_model(request, obj, form, change)
The test just wants to set up SomeClass before doing some other stuff.
class SomeClassTestCase(TestCase):
def setUp(self):
self.assertTrue(request.user)
SomeClass.objects.create(name="abc")
I know that there is a request getting set up automatically, since request.user doesn't fail with request being None; instead, the query constraint fails because the user is null. How do I set a user on the request that appears to have been passed into save_model
Alternatively, I know I can setup a request using RequestFactory and generate a user. In that case, how do I get that into the test so that the SomeClass.objects.create actually sees it.
Thanks to the comments, I realised that I had copied and pasted badly, placing save_model into a model. This and my amateurish abilities with Django caused all kinds of confusion. So the fact that request.user was not failing was because it was never called.
I am now correctly overriding the save method in the model, and the save_model method in the admin, which passes the correct details to the model for it to save.

Django Admin Form: Set the default value of a readonly field

THE GOAL: Display a request-specific, read-only user_id on a django admin form when creating and updating a resource.
This display should be a readonly field (readonly, NOT disabled). The user_id displayed is derived from the requesting user (request.user) so the initial value is set from that.
Following this post, we should simply set it in the get_form method like so:
def get_form(self, request, obj=None, *args, **kwargs):
form = super(ProfileAdmin, self).get_form(request, *args, **kwargs)
form.base_fields['user'].initial = request.user
return form
However, user is no longer on the base_fields when it is readonly. In fact, I can't find it anywhere. Any thoughts?
Other posts suggest on save, which I intend to do, but I need it to show on the form before then.
You are right that making the field readonly excludes it from the base_fields.As mentioned in the docs :
Any fields in this option (which should be a list or tuple) will display its data as-is and non-editable; they are also excluded from the ModelForm used for creating and editing.
You could have define a method for it just like list_display but since you need to set user from the request, we need to have the request object, which isn't available in the method.
So for your use case, we can use formfield_for_foreignkey method as :
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == 'lend_by':
# setting the user from the request object
kwargs['initial'] = request.user.id
# making the field readonly
kwargs['disabled'] = True
return super().formfield_for_foreignkey(db_field, request, **kwargs)
Hope it helps.

How to save related model instances before the model instance in django?

How to save the related model instances before the instance model.
This is necessary because I want to preprocess the related model's instance field under model instance save method.
I am working on Django project, and I am in a situation, that I need to run some function, after all the related models of instance get saved in the database.
Let say I have a model
models.py
from . import signals
class Video(models.Model):
"""Video model"""
title = models.CharField(
max_length=255,
)
keywords = models.ManyToManyField(
KeyWord,
verbose_name=_("Keywords")
)
When the new instance of video model is created.
I need to
1. All the related models get saved first.
a. If the related models are empty return empty or None
2. and then Save this video instance.
I tried to do it using post_save signals, but couldn't succeed as there is no guarantee that related models get saved first that the model.
from django.db.models.signals import post_save, pre_delete, m2m_changed
from django.dispatch import receiver
from .models import Video
#receiver(m2m_changed, sender=Video)
#receiver(post_save, sender=Video)
def index_or_update_video(sender, instance, **kwargs):
"""Update or create an instance to search server."""
# TODO: use logging system
# Grab the id
print("Id is", instance.id)
# Keywords is empty as keyword instance is saved later than this instace.
keywords = [keyword.keyword for keyword in instance.keywords.all()]
print(keywords) # [] empty no keywords
instance.index()
#receiver(pre_delete, sender=Video)
def delete_video(sender, instance, **kwargs):
print("Delete index object")
instance.delete()
Update:
Can be implemented by grabbing the post_save signals and wait unitls
its related models get saved in db, when the related_models get saved
start serialization process and create flat json file along with the models fields and its related instance so, the flat json file can index
into elastic search server.
And the question aries, how much time should we wait in signal handler method? and how to know all instance related fields got saved in db.
class Video(models.Model):
def save(self, *args, **kwargs):
# 1. Make sure all of its related items are saved in db
# 2. Now save this instance in db.
# 3. If the model has been saved. Serialize its value,
# 4. Serailize its related models fields
# 5. Save all the serialized data into index server
# The advantage of using this is the data are indexed in real
# time to index server.
# I tired to to implement this logic using signals, in case of
# signals, when the instance get saved, its related models are
# not instantly available in the databse.
# Other solution could be, grab the `post_save` signals, wait(delay
# the serialization process) and start the serialization of
# instance model and it's related to convert the data to flat json
# file so, that it could index in the searching server(ES) in real
# time.
# until the instance related models get saved and start to
# serialize the data when its
By the way I am using django-admin and I am not defining the logics in
a view, adding the related model instance is handled by django admin
In this case, you can flip the order with which ModelAdmin calls save_model() and save_related() so from Model.save() you will be able to reach the updated values of the related fields, as stated in this post.
class Video(models.Model):
def save(self, *args, **kwargs):
if not self.id:
super().save(*args, **kwargs)
all_updated_keywards = self.keywards.all()
...
super().save(*args, **kwargs)
class VideoAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
if not obj.pk:
super().save_model(request, obj, form, change)
else:
pass
def save_related(self, request, form, formsets, change):
form.save_m2m()
for formset in formsets:
self.save_formset(request, form, formset, change=change)
super().save_model(request, form.instance, form, change)
You can override model's save() method and save related models (objects) before saving instance.

Django validate what user sends from admin panel

I am kind of new to Django, and i am trying to make sort of a news website where users can submit articles(with an account) but the admin needs to check them before they can be posted. Is that possible?
Yes, it is.
The simplest approach would be creating simple flag in model let's say a Boolean field named verified, which by default would be False. You could add permissions. So in the end you could overwrite a function in your admin form, and show the field for superuser only.
class MyUserAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
self.exclude = []
if not request.user.is_superuser:
self.exclude.append('Permissions') #here!
return super(MyUserAdmin, self).get_form(request, obj, **kwargs)

How to implement a script while saving a file in django admin panel?

I'm new to django.
I want to run a script(for ex. zipping a file) after it gets uploaded to a server through "admin panel",i.e when user hits Save "in" from admin panel,it should get zipped(or some other manipulation that i may want to implement) after it gets uploaded.
Or can you just tell me which function is called when user hits the save button.
Signals might work, but it seems like the OP wants to do something only when an object is created or changed from the admin panel.
I think the best way to do this is to use the ModelAdmin method save_model().
From the Django docs:
ModelAdmin.save_model(self, request, obj, form, change)
You can overwrite this method in your definition of an admin class, as follows:
class SomeObjectAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
# do any pre-save stuff here
obj.save()
The change arg is a Boolean value that is True if the object is being changed, and false if the object is being created for the first time. So if you want to execute some function only on object creation:
def save_model(self, request, obj, form, change):
if not change:
# do your compression here
# do any other pre-save stuff here
obj.save()
# do any post-save stuff here
You may use signals : https://docs.djangoproject.com/en/dev/topics/signals/
to detect the save action.

Categories

Resources