Moving object id parameter to consumers.py Django-Channels - python

I have a problem. The point is that I am making application with Django backend and React frontend. I wanted to make a websocket which allows to write in live-chat rooms. The problem is that I have no idea how to load dynamic a Room id. Ill try to explain. The point is that connect method from ChatConsumer class will load messages realted to room and send it by json to frontend.
Ths is how it looks.
class ChatConsumer(WebsocketConsumer):
def connect(self):
self.room_group_name = 'test'
async_to_sync(self.channel_layer.group_add)(
self.room_group_name,
self.channel_name
)
messages = Message.objects.filter(room=[HERE I NEED TO PUT ID OF ROOM])
data_ready_for_json =list( messages.values('room','body','user'))
self.accept()
self.send(text_data=json.dumps({
'type':'chat',
'message': data_ready_for_json
}))
Iam trying to send this id from my views, where I have RoomRetrieveView built by generics.RetrieveAPIView.
Here it is:
class RoomRetrieveView(generics.RetrieveAPIView):
queryset = Room.objects.all()
permission_classes = (AllowAny,)
serializer_class = RoomSerializer
def get_serializer_context(self):
context = super(RoomRetrieveView,self).get_serializer_context()
context.update({'id' : self.get_object().id})
roomId = context['id']
return roomId
I was trying to move this roomId variable from get_serializer_context to my consumers.py file but it wants me to put "self" attribute but I have no idea how to figure it out. I also tried to use get_object method but it also not working. I have no idea. I also tried to use global to make variable global from method but its still not working. When Im trying to import anything from views.py file and do something I am getting
File "E:\Coding\Python\PORTFOLIO\Say-It-Social\venv\lib\site-packages\channels\routing.py", line 30, in get_default_application
raise ImproperlyConfigured("Cannot import ASGI_APPLICATION module %r" % path)
django.core.exceptions.ImproperlyConfigured: Cannot import ASGI_APPLICATION module 'sayitsocial.asgi'

You could use room id in the URL and retrive it using: self.room_name = self.scope['url_route']['kwargs']['id'] also avoid using a blocking code in consumers, use async ORM calls by writing the query in a separate method and use database_sync_to_async as a decorator you could import it from channels.db

Related

Django | Adding/changing code in 3rd party packages with monkey patching

(+/- 3 months experience with Django)
I'm using the django-newsletter package to create and send newsletters to subscribers on my website. It uses a message.html template for which the context is added in the Submission model:
class Submission(models.Model):
# Some irrelevant code, only showing the send_message method below:
def send_message(self, subscription):
variable_dict = {
'subscription': subscription,
'site': Site.objects.get_current(),
'submission': self,
'message': self.message,
'newsletter': self.newsletter,
'date': self.publish_date,
'STATIC_URL': settings.STATIC_URL,
'MEDIA_URL': settings.MEDIA_URL
}
unescaped_context = get_context(variable_dict, autoescape=False)
subject = self.message.subject_template.render(
unescaped_context).strip()
text = self.message.text_template.render(unescaped_context)
message = EmailMultiAlternatives(
subject, text,
from_email=self.newsletter.get_sender(),
to=[subscription.get_recipient()],
headers=self.extra_headers,
)
if self.message.html_template:
escaped_context = get_context(variable_dict)
message.attach_alternative(
self.message.html_template.render(escaped_context),
"text/html"
)
# some more code irrelevant to the question
message.send()
Essentially upon Submitting the newsletter, the context is added in variable_dict in the Submission.send_message method.
In order to add more Personalization possibilities I would like to add my own context variables to this. From what I understand is that it's best to use monkey patching to achieve this, but I am not able to access the variable_dict from outside the method itself. Below is my monkey patch code:
## Monkey Patching ##
# monkey_patches.py
DJANGO_NEWSLETTER_CONTEXT_PATCH = True # adds context to Submission
def monkey_patch():
if DJANGO_NEWSLETTER_CONTEXT_PATCH:
from newsletter.models import Submission
old_send_message = Submission.send_message
def new_send_message(self, *k, **kw):
old_send_message(self, *k, **kw)
# trying to append to variable dict here, but failing:
print(variable_dict) # gives error
# self.send_message.variable_dict['test'] = 'testvalue' # gives error
# variable_dict['test'] = 'testvalue' # gives error
Submission.send_message = new_send_message
# apps.py
from django.apps import AppConfig
class SomeAppConfig(AppConfig):
name = 'SomeApp'
def ready(self):
try:
from SomeApp.monkey_patches import monkey_patch
monkey_patch()
except ImportError:
pass
Would really appreciate some help. Any pointers are welcome.

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.

Graphene resolver for an object that has no model

I'm trying to write a resolver that returns an object created by a function. It gets the data from memcached, so there is no actual model I can tie it to.
I think my main issue is I can't figure out what type to use and how to set it up. I'm using this in conjunction with Django, but I don't think it's a django issue (afaict). Here's my code so far:
class TextLogErrorGraph(DjangoObjectType):
def bug_suggestions_resolver(root, args, context, info):
from treeherder.model import error_summary
return error_summary.bug_suggestions_line(root)
bug_suggestions = graphene.Field(TypeForAnObjectHere, resolver=bug_suggestions_resolver)
Notice I don't know what type or field to use. Can someone help me? :)
GraphQL is designed to be backend agnostic, and Graphene is build to support various python backends like Django and SQLAlchemy. To integrate your custom backend, simply define your models using Graphene's type system and roll out your own resolvers.
import graphene
import time
class TextLogEntry(graphene.ObjectType):
log_id = graphene.Int()
text = graphene.String()
timestamp = graphene.Float()
level = graphene.String()
def textlog_resolver(root, args, context, info):
log_id = args.get('log_id') # 123
# fetch object...
return TextLogEntry(
log_id=log_id,
text='Hello World',
timestamp=time.time(),
level='debug'
)
class Query(graphene.ObjectType):
textlog_entry = graphene.Field(
TextLogEntry,
log_id=graphene.Argument(graphene.Int, required=True),
resolver=textlog_resolver
)
schema = graphene.Schema(
query=Query
)

Django Test mock instance of module variable

I'm trying to test my Django app which has a proxy API which is instantiated in its own module.
api.py
class ProxyApi(object):
def __init__(self, server_info):
pass
def validate_login(self, credentials):
# call to real api here
api = ProxyAPi()
middlewares.py
from mymodule.api import api
class MyMiddleware(MiddlewareMixin):
def process_request(self, request):
if api.validate_login():
# do something with proxy api
views.py
from mymodule.api import api
class TaskView(LoginRequiredMixin, FormView):
def get(self, request):
if api.validate_login():
# do something with proxy api
tests.py
class InputTasksViewTest(TestCase):
#mock.patch('mymodule.api.ProxyAPi')
def test_add(self, mock_api):
mock_api.validate_login.return_value = True
response = self.client.get(reverse('task'))
The original validate_loginis still called.
I would like to know how to handle the instantiation of ProxyApi while still retaining mocking capacity.
Ok I found my own solution, the problem was that once Django started, it read some files (like models or views or middlewares) that automatically instantiated api variable from import.
I just needed to defer this instantiation so I can mock the ProxyApi object, here's what I did:
api = SimpleLazyObject(lambda: ProxApi())
You have def validate_login(self, credentials): in api
and in middleware you define below code. So How you will send creadentials to API from middleware api.validate_login(<You should send credentials to api as parameter>):
from mymodule.api import api
class MyMiddleware(MiddlewareMixin):
def process_request(self, request):
if api.validate_login():
pass

Store objects at "request scope" in google app engine

I would like to store some information at the "request scope" when using google app engine (python). What I mean by this is that I would like to initialize some information when a request is first received, and then be able to access it from anywhere for the life of the request (and only from that request).
An example of this would be if I saved the current user's name at request scope after they were authenticated.
How would I go about doing this sort of thing?
Thanks!
A pattern used in app engine itself seems to be threading.local which you can grep for in the SDK code. Making os.environ request local is done like that in runtime/request_environment.py for example.
A rough example:
import threading
class _State(threading.local):
"""State keeps track of request info"""
user = None
_state = _State()
From elsewhere you could authenticate early on in handler code.
from state import _state
if authentication_passed:
_state.user = user
and provide convenience that can be used in other parts of your code
from state import _state
def get_authenticated_user():
user = _state.user
if not user:
raise AuthenticationError()
return user
You need something like this:-
class BaseHandler(webapp2.RequestHandler):
#A function which is useful in order to determine whether user is logged in
def initialize(self, *a, **kw):
#Do the authentication
self.username = username
class MainHandler(BaseHandler):
def get(self):
print self.username
Now if you inherit BaseHandler class all the request will first go through the initialize method of BaseHandler class and since in the BaseHandler class you are setting the username
and MainHandler inherits form BaseHandler you will have the self.username defined and all the request wil go through initialize method.

Categories