What is the most lightweight way to notify clients of changes to a model table they are viewing?
I've used Django Rest Framework to set up an API that serves a templated table of items to clients, and allows them to change buyers on the fly.
Currently, I use a recurring jQuery AJAX request with a setTimeout for 2 seconds. This sends a ton of requests and data even when there are no changes, and the webpage size keeps growing.
I've had to disable caching as some users might have IE11.
I started looking for a way to push the updates to the clients and started exploring Django Channels and Server-Sent-Events.
Django Channels
Built the demo chat app
Very fast
Websockets are supported by all my target browsers
Seems like overkill for what I'm trying to achieve.
Lots of configuration
Requires Redis or some other datastore
I don't really need the two way communication
My app is hosted on Pythonanywhere, which doesn't allow ASGI and
doesn't seem to have any plans to do so (1, 2).
Server Sent Events
Very little information on how to configure this for Django
No native support in IE11 or Edge, but there are polyfills available
Found and tested a working example from stackoverflow, not
exactly sure what it's doing though. The webpage is updated every 5 seconds, not sure where that is controlled.
Very little set up, seems almost magical
Seems like it would be ideal to use this with the post_save
signal in Django, but I can't figure out how to set this up.
Current, AJAX based set-up:
models.py
...
...
class Buyer(models.Model):
name = models.CharField(unique=True, max_length = 20)
class Item(models.Model):
name = models.CharField(unique=True, max_length = 50)
active = models.BooleanField(default=True)
bought_by = models.ForeignKey(Buyer, null=True, blank=True, to_field="name",)
views.py
...
...
class ItemViewSet(viewsets.ModelViewSet):
queryset = models.Item.objects.select_related("bought_by")
serializer_class= serializers.ItemSerializer
filterset_fields = ("bought_by")
renderer_classes = [renderers.JSONRenderer, renderers.BrowsableAPIRenderer, renderers.TemplateHTMLRenderer]
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
if request.accepted_renderer.format == "html":
items = list()
for item in queryset:
items.append({"serializer": self.get_serializer(item), "item": item})
return Response(
{
"items_info": items,
"style": {"template_pack": "rest_framework/inline/"},
},
template_name="myapp/items_list.html",
)
else:
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
...
...
(list method was modified to make each item editable by the clients)
handler.js
...
...
$.ajaxSetup({
cache: false
});
var tableUpdater = null;
var updateRequest = null;
// helper that can be called to cancel active timer/ajax in the
// case of interaction with buttons/selects on the page or
// in the case of a new request
function stopUpdate() {
if (tableUpdater || updateRequest) {
clearTimeout(tableUpdater);
updateRequest.abort();
}
}
// Update data table
function tableUpdate() {
stopUpdate();
updateRequest = $.ajax({
type: "GET",
url: "myapp/items/?format=html",
success: function(data) {
$("#activeRequests").html(data);
// schedule another AJAX request
tableUpdater = setTimeout(tableUpdate, 2000);
}
});
}
...
...
I had a go with Django Channels, but then switched to pusher.com That has worked very well for me, so I think it would be worth looking at for you.
Related
I have a web application purely based on REST API using Django Rest Framework. I have seen that at most of the places the response for my APIs is not changing or not changing frequently, so I'm thinking to cache such API's and for that, I'm using https://pypi.org/project/redis/ package. So my question here is what could be the better way to implement caching, it should be at the view level, at the model level, or in the serializer. How it could be done?
You can use Cache for APIview and ViewSets with decorators like cache_page.
NOTE: The cache_page decorator only caches the GET and HEAD responses with status 200.
Ex:
from django.utils.decorators import method_decorator
from django.views.decorators.cache import cache_page
class UserViewSet(viewsets.ViewSet):
# Cache requested url for each user for 2 hours
#method_decorator(cache_page(60*60*2))
def list(self, request, format=None):
content = {
'user_feed': request.user.get_user_feed()
}
return Response(content)
Or
class PostView(APIView):
# Cache page for the requested url
#method_decorator(cache_page(60*60*2))
def get(self, request, format=None):
content = {
'title': 'Post title',
'body': 'Post content'
}
return Response(content)
DRF cache docs.
settings.py:
# Enable memcache
# https://devcenter.heroku.com/articles/memcache#using_memcache_from_python
CACHES = {
'default': {
'BACKEND': 'django_pylibmc.memcached.PyLibMCCache'
}
}
If you want store just result of a method or a query and store for furture requests, you can just define a key for it and set that result to that key with cache.set(cache_key, result, expire_time) and then get it(cache.get(cache_key)) whenever you want.
Remember you should define a cache backend for your results.Proper and better solution is to use message brokers like redis or memcached to store cache.based on your needs.
//Updated//
check if data is already cashed or not.
class WeatherView(APIView):
def get(self):
if(cache.get('weatherdata') == None):
url = 'https://api.openweathermap.org/data/2.5/forecast?q=' + '...';
serialized_data = urlopen(url).read()
data = json.loads(serialized_data)
print(data)
cache.set('weatherdata', data, 3600)
else:
data = cache.get('weatherdata')
serializer_class = WeatherSerializer(data)
responseJSON = JSONRenderer().render(serializer_class.data)
return Response(responseJSON)
I want to push front end data (Form inputs) to the server via Ajax. For this, I created an Ajax post request but I'm very unsteady...
At my first attemps, I constantly receive errors by python
Ajax call:
//Get journey time for the stated address
jQuery.ajax({
type: 'post',
url: 'http://127.0.0.1:8000/termin/get-journey-time/',
data: {
'method': 'get_journey_time',
'mandant_id': 1,
'customer_address': customer_address,
'staff_group': staff_group_id
},
error: function (jqXHR, textStatus, errorThrown) {
console.log("Error")
},
timeout: 120000,
});
I've created a view in Python, in which I want to do something (views.py)
class get_journey_time(generics.ListAPIView):
"""
Handle Ajax Post to calculate the journey time to customer for the selected staff group
"""
permission_classes = (AllowAny,)
def post(self, request, *args, **kwargs):
print(request)
In my url route file I have this code lines (urls.py)
urlpatterns = [
XXXXXXXXXXXXXXXXXXXXXXXXX,
path('termin/get-journey-time/', views.get_journey_time.as_view()),
XXXXXXXXXXXXXXXXXXXXXXXXX,
XXXXXXXXXXXXXXXXXXXXXXXXX,
]
I got the Error code 500:
Expected a `Response`, `HttpResponse` or `HttpStreamingResponse` to be returned from the view, but received a `<class 'NoneType'>`
Is there a mistake in my approach, did I miss anything or is it completely crap?
Define renderer classes and parser classes in settings.py.
Note: You can define many of these (based on requirements and needs) but here we only need JSON related.
As a reference, you can check my repo's this file https://github.com/hygull/p-host/blob/master/src/pmt_hostel_app/views.py. I have used function based views, just ignore the code inside it and focus on request.data and also check related HTML files.
REST_FRAMEWORK = {
'DEFAULT_PARSER_CLASSES': (
'rest_framework.parsers.JSONParser',
),
'DEFAULT_RENDERER_CLASSES': (
'rest_framework.renderers.JSONRenderer',
)
}
In this way you will be able to access the posted data in the form of dictionary which can be obtained using request.data in views.
Finally, return Response from the post() method. By default return type of function/method is None and you are just printing the request.
Check the below mentioned links, it will help you a lot.
https://www.django-rest-framework.org/api-guide/renderers/
https://www.django-rest-framework.org/api-guide/parsers/
In client code, I mean in JavaScript code, define a success callback as well (you have just defined error callback).
Please comment, if you are stuck.
you can do it like this
from rest_framework.response import Response
from rest_framework.views import APIView
class get_journey_time(APIView):
# ListAPIView is used for read-only endpoints
#
"""
Handle Ajax Post to calculate the journey time to customer for the selected staff group
"""
permission_classes = (AllowAny,)
def post(self, request, *args, **kwargs):
# you can get the posted data by request.data
posted_data = request.data
data = {"test": "test"}
return Response(data)
you can get the posted data and use serializers. you can start learning playing with serializers from here
Example serializer code can be like this
from rest_framework import serializers
class DummySerializer(serializers.Serializer):
name = serializers.CharField()
mobile_number = serializers.CharField(required=False)
and then use it in post method of your get_journey_time class
I'm using wagtail with Python and Django.
I have model as follows:
class HomePage(Page):
logo = models.ForeignKey(
'wagtailimages.Image',
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name='+'
)
banner_text = RichTextField(blank=True)
def get_context(self, request):
context = super().get_context(request)
context['vehicles'] = get_vehicles("nl")[0:12]
return context
content_panels = Page.content_panels + [
FieldRowPanel([ImageChooserPanel('logo', classname="col4")]classname="full"),
FieldRowPanel([FieldPanel('banner_text', classname="full") classname="full")
]
And get_vehicles("nl") is as follows:
def get_vehicles(lang):
response = requests.get(API_URL, headers={'Authorization': "Token {}".format(token), "Accept-Language": lang})
data = json.loads(response.content.decode("utf-8"))
return data['vehicles']
Is there any way to do get those vehicles with ajax call inside def get_context(self, request):?
I want to show some spinner in my home_page.html template until all vehicles are fetched.
I'm totally new to wagtail and I'm not sure how can I do that.
get_context() would not be relevant here.
You'll need to approach this in "traditional Django style." In other words, you build a URL pattern and a JSON view, entirely apart from Wagtail.
On the client side, the code that is issuing these JSON requests in some kind of loop can receive, for example, some indication from the host (that is to say, in the returned JSON data ...) that "we're done ... there is no more data to return." Upon seeing this, the JavaScript code could, say, "issue a signal" (with whatever JS framework you are using on that side) which would trigger a piece of JavaScript code embedded in the target page to cause it to remove the spinner.
Here is my simple view:
def transaction_list(request):
current_user = request.user
month = datetime.date.today().month
try:
page = request.GET.get('page', 1)
except PageNotAnInteger:
page = 1
objects = Transaction.objects.filter(user=current_user, date__month=month)
p = Paginator(objects, 10, request=request)
transactions = p.page(page)
return render(request, 'transaction/list.html',
{'transactions': transactions})
It shows a list of transactions which occured in the current month.
I want to add an option to change the month of the transactions being displayed but I have no clue how to tackle this and make it work. Should it be done in a view? maybe template? I would appreciate any ideas
Take some time to read some Django docs as they may prove really valuable (not to mention they are very clean and well written for every version available). I would focus on Working With Forms
In short, you'll pass the month from your django template (maybe via ajax or a simple HTML form POST), to your view, and use your view function to get the POST data and use that in your queryset.
It is hard to provide a good, thorough answer because there are different ways to do this. Do you want AJAX? Simple form with page reload, etc?
Okay, here, in detail, is how I usually handle a POST request. This isn't tested code, it's just psuedo code, but should work as far as I can tell, aside from a few minor typos probably. BUT, it should give you an idea of how to handle ajax requests in Django.
This is pretty 101 and taking some time to read the docs and run through some of the early projects covers a these concepts much I, so my going into more depth isn't really valuable to SO readers.
views.py
class change_date_form(forms.Form):
new_date = forms.DateField()
def change_transaction_date(request):
#Do some checks to make sure user is authorized to do this
current_user = ...
customer_data = []
if request.method == 'POST':
form = change_date_form(request.POST, request.FILES)
if form.is_valid():
change_date = form.cleaned_data.get('new_date')
objects = Transaction.objects.filter(user=current_user, date__month=change_date)
for i in objects:
customer_data.append(objects.name)
response_data = json.dumps(customer_data)
return HttpResponse(response_data, content_type='application/json')
urls.py
...
url(r'^change_date_view/', 'module.views.change_transaction_date'),
Jquery:
$('button_handler').on('click', function() {
var new_date_value = $(date_field_selector).val()
$.ajax({
url: "/change_date_view/",
type: "POST",
data: {
new_date: button_handler,
},
success:function(data) {
//build your html via javascript with response data
}
})
})
I'm using djangorestframework (which I love) and I am attempting to POST some data from the front end to the REST view/serializer waiting to accept it.
When I log into the REST API back end (that django rest provides for users to be able to test their queries), I can submit this information, and it will successfully pass the information to the back end and save the object:
{
"user": 1,
"content": "this is some content",
"goal":
{
"competencies[]": [
32
],
"active": false,
"completed": false,
"user": 1
}
}
But, when I run the POST request, it fails, stating that:
{"goal": ["This field is required."]}
So that's interesting. It works from the back end, but not the front.
Here's my code for added help:
//the ajax request
$.ajax({
// obviates need for sameOrigin test
crossDomain: false,
//adds a CSRF header to the request if the method is unsafe (the csrfSafeMethod is in base.html)
beforeSend: function(xhr, settings) {
if (!csrfSafeMethod(settings.type)) {
xhr.setRequestHeader("X-CSRFToken", csrftoken);
}
},
//needed because we're setting data, I think.
type: "POST",
//target API url
url: '/api/goal-status/add',
data: this_instead,
//on success, reload the page, because everything worked
success: function(){
location.reload();
alert("In the goal-add click listener");
},
//we'll have to find something better to do when an error occurs. I'm still thinking through this. There will probably just have to be some standardized way of going about it.
error: function(){
alert('An error ocurred!');
}
});
And this is the view that is responding to the request:
class AddGoalStatus(generics.CreateAPIView):
serializer_class = GoalStatusSerializer
permission_classes = (
permissions.IsAuthenticated,
)
And the corresponding models:
class Goal(TimeStampedModel):
"""A personalized Goal that a user creates to achieve"""
completed = models.BooleanField(default=False)
user = models.ForeignKey(User)
competencies = models.ManyToManyField(CoreCompetency)
def __unicode__(self):
return self.user.get_full_name()
class GoalStatus(TimeStampedModel):
"""As goals go on, users will set different statuses towards them"""
content = models.TextField(max_length=2000)
goal = models.ForeignKey(Goal, related_name="goal_statuses")
def __unicode__(self):
return self.goal.user.get_full_name() + ": " + self.content
class Meta:
verbose_name_plural = "Statuses"
verbose_name = "Goal Status"
And here's the serializer for completeness:
class GoalSerializer(serializers.ModelSerializer):
competencies = serializers.PrimaryKeyRelatedField(many=True, read_only=False)
class Meta:
model = Goal
class GoalStatusSerializer(serializers.ModelSerializer):
goal = GoalSerializer()
class Meta:
model = GoalStatus
As Tom Christie says in here:
django rest framework create nested objects "Models" by POST
Django Rest Framework does not allow you to write to a nested serializer.
It looks like there is work being done on building out this functionality, but I don't know if it is done yet:
https://github.com/tomchristie/django-rest-framework/tree/writable-nested-modelserializer
In the meantime see this thread for ideas on how to work around this limitation:
https://groups.google.com/forum/#!topic/django-rest-framework/L-TknBDFzTk