I've been struggling with this problem for 5 hours and I have a feeling it's a simple solution that I'm just overlooking.
I'm trying to tie in a third party module (Django Activity Stream) that uses a series of senders and receivers to post data about user activity to a database table. Everything is set up and installed correctly, but I get a 'Signal' Object has No Attribute 'Save' error when I try to run it.
I suspect the problem is in my syntax somewhere. I'm just getting started with Signals, so am probably overlooking something a veteran will spot immediately.
In views.py I have:
from django.db.models.signals import pre_save
from actstream import action ##This is the third-party app
from models import Bird
def my_handler(sender, **kwargs):
action.save(sender, verb='was saved')
#return HttpResponse("Working Great")
pre_save.connect(my_handler, sender=Bird)
def animal(request):
animal = Bird()
animal.name = "Douglas"
animal.save()
The Django Activity Stream app has this signals.py file:
from django.dispatch import Signal
action = Signal(providing_args=['actor','verb','target','description','timestamp'])
And then this models.py file:
from datetime import datetime
from operator import or_
from django.db import models
from django.db.models.query import QuerySet
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _
from django.utils.timesince import timesince as timesince_
from django.contrib.contenttypes import generic
from django.contrib.contenttypes.models import ContentType
from django.contrib.auth.models import User
from actstream import action
...
def action_handler(verb, target=None, **kwargs):
actor = kwargs.pop('sender')
kwargs.pop('signal', None)
action = Action(actor_content_type=ContentType.objects.get_for_model(actor),
actor_object_id=actor.pk,
verb=unicode(verb),
public=bool(kwargs.pop('public', True)),
description=kwargs.pop('description', None),
timestamp=kwargs.pop('timestamp', datetime.now()))
if target:
action.target_object_id=target.pk
action.target_content_type=ContentType.objects.get_for_model(target)
action.save()
action.connect(action_handler, dispatch_uid="actstream.models")
Your main problem is in the discipline in maintaining coding style, or rather in this case, lack of. You will find that it is easier to identify problems in your code if you do not use the same name to refer to multiple things within the same module; give each object a unique, meaningful name, and refer to it using only that name.
The bottom line here is that the docs for that project contain bad code. This line:
action.save(sender, verb='was saved')
isn't ever going to work. The from actstream import action ultimately imports a signal from actstream.signals, and signals do not and never have had a save method. Especially not with such an odd signature of sender, verb.
At first I thought maybe the author had done something odd with subclassing Signal, but after looking at the rest of the codebase, that's just not the case. I'm not entirely sure what the intention of those docs was supposed to be, but the right thing to do in your handler will either be to save a new Action (imported from actstream.models) instance, or to do something with your model.
Sadly, the project's repository has a pretty sorry set of tests/examples, so without downloading and trying the app myself, I can't tell you what needs to happen there. You might try contacting the author or simply try finding a better-documented/better-maintained Activity Streams app.
Related
In the docs for Django's System check framework it says:
Checks should be registered in a file that’s loaded when your application is loaded; for example, in the AppConfig.ready() method.
None of the examples on that page, or around the AppConfig.ready() method, show how to do this. Given a checking method like:
from django.core.checks import register, Tags
#register(Tags.compatibility)
def my_check(app_configs, **kwargs):
# ... perform compatibility checks and collect errors
return errors
How would you do this in/from the AppConfig.ready() method? Is one called from the other? Which file should the above method go in? Do you add #register(...) to the ready() method?
From reading the examples on this page about the Apps Registry and System Checks Framework, it seems there are (at least) two ways to add your own System Checks. To adapt that page's examples (assuming you're creating an app called myapp):
1) Create a myapp/checks.py file like this:
from django.apps import apps as camelot_apps
from django.core.checks import register, Warning
from django.core.checks import Tags as DjangoTags
class Tags(DjangoTags):
"""Do this if none of the existing tags work for you:
https://docs.djangoproject.com/en/1.8/ref/checks/#builtin-tags
"""
my_new_tag = 'my_new_tag'
#register(Tags.my_new_tag)
def check_taggit_is_installed(app_configs=None, **kwargs):
"Check that django-taggit is installed when usying myapp."
errors = []
try:
from taggit import models
except ImportError:
errors.append(
Warning(
"The django-taggit app is required to use myapp.",
hint=("Install django-taggit"),
# A unique ID so it's easier to find this warning:
id='myapp.W001',
)
)
return errors
And then in myapp/__init__.py (create it if it doesn't exist):
from . import checks
Running this should run that check above:
$ ./manage.py check myapp
2) Or, as I thought in my initial question, you can register that check in your AppConfig. So, keep the above code in myapp/check.py, but remove the #register(Tags.my_new_tag) line.
Then create myapp/apps.py containing this:
from django.core.checks import register
from .checks import Tags, check_taggit_is_installed
class MyappConfig(AppConfig):
name = 'myapp'
def ready(self):
super(MyappConfig, self).ready()
register(Tags.my_new_tag)(check_taggit_is_installed)
And alter myapps/__init__.py so it contains this:
from . import checks
default_app_config = 'myapp.apps.MyappConfig'
The first example seems simpler, with no need for a custom AppConfig.
Django recommends:
Checks should be registered in a file that’s loaded when your application is loaded; for example, in the AppConfig.ready() method.
Therefore, place the checks code in a checks.py file. Then simply in apps.py, as with signals:
from django.apps import AppConfig
class MyAppConfig(AppConfig):
name = 'MyApp'
verbose_name = "My App"
def ready(self):
# import myapp.signals
import myapp.checks
My guess is that you can just place this code in ready() method. For example of usage you can take a look at django.contrib.auth (or other contrib app). This app has check in checks.py file, that imported in apps.py and registered in ready() method: checks.py, apps.py.
I realize there are many other questions related to custom django signals that don't work, and believe me, I have read all of them several times with no luck for getting my personal situation to work.
Here's the deal: I'm using django-rq to manage a lengthy background process that is set off by a particular http request. When that background process is done, I want it to fire off a custom Django signal so that the django-rq can be checked for any job failure/exceptions.
Two applications, both on the INSTALLED_APPS list, are at the same level. Inside of app1 there is a file:
signals.py
import django.dispatch
file_added = django.dispatch.Signal(providing_args=["issueKey", "file"])
fm_job_done = django.dispatch.Signal(providing_args=["jobId"])
and also a file jobs.py
from app1 import signals
from django.conf import settings
jobId = 23
issueKey = "fake"
fileObj = "alsoFake"
try:
pass
finally:
signals.file_added.send(sender=settings.SIGNAL_SENDER,issueKey=issueKey,fileName=fileObj)
signals.fm_job_done.send(sender=settings.SIGNAL_SENDER,jobId=jobId)
then inside of app2, in views.py
from app1.signals import file_added, fm_job_done
from django.conf import settings
#Setup signal handlers
def fm_job_done_callback(sender, **kwargs):
print "hellooooooooooooooooooooooooooooooooooo"
logging.info("file manager job done signal fired")
def file_added_callback(sender, **kwargs):
print "hellooooooooooooooooooooooooooooooooooo"
logging.info("file added signal fired")
file_added.connect(file_added_callback,sender=settings.SIGNAL_SENDER,weak=False)
fm_job_done.connect(fm_job_done_callback,sender=settings.SIGNAL_SENDER,weak=False)
I don't get any feedback whatsoever though and am at a total loss. I know for fact that jobs.py is executing, and therefore also that the block of code that should be firing the signals is executing as well since it is in a finally block (no the try is not actually empty - I just put pass there for simplicity) Please feel free to ask for more information - I'll respond asap.
here is the solution for django > 2.0
settings.py:
change name of your INSTALLED_APPS from 'app2' to
'app2.apps.App2Config'
app2 -> apps.py:
from app1.signals import file_added, fm_job_done
Class App2Config(AppConfig):
name = 'app2'
def ready(self):
from .views import fm_job_done_callback, file_added_callback
file_added.connect(file_added_callback)
fm_job_done.connect(fm_job_done_callback)
use django receiver decorator
from django.dispatch import receiver
from app1.signals import file_added, fm_job_done
#receiver(fm_job_done)
def fm_job_done_callback(sender, **kwargs):
print "helloooooooooooooo"
#receiver(file_added)
def file_added_callback(sender, **kwargs):
print "helloooooooooooooo"
Also, I prefer to handle signals in models.py
I have a requirement to write a DB row when a user logs in. The following code is in models.py (at end of file, after model definitions);
models.py
from django.contrib.auth.signals import user_logged_in
from utils import *
def rec_login(sender, request, user, **kwargs):
u_audit('some text here', user)
user_logged_in.connect(rec_login)
There's a utilities module which is imported in modules.py. The following code is called in utils.py from the above function;
utils.py
from app.models import *
def u_audit(msg,u):
ua=UserLog(action=msg, user=u, actiontime=datetime.now())
ua.save()
I'm reusing the u_audit() function in several other places (post-login).
When a user logs in, I get a NameError for the UserLog object (i.e. it looks like the model definitions can't be accessed by the signal callback function).
The UserLog object referred to above is just a simple models.Model.
Anyone know what I'm missing?
I've tried putting a simple file write in the callback function in models.py and tried the same thing in the u_audit() function. instead and that works fine, so I know it's being called properly. I've got other signal callback functions registered (all post-login) and they're using models and working fine.
Not a circular import? Utils refers to models and models to utils - it will not work. Please change utils.py to:
def u_audit(msg,u):
from app.models import UserLog
ua=UserLog(action=msg, user=u, actiontime=datetime.now())
ua.save()
But also something like that suggest that maybe models.py is good place for u_audit and you should just move it there.
I have the requirement that whenever there is a model get's added/changed/deleted, it should send a mail notification. The content will be more like the django_admin_log entries. I just need to extend this functionality in my model to send the mail. Any suggestions?
Django_log_admin will only track changes made in the admin interface. If the model is changed anywhere else, it will not update the log. However, if you are OK with just admin changes, then you can use a combination of django_log_admin and the post_save signal to do the trick. Put this in your management.py:
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.contrib.admin.models import LogEntry
from django.core.mail import mail_admins
from django.template.loader import render_to_string
#will be triggered every time a LogEntry is saved i.e. every time an action is made.
#receiver(post_save, sender=LogEntry)
def send_notification_email(change, **kwargs):
mail_admins(subject="model %(model) has been changed by %(user)" %
{'model':change.content_type, 'user': change.user},
message = render_to_string('change_email.html', { 'change': change }) )
note to self: wow, django really includes all the batteries :D
You should look at Django's signals. In your case, you'll connect your handlers to the post_save and post_delete signals, for starters. Look through the built-in signal documentation for others you may want to tap. No need to hack into admin.
Is there a way to extend another apps ModelAdmin?
I have a project that uses functionality offered by django.contrib.comments.
The CommentsAdmin ModelAdmin class has:
actions = ["flag_comments", "approve_comments", "remove_comments"]
I would like to extend the CommentsAdmin ModelAdmin in my project to include an action ban_user.
I've tried creating my own NewCommentsAdmin(CommentsAdmin) object in my admin.py file and registering it, but I get a notice 'AlreadyRegistered at /admin/' 'The model Comment is already registered'.
class NewCommentAdmin(CommentAdmin):
actions = ['ban_user']
def ban_user(self, request, queryset):
pass
admin.site.register(Comment, NewCommentAdmin)
Is there a way to do this without modifying the original django.contrib.comments code?
Here's how I do it in one project for the User model. In the admin.py for my app:
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.models import User
class MyUserAdmin(UserAdmin):
# ...
admin.site.unregister(User)
admin.site.register(User, MyUserAdmin)
Unregister the Comment model first.
I guess you have something like this at the top of your file:
from django.contrib.comments.admin import CommentAdmin
This import executes the registration of the model (at the very bottom of this admin file) again.
One idea that doesn't look very nice (I actually haven't tried it) could be:
from django.contrib.comments.models import Comment
from django.contrib import admin
from django.contrib.admin.sites import NotRegistered
# Try to unregister the Comment model
# that was registered via the auto_discover method
try:
admin.site.unregister(Comment)
except NotRegistered:
pass
# Now we can load the CommentAdmin (which reregisters the admin model)
from django.contrib.comments.admin import CommentAdmin
# We have to unregister again:
try:
admin.site.unregister(Comment)
except NotRegistered:
pass
# Now your stuff...
I guess this could be done better but it should work. To make this approach work, the application that contains this file has to be after the comments application in INSTALLED_APPS.
Now to your class. I think if you write actions = ['ban_user'] you actually overwrite all the actions in the parent class. I think it is the easiest way to override the get_actions method:
class NewCommentAdmin(CommentAdmin):
def get_actions(self, request):
actions = super(NewCommentAdmin, self).get_actions(request)
# Do some logic here based on request.user if you want
# to restrict the new action to certain users
actions.append('ban_user')
return actions
def ban_user(self, request, queryset):
pass
admin.site.register(Comment, NewCommentAdmin)
Hope that helps (or at least gives an idea) :)
Have a look at https://github.com/kux/django-admin-extend
It offers some easy to use functions and decorators that implement the functionality you're requesting in a very flexible manner. The documentation does a pretty good job at explaining why using this approach is better than direct inheritance.
It also has support for injecting bidirectional many to many fields.