I am experimenting with logging in Django. I would like to log each occasion of users saving or modifying data. In my signal handler code, I have something like this:
#receiver(post_save)
def post_save_callback(sender, instance, created, raw, using, update_fields, **kwargs):
logger.info(
'Post-save called for %s. Created? %r. update_fields: %s',
sender, created, json.dumps(update_fields)
)
I would also like to log the username of the user performing the data manipulation, basically—the currently logged in user. How can I get this username?
Can't be done. The current user is only available via the request which is not available when using post_save.
To achieve what you are looking for you can do one of these:
you can override the save() method on that model.
You can use a middleware to store the current user(see this snippet wich can add user created_by and modified_by foreign key refs to any model automatically)
As described in documentation here signals are useful when many pieces of code may be interested in the same events.
Is it that case?
If the data manipulation is performed in admin panel you can consider to override log_change method of ModelAdmin object and you can use request.user.username to get the username of current logged in user.
def log_change(self, request, object, message):
logger.info('User %s changed user data %s. %s' %
(request.user.username, object, message)
super(MyClass, self).log_change(request, object, message)
Keep in mind that ModelAdmin already log changes in admin log, documentation here
Related
In Django admin, when you are looking at a record there is a button called "history". When you click on this button, it tells you when the record was created and by whom and also each time a field was changed and by whom. I find this extremely useful. However, I noted that these only show up for actions that are done on the admin page. So when I change the record through a view, this is not displayed in that history. Is there an easy way to have my views store this information also so that it will all be visible from the admin page?
Thanks so much in advance for your help.
The admin app comes with a model - LogEntry. Every time you do something in the admin app, there is some code somewhere that saves a LogEntry. This is how the app works. For example in the changeform_view there is something like this:
def changeform_view(self, request, ...):
...
if request.method == "POST":
if all_valid(formsets) and form_validated:
# do some other stuff then
self.log_change(request, new_object, change_message)
def log_change(self, request, object, message):
"""
Log that an object has been successfully changed.
The default implementation creates an admin LogEntry object.
"""
from django.contrib.admin.models import CHANGE, LogEntry
return LogEntry.objects.log_action(
user_id=request.user.pk,
content_type_id=get_content_type_for_model(object).pk,
object_id=object.pk,
object_repr=str(object),
action_flag=CHANGE,
change_message=message,
)
LogEntry.objects.log_action is where the log is created. Unfortunately, this doesn't happen anywhere else, unless you were to make it happen.
There's nothing stopping you from doing this though. You can create a LogEntry wherever you want.
Having said that, it might be a bit confusing since when you see a LogEntry you now no longer no if that is a change that has happened because of someone manually changing some data via the admin app, or a change that has occurred programatically. It would probably be a better idea to create your own Log model, and save logs where and when you want.
You can always display your Logs in your in the relevant admin view should you so wish. Something like this will do the trick:
class MyAdmin(admin.modelAdmin):
readonly_fields = (logs_field,)
def logs_field(self, instance):
logs = Log.objects.filter(object=instance)
return format_html_join(
'\n', "<p>{}: {}</p>",
((log.date, log.message) for log in logs)
)
If I am logged in as user1 and I am accessing a ViewSet called RecipeSubmissionViewSet, the POST takes a recipe_id of the recipe the user wants to submit. How do I ensure that user1 does not submit user2's recipe, assuming the Recipe model has an owner field on it and I can just compare it to request.user? Should I use a permission class for this or is there a better way? I'm speaking from an backend point of view and not taking into account that the front end would of course filter out the recipes that belong to the user and only show them their own recipes.
There can be two ways. You can filter out queryset or define permission class.
If you override get_queryset method like this.
class RecipeSubmissionViewSet(...):
def get_queryset(self):
return Recipe.objects.filter(owner=self.request.user)
# you can also use filtration based on action name like this
# if self.action == 'update':
# return Recipe.objects.filter(owner=self.request.user)
# return Recipe.objects.all()
User will get 404 response and will never be able to access objects other than he owns.
Second choice is permission class. You can define custom permission class and check ownership explicitly like this.
from rest_framework.permissions import BasePermission
class RecipeSubmissionPermission(BasePermission):
def has_object_permission(self, request, view, obj):
# you can also check permission here based on action
# if view.action == 'update':
# pass
return request.user.is_authenticated and obj.owner == request.user
class RecipeSubmissionViewSet(...):
permission_classes=[RecipeSubmissionPermission]
In this case user will get 403 permission error.
If you use both of these methods. 404 will be preferred.
You can use whichever method you want or both of these. Permission class looks more programmatic and structured way of doing it but user will know that object with this id exists but he did not have permission to update it. But if you override queryset, user is not even be able to know if object exists or not thus more secure.
Are you using the django authentication system? Then you should be able to access request.user in the views and set the owner field accordingly.
EDIT: I think I misunderstood the question.
But this could help and Nafees Anwar looks good.
Nafees answer is pretty much the way to go.
I have been developing microservices with multitenancy to users and the rules for them(as per my projecs spec) are:
Users cannot create items on behalf of another company/user
Users cannot view/edit items belonging to another company.
The way I do this is simple.
To prohibit viewing/editing of someone else's stuff
def get_queryset(self):
return self.queryset.filter(user=request.user)
And to prohibit editing of someone else's stuff, this is done on a serializer
class SomeSerializer(...):
def validate(self, data):
data.pop('user', None)
data['user'] = self.context['request'].user
With the above, the get_queryset in the viewset will always return a 404 if user1 requests user2 info.
And the validate function will prevent assignment. Ie. If user1 creates something assigned to user2, it will rather assign user1, this behaviour is what I needed in my application, you can always raise serializers.ValidationError("You cannot assign stuff to user2") instead if thats what you need instead of reassigning the user as I do in my use case. With having this logic in the validate you can be sure that any writable function will always carry the same behaviour for that serializer.
Hope that this helps.
I'm using Django Allauth. Users can either sign up using Google, Twitter, Facebook or they can sign up using their email address. Once signed up, their details will be stored in the User table. There's also another model I have called Profile that contains user information like bio, avatar, etc. I'd like to create a Profile for the user when they sign up. I looked at Allauth signals and found the user_signed_up signal to be appropriate. Here's how I wrote the code in my handlers.py file:
#receiver(user_signed_up)
def create_profile(request, user):
profile = Profile(avatar='img/blah/blah.jpg', bio='Example text', gender='M', dob='2018-01-01',
country='US', user=user)
profile.save()
I added random stuff just so I can see if it's being created or not, but for some reason when the user signs up their profile is not being created. What am I doing wrong?
You have to be sure you're importing the handlers.py module somehow in order to signal handler gets registered, you can write
import handlers
anywhere in your code, but the recommended place is in the ready method of your app config class.
References:
https://chriskief.com/2014/02/28/django-1-7-signals-appconfig/
https://docs.djangoproject.com/en/2.1/ref/applications/
You need to write signal when user model instance save signal will work there you need to write one condition this instance is new created or old modify base on this condition you can create profile
#receiver(post_save, sender=User)
def user_updated(sender, created=False, **kwargs):
user = kwargs.get('instance', None)
if user and created:
##create profile object here
For a password change I am using auth_views.password_change and for the password reset auth_views.password_reset.
How can I be notified when a user successfully changes their password? I do not need to know old nor new password. Just that the event took place, and for which user.
Similarly, I would like to get notified when someone requested a password reset and also when they successfully completed the reset procedure.
Can i do the above with signals or some simple patching? Or do I need to write my own views to do this?
Create a decorator:
def notify_admins(func):
def wrapper(request, *args, **kwargs):
# send email to admins
return func(request, *args, **kwargs)
return wrapper
Then, just add wrap it around the appropriate views in your urls.py:
urlpatterns = patterns('',
...
(r'^password_change/done/$', notify_admins(auth_views.password_change_done)),
(r'^password_reset/done/$', notify_admins(auth_views.password_reset_done)),
(r'^reset/done/$', notify_admins(auth_views.password_reset_complete)),
...
)
Keep in mind that sending email directly from a view, or in this case a decorator, will tie up the request. Instead of sending the email there directly, it would be better to create a custom signal and a handler that will fire off a thread to actually send the email. Then, in the decorator, you simply send the signal.
You could write a custom password_change_form that you pass to password_change. This form would extend django's PasswordChangeForm overriding its save method to first notify you of the change and then call it's parent PasswordChangeForms save method.
Docs on password_change view:
https://docs.djangoproject.com/en/dev/topics/auth/#django.contrib.auth.views.password_change
Docs on ChangeForm:
https://docs.djangoproject.com/en/dev/topics/auth/#django.contrib.auth.forms.PasswordChangeForm
Code for PasswordChangeForm:
https://code.djangoproject.com/browser/django/trunk/django/contrib/auth/forms.py
Beginning in Django 1.9, you can define your define your own password validators. You could even simply re-define an existing one, if you like. When you do, add a method:
from django.contrib.auth.password_validation import MinimumLengthValidator
class MyPasswordValidator(MinimumLengthValidator):
def password_changed(self, password, user):
# put your password changed logic here
Be sure to include your new class in your settings as follows:
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'my_package.password_validators.MyPasswordValidator',
'OPTIONS': {
'min_length': 8,
}
},
...
]
Now, each time a password is changed by the user, your class MyPasswordValidator will be notified. In my experience, this is the best way to do this because:
When using signals to capture these events, you will also capture events where the system re-encoded an existing password due to a change in hashing parameters, in most cases, you would not want to capture these events and there's no obvious way to prevent it with signals.
You could simple add a function-call in the save() method of all your password-handling forms, but this becomes difficult when you want to do the same with the built in admin change password form and will not help you if password changes are made programmatically outside of a form.
I will caution you to be aware that the password parameter in password_changed() is in fact the user's raw password. Take care when handling this and absolutely never store this anywhere unencrypted/unhashed.
If you are already using the auth_views.password_change built in view, then it would be easy to notify yourself once they are redirected after a successful change:
https://docs.djangoproject.com/en/dev/topics/auth/#django.contrib.auth.views.password_change
password_change(request[, template_name, post_change_redirect, password_change_form])
If you set the post_change_redirect url to redirect to one of your own views, then you simply take whatever action you want in that view to send a notification (email, database updates, etc).
You could even, in your redirect view, just do your notification and then return password_change_done(request[, template_name])
You could also capture the signal and check if the password has changed. Just keep in mind that this code will run every time a user changes.
#receiver(pre_save, sender=User)
def record_password_change(sender, **kwargs):
user = kwargs.get('instance', None)
if user:
new_password = user.password
try:
old_password = User.objects.get(pk=user.pk).password
except User.DoesNotExist:
old_password = None
if new_password != old_password:
# do what you need here
I am trying to create a custom signal for when the field auth_user.is_active becomes 1. I looked at Django's docs on signals, but was having trouble understanding how to implement custom signals.
When a user account becomes active, I want to execute the following function:
def new_user(sender, **kwargs)
profile = User.objects.get(id=user_id).get_profile()
return RecentActivity(content_object=profile, event_type=1, timestamp=datetime.datetime.now())
How would I do this. And also, what is the advantage of using signals over just doing the database insert directly? Thank you.
Here is what I did:
# in models.py
#receiver(pre_save, sender=User, dispatch_uid='get_active_user_once')
def new_user_activation_handler(sender, instance, **kwargs):
if instance.is_active and User.objects.filter(pk=instance.pk, is_active=False).exists():
profile = User.objects.get(pk=instance.pk).get_profile()
RecentActivity.objects.create(content_object=profile, event_type=1, timestamp=datetime.datetime.now())
If you want to do something when the field is changing, you can use the approach suggested by Josh, which is essentially to override the __init__ method.
Signals are generally used to communicate between apps. For example auth app sends user_logged_in signal. So if you want to do something when user is logging in, you just handle this signal, no need to patch the app.