The following description assumes that you are familiar with Python and understand the basic principles of building web applications.
server.py
file): from wsgiref.simple_server import make_server from pyramid.config import Configurator def create_app(): config = Configurator() app = config.make_wsgi_app() return app if __name__ == '__main__': app = create_app() server = make_server('0.0.0.0', 8080, app) server.serve_forever()
For launching web applications, a simple minimalistic HTTP server with WSGI support is used - the wsgiref.simple_server module from the standard Python library. It is quite enough for our web application, both for development and use in a local network. At least for now. At the end of the article it will be shown how to prepare the Pyramid application for deployment on the server - in combat we will use another server.
def create_app(): config = Configurator() path = os.path.abspath(__file__) root = path[:path.rindex("/")] config.add_static_view("css", "{0}/css".format(root)) config.add_static_view("js", "{0}/js".format(root)) config.add_static_view("img", "{0}/img".format(root)) app = config.make_wsgi_app() return app
/css/*
, /js/*
, /img/*
will return files from the corresponding directories.To each request coming to the server, Pyramid must associate a view-function that will process it. If the function cannot be found, the client will return a response with an error status. Pyramid has several ways to register view functions. To register static handlers, we use the imperative approach - when we call add_static_view , an object of the pyramid.static.static_view class is created and registered, which will process requests to URLs starting with the prefix passed in the first parameter.
config = Configurator() config.add_route("index_route","/")
The route is determined by the URL pattern (second parameter). The first parameter is the name of the route. If the requested URL falls under the route pattern, the view request processing function associated with the route is called.
Mapping by route is one way to bind view functions to queries. There is also a traversal - a search engine for view functions based on a resource tree. In this web application, only the first approach is used. You can read more about traversal in the documentation .
views.py
view functions views.py
, add the index_route
route handler code to it: @view_config(route_name="index_route") def index(request): return render_to_response('pt/index.pt', { 'name' : 'world' }, request)
pt/index.html
template in the pt
folder: <!doctype html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:tal="http://xml.zope.org/namespaces/tal" xmlns:metal="http://xml.zope.org/namespaces/metal"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>wishbar</title> </head> <body> Hello, ${name}! </body> </html>
render_to_response
generates an HTML page for the requestrequest
for thept/index.pt
template specified in the first parameter, substituting data from the dictionary in the second parameter.
create_app
“scan” views.py
and register all the handlers described in it. config = Configurator() config.add_route("index_route","/") config.scan("views")
pt/base.pt
and a template for each of their three pages - pt/login.pt
, pt/index.pt
(change the “hello world”), pt/confirm.pt
. To use the base.pt
template as a base template, create a file subscribers.py
next to the server.py
file with the following contents: from pyramid.renderers import get_renderer from pyramid.events import BeforeRender, subscriber @subscriber(BeforeRender) def add_base_template(event): base = get_renderer('pt/base.pt').implementation() event.update({'base': base})
subscribers.py
in the web application in the create_app
function. config = Configurator() config.add_route("index_route","/") config.scan("subscribers")
@subscriber defines theadd_base_template
function as a handler for internal Pyramid events of typeBeforeRender
. The BeforeRender event handler is called immediately before the template is rendered. In the handler, we can change the set of renderer globals — the named values ​​available in the templates. In particular, we add the renderer of the base templatebase.pt
to this set under the namebase
.
pt/base.pt
declare the necessary expansion slots: <div id="content"> <tal:block metal:define-slot="content"> </tal:block> </div>
login.pt
, index.pt
and confirm.pt
you can “inherit” from the base template: <html xmlns="http://www.w3.org/1999/xhtml" xmlns:tal="http://xml.zope.org/namespaces/tal" xmlns:metal="http://xml.zope.org/namespaces/metal" metal:use-macro="base"> <tal:block metal:fill-slot="content"> Hello, ${name}! </tal:block> </html>
if 'username' in request.cookies: pass else: return render_to_response('pt/login.pt', {}, request)
/login/
in the form of a POST request. Add a route and a handler. In the handler, save the girl's name in the form of a cookie with the username
key and send 302 Found to the root of the web application. config.add_route("index_route","/")
@view_config(route_name="login_route") def login(request): username = request.params['username'] response = Response() response.set_cookie('username', value=username, max_age=86400) return HTTPFound(location = "/", headers=response.headers)
As in any web application, redirection is necessary so that when the page is refreshed, the browser does not offer to re-send a POST request with the form data. This happens if you return the HTML page in response to a POST request.
index_route
route, index_route
can display a list of "desires". Encode the wish list in the form of a list in Python and transfer it to the template along with the girl's name. if 'username' in request.cookies: username = request.cookies['username'] response = render_to_response("pt/index.pt", { "username" : username, "wishbar" : WISHBAR }, request) return response
<div class="table"> <tal:block repeat="wish wishbar"> <label for="${wish.name}"> <div><input id="${wish.name}" name="wish-${wish.name}" type="checkbox"></input><div class="checkbox"></div></div> <div> <div class="title">${wish.title}</div> <div class="description">${wish.description}</div> </div> </label> </tal:block> </div>
For styling flags, a method was used with input substitution to div and their binding through JavaScript. It would be correct to add preloading images when loading the page in accordance with the recommendations of the author of the article, but I did not. Since the application was deployed on the local network, the girls did not even notice a delay in downloading images.
/confirm/id-
. if request.method == "POST": username = request.cookies['username'] wishlist = [] for key,value in request.POST.items(): if key.startswith("wish-"): wishlist.append(NAME2WISH[key[5:]]) special = request.params["special"] bequick = request.params["bequick"] order = Order(username,request.remote_addr,wishlist,special,bequick) ORDERS[order.id] = order return HTTPFound(location = "/confirm/{}".format(order.id))
/confirm/*
handler. config.add_route("confirm_route","/confirm/{order}")
@view_config(route_name="confirm_route") def confirm(request): order_id = request.matchdict['order'] if order_id in ORDERS.iterkeys(): order = ORDERS.pop(order_id) notify(order) return render_to_response('pt/confirm.pt', { "order" : order }, request) else: return HTTPFound(location = "/")
notify
function notify
intended to notify the waiters about the order. About her a little later. For now, let's finish with the web application. It remains a bit: it is necessary to allow the user to "log out" and "log in" under a different name. To do this, on the wish selection page there is a link to the URL /logout/
. Register the corresponding view logout
function. It is enough to clear the cookie with the username and redirect it to the main one. config.add_route("logout_route","/logout/")
@view_config(route_name="logout_route") def logout(request): response = Response() response.set_cookie('username', value=None) return HTTPFound(location = "/", headers=response.headers)
notify
the notify.py
implementation in notify.py
. USERNAME = 'username' # @gmail.com PASSWORD = 'password' class User(): def __init__(self,uri,name): self.uri = uri self.name= name USERS = { "user@gmail.com" : User("user@gmail.com",u" ") } def notify(order): cnx = xmpp.Client('gmail.com') cnx.connect( server=('talk.google.com',5223) ) cnx.auth(USERNAME,PASSWORD,'botty') message = order.username + " (" + order.remote_addr + "): " if order.donothing: message += u" " else: message += order.wishstr if order.bequick: message += u", -" for user in USERS.itervalues(): cnx.send(xmpp.Message(user.uri, message, typ='chat'))
notify
sends a message with order information to all users in the USERS
list.notify.py
. The bot was launched when the application started in a separate thread, processed incoming messages and sent them to the USERS
list.USERS
list there is only a room account. Now, when someone left the order, the message was sent to the room where everyone saw it and could immediately agree on processing.I intend to bring this information to the end of the article. Firstly, in order to start directly with the task and the code and not to confuse the reader. Second, bring your project to standard Pyramid easily and quickly. What I did.
pcreate
utility automatically generates a typical project structure and creates a setup.py
for a new web application, in which all dependencies are already written. You must go to the level above our project and run in the console pcreate -s starter wishbar
.pcreate
offers other scaffolds for web applications. For example, alchemy - creates a web application using sqlalchemy .
wishbar
package. Which is correct, the modules should be in packages. In my case, the files were in the root of the project. The transfer was not difficult - the extra generated code was removed, the missing import
directives were added for dependencies between modules, the create_app
call from server.py
to __init__.py
. cd ~ mkdir wishbar cd wishbar git init git remote add origin "https://github.com/rgmih/wishbar.git" git pull origin master sudo python ./setup.py develop pserve production.ini
With this deployment method, the web application runs under the Waitress WSGI server on port 7777.
Source: https://habr.com/ru/post/172243/
All Articles