Use signals in Django 1.9 - python

In Django 1.8, I was able to do the following with my signals, and all was well:
__init__.py:
from .signals import *
signals.py:
#receiver(pre_save, sender=Comment)
def process_hashtags(sender, instance, **kwargs):
html = []
for word in instance.hashtag_field.value_to_string(instance).split():
if word.startswith('#'):
word = render_to_string('hashtags/_link.html',
{'hashtag': word.lower()[1:]})
html.append(word)
instance.hashtag_enabled_text = ' '.join(html)
In Django 1.9, I get this error: django.core.exceptions.AppRegistryNotReady: Apps aren't loaded yet.
I know it's coming from the __init__.py, but does anyone know a workaround for this? I'm assuming maybe putting it in the models? If so, could someone please show me how to do that?
models.py:
class Comment(HashtagMixin, TimeStampedModel):
user = models.ForeignKey(settings.AUTH_USER_MODEL)
text = models.TextField(max_length=240)
hashtag_enabled_text = models.TextField(blank=True)
hashtag_text_field = 'text'
objects = CommentManager()
class Meta:
app_label = 'comments'
def __unicode__(self):
return self.text
Thank you in advance!

From the release notes:
All models need to be defined inside an installed application or declare an explicit app_label. Furthermore, it isn’t possible to import them before their application is loaded. In particular, it isn’t possible to import models inside the root package of an application.
By importing your signals in __init__.py, you're indirectly importing your models in the root package of your application. One option to avoid this is to change the sender to a string:
#receiver(pre_save, sender='<appname>.Comment')
def process_hashtags(sender, instance, **kwargs):
...
The recommended way to connect signals that use #receiver decorator in 1.9 is to create an application configuration, and import the signals module in AppConfig.ready().

Related

Django: How to call the same model class inside it s self?

I have a function make_fields_permissions that I need to use it inside the model calss in order to parse the fields and to make permissions for each field like [('can_view_first_name_field','can view first name'),...]
goal I need to call and override Person class and inside it self
I tried
def __init__(self,*args, **kwargs):
self.Meta.permissions = make_fields_permissions(self.model)
super().__init__(*args, **kwargs)
My code look like this
from django.db import models
class Person(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
def __init__(self, *args, **kwargs):
# kwargs['user']['permissions'] = make_fields_permissions(Profile)
# x = self['age']
super().__init__(*args, **kwargs)
class Meta:
permissions = make_fields_permissions(Person) #< I can't use the same model inside meta
Your goal is as follows:
Goal X (Real goal): Create permissions dynamically according to the model fields
Goal Y (Perceived goal that will achieve X): Call the model class while creating it.
Note: See What is the XY problem?
Let us first discuss Goal Y and why it is too complex, and somewhat unfeasable. When one wants to customize how the creation of a class occurs one would use metaclasses, and at first sight this would appear as a perfect solution for your needs (in fact if you do create one properly it would be). But the problem here is that Model already has a metaclass being ModelBase and it is already doing lots of stuff and is a little complicated. If we would want a custom metaclass we would need to inherit from it and very carefully work around its implementation to do what we want. Furthermore making it would not be the end of the story, because then we would need to maintain it since it would be easily breakable by updates to Django. Hence Goal Y is not feasible.
Moving on to the actual Goal X to do that one can Programmatically create permissions [Django docs]. A good place to do this would be in the app configs ready method. For all apps created using startapp there is an apps.py file which has an appconfig inheriting from AppConfig, when the models are loaded its ready method is called. Hence this method is used to do various tasks like attaching signals, various setup like tasks, etc. Modify the appconfig of your app to create permissions programmatically like so:
from django.apps import AppConfig
class YourAppConfig(AppConfig):
default_auto_field = 'django.db.models.AutoField' # Don't modify, keep it as it is in your code
name = 'your_app' # Don't modify, keep it as it is in your code
def ready(self):
from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType
from path.to import make_fields_permissions
from .models import Person
# import models here, not outside as models may not be loaded yet
content_type = ContentType.objects.get_for_model(Person)
for codename, name in make_fields_permissions(Person):
Permission.objects.get_or_create(
codename=codename, # 'can_view_first_name_field'
name=name, # 'can view first name'
content_type=content_type,
)

Django signals not called when app is registered using package name but works with app config class

I was experiencing a problem where my signals were not getting called. I have an app called users and in it I have a model for Profile that extends the django User model and when a user object is saved, I need to create a corresponding profile for it for that I added a signals.py module in the users app, here's my signals file
from django.db.models.signals import post_save
from django.contrib.auth.models import User
from django.dispatch import receiver
from .models import Profile
#receiver(post_save, sender=User)
def create_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)
#receiver(post_save, sender=User)
def save_profile(sender, instance, created, **kwargs):
instance.profile.save()
And inside my settings.py INSTALLED_APPS I added my app like this
INSTALLED_APPS = [
# other apps
'users',
]
And my users/apps.py looks like this
from django.apps import AppConfig
class UsersConfig(AppConfig):
name = 'users'
def ready(self):
import users.signals
After registration, the user object was getting created but no corresponding profile. I even put some debug statements in the signal receivers just to confirm my receivers were not getting called and yes the receivers were never getting called.
I couldn't find a solution to this as all answers I found on SO showed similar configurations that worked. Out of curiosity I decided to register the users app using config class and not the module name and changed my settings INSTALLED_APP to look like this
INSTALLED_APPS = [
# other apps
'users.apps.UsersConfig',
]
After doing that everything was working. On registration a new user, a profile was been created from within my signal receiver.
Now I don't understand what difference doing that makes. Everything(migrations, templates, etc) else was working fine with the previous configuration. Why did I have to change it for my signal receivers to work?
Prior to the Django 3.2 version, Django was only checking default_app_config in your applications __init__.py file to determine which application configuration class should be used by default when you've not provided it explicitly in your INSTALLED_APPS. If you didn't specify one there, it will create a new, blank one for you and use that.
On 3.2 release this has changed and now, if there is AppConfig class in your apps.py file with default = True set, this one will be used by default or if your application defines only one AppConfig in the apps.py file and it doesn't have the default = True specified, this one will be used.
You can read more about the new behavior in the changelog and for the previous behavior, you can check docs of the 3.1 version
To fix your problem, you can either define default_app_config in your application or upgrade the Django version to 3.2 (given you have only one AppConfig defined or you've marked one with default = True), whichever is more relevant to your case.

Using Python 3.7 contextvars to pass state between Django views

I'm building a single database/shared schema multi-tenant application using Django 2.2 and Python 3.7.
I'm attempting to use the new contextvars api to share the tenant state (an Organization) between views.
I'm setting the state in a custom middleware like this:
# tenant_middleware.py
from organization.models import Organization
import contextvars
import tenant.models as tenant_model
tenant = contextvars.ContextVar('tenant', default=None)
class TenantMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
user = request.user
if user.is_authenticated:
organization = Organization.objects.get(organizationuser__is_current_organization=True, organizationuser__user=user)
tenant_object = tenant_model.Tenant.objects.get(organization=organization)
tenant.set(tenant_object)
return response
I'm using this state by having my app's models inherit from a TenantAwareModel like this:
# tenant_models.py
from django.contrib.auth import get_user_model
from django.db import models
from django.db.models.signals import pre_save
from django.dispatch import receiver
from organization.models import Organization
from tenant_middleware import tenant
User = get_user_model()
class TenantManager(models.Manager):
def get_queryset(self, *args, **kwargs):
tenant_object = tenant.get()
if tenant_object:
return super(TenantManager, self).get_queryset(*args, **kwargs).filter(tenant=tenant_object)
else:
return None
#receiver(pre_save)
def pre_save_callback(sender, instance, **kwargs):
tenant_object = tenant.get()
instance.tenant = tenant_object
class Tenant(models.Model):
organization = models.ForeignKey(Organization, null=False, on_delete=models.CASCADE)
def __str__(self):
return self.organization.name
class TenantAwareModel(models.Model):
tenant = models.ForeignKey(Tenant, on_delete=models.CASCADE, related_name='%(app_label)s_%(class)s_related', related_query_name='%(app_label)s_%(class)ss')
objects = models.Manager()
tenant_objects = TenantManager()
class Meta:
abstract = True
In my application the business logic can then retrieve querysets using .tenant_objects... on a model class rather than .objects...
The problem I'm having is that it doesn't always work - specifically in these cases:
In my login view after login() is called, the middleware runs and I can see the tenant is set correctly. When I redirect from my login view to my home view, however, the state is (initially) empty again and seems to get set properly after the home view executes. If I reload the home view, everything works fine.
If I logout and then login again as a different user, the state from the previous user is retained, again until a do a reload of the page. This seems related to the previous issue, as it almost seems like the state is lagging (for lack of a better word).
I use Celery to spin off shared_tasks for processing. I have to manually pass the tenant to these, as they don't pick up the context.
Questions:
Am I doing this correctly?
Do I need to manually reload the state somehow in each module?
Frustrated, as I can find almost no examples of doing this and very little discussion of contextvars. I'm trying to avoid passing the tenant around manually everywhere or using thread.locals.
Thanks.
You're only setting the context after the response has been generated. That means it will always lag. You probably want to set it before, then check after if the user has changed.
Note though that I'm not really sure this will ever work exactly how you want. Context vars are by definition local; but in an environment like Django you can never guarantee that consecutive requests from the same user will be served by the same server process, and similarly one process can serve requests from multiple users. Plus, as you've noted, Celery is a yet another separate process again, which won't share the context.

in django 1.8, how to set sender for post_migrate and post_syncdb signal receiver when a custom user model is set?

Following is my code in the signals.py file placed in the package where the auth model is defined.
#receiver(post_migrate, sender=settings.AUTH_USER_MODEL)
def define_groups(sender, **kwargs):
# Create groups
Group.objects.get_or_create(name='Promoter')
Group.objects.get_or_create(name='Client')
Group.objects.get_or_create(name='Superuser')
Group.objects.get_or_create(name='Staff')
The documentation (https://docs.djangoproject.com/en/1.8/topics/auth/customizing/#referencing-the-user-model) states that it should be set as
sender=settings.AUTH_USER_MODEL
while this only works for post_save as mentioned in the documentation example.
I've already tried get_user_model() and also directly using the my_custom_user.models.
get_user_model() returns an error, while setting models as sender works just fine, as -
from . import models
#receiver(post_syncdb, sender=models)
def define_groups(sender, **kwargs):
# Create groups
Group.objects.get_or_create(name='Promoter')
Group.objects.get_or_create(name='Client')
Group.objects.get_or_create(name='Superuser')
Group.objects.get_or_create(name='Staff')
But according to documentation this is not the right way to refer a custom user model and is just an ugly workaround.
Would someone please be able to help me with a solution so i can add these Groups with the first migration of user model.
Thank You
EDIT : using get_user_model() returns the following error -
django.core.exceptions.AppRegistryNotReady: Models aren't loaded yet.
The sender for the post_migrate method is never a model (custom or otherwise), it is the AppConfig instance for the app which was installed.
The docs give the following example for connecting your signal handler in the ready method.
from django.apps import AppConfig
from django.db.models.signals import post_migrate
def my_callback(sender, **kwargs):
# Your specific logic here
pass
class MyAppConfig(AppConfig):
...
def ready(self):
post_migrate.connect(my_callback, sender=self)
Similarly, the sender for post_sync_db signal (note the signal is deprecated) is the module containing the models which were installed.

Importing models in signals cause django deprecation warnings app_label

This is my code in my signals.py
from .models import Entry
#receiver(pre_save, sender=Entry)
def do_stuff(sender, instance, *args, **kwargs):
pass
Now this questions is related
Django 1.9 deprecation warnings app_label
But I am not able to figure out why I need to create extra class for that.
Warning:
Model class app.models.Entry doesn't declare an explicit app_label and either isn't in
an application in INSTALLED_APPS or else was imported before its application was loaded.
This will no longer be supported in Django 1.9.
If I just empty my signals file then there is no warning.
The issue is using .models in signals as mentioned in that question
This is mostly likely because your application is not in the INSTALLED_APPS within your settings.py
I also got this error, what I found the error is in model import before it exist.
I used this to import model and it works for me
from django.apps import apps
model_obj = apps.get_model('app_name', 'model_name')
model_obj.objects.get() ...etc

Categories