ReactJS server side rendering fo single page application - python

What I've already done
I have a frontend entirely built with React.JS.
All the business logic is handled by Django and exposed by Django REST Framework via an API.
I'm able to build for different mobile environnements (Android and iOS via Cordova)
The web application is accessible via my Django project (the same that exposes the api), the frontend is sill the same ReactJS code bundled via webpack.
The App has a single entry point, main.js which is a bundled version of my react.js components and dependencies, so my index.html typically looks like this :
<body>
<script type="text/javascript" src="/static/bundles/main-3997ad3476694c3c91cf.js"></script>
</body>
What I want to do
I want to provide a server-side rendering of my web application to let web crawlers correctly index my app on web (I'm not looking for server-side rendering for mobile builds)
How can I handle this considering the fact that my app is a Single Page Application ? I do not want to reinvent the wheel nor to duplicate my code. What kind of node.js server do I have to write in order to achieve this automatic server-side rendering ? Is there any way to provide the server side rendering directly in Django (via some tools reading and interpreting the final results of the page as displayed on the client-side and returning this raw html ?)

You have probably solved your problem by now, but I wanted to share my solution for this.
I have a very similar setup, and have something that seems to work pretty well so far. I basically have a django w/ DRF backend api and isomorphic React/Flux javascript app. I also run a node server next to the python backend server, which acts only as a 'template rendering' service. In essence, replacing the django render function.
So I simply replace the django View with a special IsoView which calls off via http to the node server and returns the rendered html.
from rest_framework.renderers import JSONRenderer
import requests
class IsoView(View):
def user_json(self):
if self.request.user.is_anonymous():
return {'anonymous': True}
else:
return UserSerializer(self.request.user, context={'request': self.request}).data
#classmethod
def render(cls, request, template, data):
req_data = JSONRenderer().render(data)
try:
headers = {'content-type': 'application/json'}
query_params = request.GET
resp = requests.post(_build_url(request.path), params=query_params, data=req_data, headers=headers, timeout=0.1)
reply = resp.json()
if resp.status_code == 302:
return redirect(reply.get('redirect'))
if 'error' in reply:
raise Exception("\n\nRemote traceback: {}".format(reply.get('traceback')))
except requests.exceptions.RequestException as err:
logger.warn('IsoView request exception: {}'.format(err))
reply = {}
return render(request, template, {
'react': reply.get('result'),
'data': data
})
And use it like so:
class HomePage(IsoView):
def get(self, request, *args, **kwargs):
return self.render(request, 'app.html', {
'user': json_data...
})
This also assumes a django template which uses something like this
<html>
<head>
<script>
window.data = {{ data|json }};
</script>
</head>
<body>{{ react|safe }}</body>
</html>
What this does is it renders the html returned from node in the body tag and also dumps the json data required for bootstrapping the app on the client in the window.data object.
This is a really simplified version of the system, but it should work. You should be careful with XSS attacks on the window.data bit, so make sure to escape all your json data but other than that, you should be all good.
Then the node template server looks really similar to any of the tutorials online that you can find for server-side react. Just a simple express app.
Alternatively, you don't need to mess around with django templates at all if you render the full ... in node and return that as a string.
Hope that helps.

Related

How to retrieve a json without the use of database?

I want to scrape a website using python and send as json the information that I got to my flutter application without saving it to the database.
I don't want to use the Django Restframework database. I want to send a string to the backend and trigger a function that scrapes a certain website and sends back a json to the client. Without using the database.
For simple usage, you can setup a view function, bind it to a url that will accept your string, and inside the function build the logic to scrape the third-party page and return a JsonResponse
Something like below:
in urls.py:
urlpatterns = [
path('my_scrape/<str:input_string>', views.scrape)
]
in views.py:
def scrape(request, input_string):
#scrape a website
url = 'http://google.com/{}'.format(input_string)
third_party_data = BeautifulSoup(requests.get(url).content)
my_payload = { 'data1' : third_party_data.find(text='data1') }
return JsonResponse(my_payload)
Note that this doesn't account for anything like errors and timeouts from the third-party, you will have to handle those errors in your view function, depending on what you receive.

How do I emit the HTML of an external website in a Django template?

I want to get the contents of one of my pages on an external site, the HTML and all contents. If were in .NET I could use WebClient and retrieve the page, save it to a variable, and emit it in Razor. PHP can use cURL.
How can I do that in a Django template? Do I need to create a Plugin that uses urllib?
If it matters, I am using DjangoCMS. I searched and tried the Django http module.
I have looked at the helper Django module for http and didn't see anything. I wasn't sure where to put urllib or requests, as in I do not know if what I am trying to do requires me to build a plugin or a custom template tag.
I was hoping to know how to do it with either Python and Django's template language or just the template language, so I really do not want to do this in JavaScript, iframe, object, etc.
You can use the reqeusts library, and define a function as a part of a view.
Alternatively you can define a simple tag if you want it to be globally accessible.
For instance
import requests
from django import template
register = template.Library()
#register.simple_tag
def get_site_source(url):
res = requests.get(url)
return res.text
and in the template:
...
{% get_site_source url %}
...

How should an index.html file be served from Python Pyramid for an AngularJS app?

I am getting into single-page apps with AngularJS but rather than using Node or similar, I am most comfortable with Python on the server. So, given I am somewhat familiar with Pyramid, I plan to use the pyramid_rpc module to return JSON objects to the client app. That's all straight forward enough, however, what then is the best way to serve the starting index file which contains the AngularJS initial AngularJS app? Usually, static files are served from a static directory, but is there any problem serving a index.html file from the root? Or should I use a view-callable and '/' route with a renderer to a html template? All that being said, is Pyramid overkill for this kind of application? Any advice would be great.
If you're planning to return some JSON responses then Pyramid is a great option. But I wouldn't recommend using pyramid_rpc. JSON-RPC is a protocol that is intended for RPC communication between servers. Straight json responses fit most clients (like browsers) better such as just a bunch of routes that return JSON responses in response to GET/POST requests. This is also a good place to serve up index.html, probably with a good http_cache parameter to prevent clients from requesting that page too often (of course you can go further with optimizing this route, but you should probably save that for later).
config.add_route('index', '/')
config.add_route('api.users', '/api/users')
config.add_route('api.user_by_id', '/api/users/{userid}')
#view_config(route_name='index', renderer='myapp:templates/index.html', http_cache=3600*24*365)
def index_view(request):
return {}
#view_config(route_name='api.users', request_method='POST', renderer='json')
def create_user_view(request):
# create a user via the request.POST parameters
return {
'userid': user.id,
}
#view_config(route_name='api', request_method='GET', renderer='json')
def user_info_view(request):
userid = request.matchdict['userid']
# lookup user
return {
'name': user.name,
}

Flask session not persisting

I have a web application running on a Flask backend with a JS client handling the front-end work. I'm running into problems trying to save a key-value pair to Flask's session object (flask.session) through a simple Flask API.
The session object I'm trying to modify is called account_id and the two API routes basically look like this:
GET
#access_service.route('/current_account.json', methods=['GET'])
#login_required
def show_current_account():
return jsonify(account_id=session.get('account_id'))
POST
#access_service.route('/current_account.json', methods=['POST'])
#login_required
def update_current_account():
if request.json:
session['account_id'] = request.json['account_id']
return jsonify(account_id=session.get('account_id'))
return jsonify()
In the JS frontend a call to the POST route is made as follows:
$.ajax({
url: '/current_account.json',
contentType: 'application/json',
type: 'POST',
data: JSON.stringify({ 'account_id': 10 })
});
Which does appear to work correctly, the ajax call returns with 200 OK and a correct return value. Logging from the Flask application also reveals that the session now contains the key account_id with value 10. However, looking up /current_account.json immediately after the POST request is made simply returns an account_id with value null.
What's stranger still is that using a simple in-browser REST client and making an identical POST request to current_account.json causes the session to work and persist as expected through full-page refresh, etc. Since that is the case, it leads me to believe that the problem has to do with the request itself rather than with Flask's session object, although I can't seem to figure out what exactly is causing it.
In my previous project, we experienced the same issue and it turns out that $.ajax does not carry cookies. We used manual session store to remedy the problem.

Locating django app resources

tl:dr
How would a hosted django app correctly transform resource paths to match any hosted location (/ or /test or /testtest)?
Full Description
Let me try to explain what I am trying to do.
I am trying to write a somewhat re-usable django app which I intend to use from within multiple projects. This app is called systemstatus.
The systemstatus app provides a page under '$^' which provides a simple interface to query the system status.
This page makes an ajax query back to the systemstatus app to determine the actual system status and report it on the UI.
The systemstatus app provides a location '^service/$' which points to the ajax call handler.
This page has to somehow figure out the correct URI for the ajax handler depending on where this app is hosted (e.g. under / or /status or /blahblah).
I am wondering what an ideal way of doing this would be. I would say that this applies to other resources bundled inside the app too (stylesheets, images).
Right now I am using request.path to determine what the target path should be. This path is then passed down as a parameter to the template. But this approach will soon become too cumbersome to handle.
def system_status (request):
queryPath = request.path + "service/"
return render_to_response ('systemstatus.html', {'queryPath': queryPath})
My page template looks like this:
function do_ajax () {
$.getJSON ('{{ queryPath }}', function (data) {
$("#status").html (data.status);
});
}
Thanks!
You shouldn't hardcode your urls like that, but use reverse instead!
Django also has a built-in template tag to reverse urls. So you could do something like
function do_ajax () {
$.getJSON ('{% url path.to.my_ajax_view %}', function (data) {
$("#status").html (data.status);
});
}
directly in your template!
You can also send the ajax request directly to your current page's url and check if it is an ajax request or not:
def my_view(request):
if request.is_ajax():
# generate response for your ajax script
else:
# generate the response for normal request
# (render template of your page)

Categories

Resources