How to fetch data with ajax call in Django and Wagtail - python

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.

Related

Add argument to all views without having to edit them all to include variable in Django

What I need
I'm developing a Pull Notification System to an existing Django Project. With there begin over 100+ views I'm looking to find a way to incorporate a argument(notification queryset) into all the views, as this is rendered to my base.html which I can do by passing it into a view's arguments dictionary.
Problem
I want to do this without editing all of the views as this would take a long time and would be required for all future views to include this variable.
What I've tried
Creating a template filter and pass in request.user as variable to return notification for that user. This works, however when the user selects a 'New' notification I want to send a signal back to the server to both redirect them to the notification link and change the status of viewed to 'True' (POST or AJAX). Which would required that specific view to know how to handle that particular request.
What I've considered
• Editing the very core 'View' in Django.
I've tried my best to explain the issue, but if further info is required feel free to ask.
Thanks!
models.py
class NotificationModel(models.Model):
NOTIFICATION_TYPES = {'Red flag':'Red flag'}
NOTIFICATION_TYPES = dict([(key, key) for key, value in NOTIFICATION_TYPES.items()]).items()
notification_type = models.CharField(max_length=50, choices=NOTIFICATION_TYPES, default='')
user = models.ForeignKey(User, on_delete=models.CASCADE)
created = models.DateTimeField(blank=True, null=True)
text = models.CharField(max_length=500, default='')
viewed = models.BooleanField(default=False)
views.py Example
class HomeView(TemplateView):
template_name = 'app/template.html'
def get(self, request, *args, **kwargs):
notifications = NotificationsModel.objects.filter(user=request.user)
args={'notifications':notifications}
return render(request, self.template_name, args)
def notification_handler(self, request)
if 'notification_select' in request.POST:
notification_id = request.POST['notification_id']
notification = NotificationModel.objects.get(id=notification_id)
notification.viewed = True
notification.save()
return redirect #some_url based on conditions
def post(self, request, *args, **kwargs):
notifications = self.notification_handler(request, *args, **kwargs)
if notifications:
return notifications
return self.get(self, request)
With there begin over 100+ views I'm looking to find a way to incorporate a argument(notification queryset) into all the views, as this is rendered to my base.html which I can do by passing it into a view's arguments dictionary.
You don't have to put it in the views (and actually, you shouldn't - views shouldn't have to deal with unrelated responsabilities) - you can just write a simple custom template tag to fetch and render whatever you need in your base template. That's the proper "django-esque" solution FWIW as it leaves your views totally decoupled from this feature.
Have you considered mixins ?
class NotificationMixin:
my_var = 'whatever'
class MyDjangoView(NotificationMixin,.....,View)
pass
Better, Using django builtins..
from django.views.generic import base
class NotificationMixin(base.ContextMixin):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['notification'] = 'whatever'
return context
and use this mixin with all your views AS THE FIRST CLASS INHERITED, See this.
For your case, Typing it instead of applying it to the base class is better, The base view class is not intended to be altered, It's meant to be extended, That's why we can solve the issue by this...
from django.generic.views import View as DjangoBaseView
from .mixins import NotificationMixin
class View(NotificationMixin, DjangoBaseView):
pass
and use any IDE to change all the imported Views to your View and not the django one.

Wagtail route still returns 404

I encountered a bit weird problem and not really sure why it happens.
This is my model:
class PostIndexPage(RoutablePage):
max_count = 1
parent_page_types = []
intro = RichTextField(blank=True)
content_panels = Page.content_panels + [
FieldPanel('intro', classname="full")
]
#route(r'^posts/$')
def main(self, request):
from django.shortcuts import render
return render(request, self.get_template(request), self.get_context(request))
I defined route in that model but it seems to have no effect.
http://127.0.0.1:8000/post-index-page/ - the old url is still working the old way
http://127.0.0.1:8000/posts/ - but this one doesn't (404, not found).
Do you have any idea what I'm doing wrong?
URL routes defined by #route on a RoutablePage are relative to the normal path of the page, as determined by its slug and position in the page tree. If you created a routable page with the slug post-index-page, the posts route will be found at /post-index-page/posts/.
If you want a view that remains at a completely fixed URL, you can always define that as a standard Django view.

Sending model updates from db to clients with WSGI

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.

Embedding a Form Builder page in another Wagtail page

The Wagtail Form Builder documentation states:
form_page.html differs from a standard Wagtail template in that it is
passed a variable form, containing a Django Form object, in addition
to the usual page variable.
But in my current project, I need to embed a contact form (implemented as a Form Builder page) in another page. In the model for that target page, I'm using a PageChooserPanel to let an editor select the form page to embed. In other words:
class TargetPage(Page):
[...snip...]
contact_form = models.ForeignKey(
'wagtailcore.Page',
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name='+',
)
content_panels = Page.content_panels + [
[...snip...]
PageChooserPanel('contact_form', 'FormPage'),
]
My problem is in the template for the target page. I can access the attributes of the form page via page.contact_form, but since this page doesn't have a form object passed in, I can't figure out how to render my fields.
I am guessing that I need to override my target page's get_context() so that it includes the form object I need. But I can't figure out how to get that object. Can some kind soul put me on the right track?
After a night's sleep, the answer turned out to be relatively obvious. The missing link is the get_form() method of a Wagtail FormPage. I'm now using this in my TargetPage model:
def attached_form(self):
return self.contact_form.specific.get_form()
And thus, in my template, I can refer to attached_form to get my fields.

How to POST/PUT scraped data to RESTful API

I have a scraper that outputs JSON. I would like to programmatically read this output (e.g. on a daily basis) and deserialize it into my Django model, through a RESTful API like Tastypie. I would like to check for any duplicate entries / validate data before updating the model.
What is the best practice and most seamless way to do this?
--
JSON Output From Scraper (returns structured data)
Note: exchange_id is the foreign key of the Exchange object in my Django model
{
"website": "http://www.igg.com/",
"exchange_id": 1,
"ticker": "8002",
"full_name": "IGG Inc"
}
Django model
class Company (models.Model):
ticker = models.CharField(max_length=10, null=True)
full_name = models.CharField(max_length=200, null=True)
exchange = models.ForeignKey(Exchange, null=True)
website = models.URLField(null=True)
def __unicode__(self):
return self.ticker
def website_url(self):
if self.website:
return '%s' % (self.website, self.website)
else:
return ''
website_url.allow_tags = True
class Meta:
verbose_name_plural = "Companies"
I am going to assume that your app is private, and only you will have access to it. What you can do is implement django-restless with a model form.
from restless.http import Http201, Http400
from restless.views import Endpoint
from .forms import NewCompanyForm
class APIEndpoint(Endpoint):
"""
Endpoint for posting json data to server
"""
def post(self, request):
company_form = NewCompanyForm(request.data)
if company_form.is_valid():
# Check for duplicate data
# ...
if unique:
company_form.save()
return Http201({"message": "Post successful"})
else:
return Http400(reason='Data was not unique')
else:
return Http400(reason='You did not post a valid input')
Also, here is an example app using this library, https://github.com/dobarkod/django-restless/blob/master/testproject/testapp/views.py
As far as I understand, you need some tool to post/put data to your Service via RestAPI. Look at slumber. It very simple and very good interacting with tastypie.

Categories