- Make sure you have docker already installed.
- Install the Nginx proxy with
docker-gen
sudo docker run --name=Nginx -d \ --restart=always \ -p 80:80 -p 443:443 \ -v /data/certs:/etc/nginx/certs:ro \ -v /var/run/docker.sock:/tmp/docker.sock:ro \ -v /data/Nginx/vhost.d:/etc/nginx/vhost.d \ -v /data/Nginx/html:/usr/share/nginx/html \ --label com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy \ jwilder/nginx-proxy
- Since I run portainer, start it up with the
VIRTUAL_HOSTandVIRTUAL_PORTenvironment variables so that docker-gen can pick it up. You can do this with any app you desire.
sudo docker run --name Portainer -d \ --restart=always \ -p 9000:9000 \ -v /var/run/docker.sock:/var/run/docker.sock \ -v portainer_data:/data \ -e VIRTUAL_HOST=portainer.local.network \ -e VIRTUAL_PORT=9000 \ portainer/portainer
- Now to use the Let's encrypt container to make certificates for our docker containers:
sudo docker run --name=Letsencrypt -d \ --restart=always \ -v /data/certs:/etc/nginx/certs:rw \ -v /var/run/docker.sock:/var/run/docker.sock:ro \ --volumes-from Nginx \ jrcs/letsencrypt-nginx-proxy-companion
- To enable SSL for your site, set the environment variables
VIRTUAL_PROTO=https,VIRTUAL_PORT=433environment as well as theLETSENCRYPT_HOSTandLETSENCRYPT_EMAILvariables so that docker-gen can pick it up. You can do this with any app you desire. You will also need to mount the certificates and keys within the SSL folder of the container for the container to use the LetsEncrypt keys.
This blog is a knowledge dump of all the technical information floating around in my head. It deals with anything involving software, hardware, gadgets, and technology.
Showing posts with label webapp. Show all posts
Showing posts with label webapp. Show all posts
Mar 2, 2018
Automatically make web apps use HTTPS with Let's Encrypt, Nginx, and Docker
Oct 8, 2013
Laravel 4: Setting up and basics
This guide will go through one method of setting up a basic Laravel 4 environment. There are quick-start instructions, however in this guide we are not going to rely on a pre-installed global Composer binary.
These instructions require that you have git installed.
These instructions require that you have git installed.
Installation
- Clone the laravel framework repository. This contains a basic layout for an application, although composer will be required to install all the dependencies.
git clone https://github.com/laravel/laravel.git - Rename the directory to one more suited for your project
mv laravel/ [project] - Change the permissions and owner of your project
chown -R [user]:[group] [project] - Go into your project directory
cd [project] - Download a copy of composer for your project
curl -sS https://getcomposer.org/installer | php - Since composer will now handle all dependancies, we no longer need git to mirror the laravel repository. Remove it:
git remote -vgit remote rm origin
If you wish to keep the repository around, you should just rename it:
git remote rename origin laravel - Edit your composer dependencies, which are stored in composer.json. If nothing else, edit the name, description, keywords, and license fields to suit your project.
- Ensure your composer.json is valid by running:
php composer.phar validate - Install all dependancies via composer
php composer.phar install - Run the in-built PHP development server to check that everything is working correctly
php artisan server
You should now be able to view your new application through http://localhost:8000/. Use the --help flag to see more configuration options. - If you make changes to your composer dependancies, just run:
php composer.phar update
Netbeans and Laravel set-up
- Follow the above instructions to create a new instance of Laravel
- Start a new project by clicking on File -> New Project
- Select PHP -> PHP Application with existing sources
- Select the sources folder as the project directory you created earlier
- Set the 'Run As' configuration as PHP Built-in Web Server with the router script set to public/index.php. Note that other files (such as css and js files) stored in the public directory will not be served.
Eclipse and Laravel set-up
- Ensure you have installed the PHP development extensions
- Select File -> New Project -> Project
- Select PHP -> PHP Project
- Enter in a project name and choose the existing source folder. You can finish the settings now, or configure the project further.
- I could not get the Eclipse PHP server to work, so you will have to figure that one our for yourself.
Composer overview
- The command php composer.phar will list all the available commands in composer
- The composer.json file will list dependencies and configuration defaults for the composer binary.
- The composer.lock file is generated after you run the install or update composer command. This file will store the exact version downloaded and any local configuration.
Resources
Sep 7, 2013
AJAX with JQuery and Google App Engine: Python
This is an adaption of AJAX with JQuery and PHP. It is a continuation of my Python 2.7 and Google App Engine series, and portions of the code builds upon my earlier work.
Create a file called static.html and add the following:
Create a file called app.yaml and add the following:
The above will simply serve ALL html page requests directly (which is enough for this example).
Now, if you are using Eclipse just right click on the project, select 'Run As' and choose the 'PyDev: Google App Run' option. Depending on how you configured your Google SDK, it will start up a server on your machine on port 8080. Open up a new tab and navigate to http://localhost:8080/static.html. You should now see a text box; when you type into the box it will automatically display the text in the space below.
Our JavaScript now sends some data to an external URL (in this case /tutorial) and inserts the returned data into the display div. Now all we need to do is code our Python script....
Now we just need to edit app.yaml so it can serve up our tutorial script:
Open up a new tab and navigate to http://localhost:8080/static.html. You should now see a text box with a button called 'Send'. Typing into this box and then clicking 'Send' will transmit the text to our tutorial script and then display the text in the space below.
A first example...
AJAX stands for Asynchronous JavaScript And XML, and allows us to make our websites seem more dynamic. Let's start with the JavaScript side of things...Create a file called static.html and add the following:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<title>Ajax With Jquery</title>
<!-- Grab the JQuery API from the Google servers -->
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js" type="text/javascript" charset="utf-8"></script>
<!-- Our own JQuery code to do the AJAX-y stuff -->
<script type="text/javascript" charset="utf-8">
$(document).ready(function(){
$('#txtValue').keyup(function(){
$('#display').html($(this).val());
});
});
</script>
</head>
<body>
<label for="txtValue">Enter a value : </label>
<input type="text" name="txtValue" value="" id="txtValue">
<div id="display"></div>
</body>
</html>
Create a file called app.yaml and add the following:
application: almightynassar
version: 1
runtime: python27
api_version: 1
threadsafe: yes
handlers:
- url: /(.*).html
static_files: \1.html
upload: (.*).html
The above will simply serve ALL html page requests directly (which is enough for this example).
Now, if you are using Eclipse just right click on the project, select 'Run As' and choose the 'PyDev: Google App Run' option. Depending on how you configured your Google SDK, it will start up a server on your machine on port 8080. Open up a new tab and navigate to http://localhost:8080/static.html. You should now see a text box; when you type into the box it will automatically display the text in the space below.
Something a little more AJAX - The HTML
So our original example just showed how we could manipulate the DOM (Document Object Model) of the browser during run-time, without a page refresh. How can we use this knowledge utilizing the power of Google App Engine? Well, first let us edit static.html to something we can manipulate:<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<title>Ajax With Jquery</title>
<!-- Grab the JQuery API from the Google servers -->
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js" type="text/javascript" charset="utf-8"></script>
<!-- Our own JQuery code to do the AJAX-y stuff -->
<script type="text/javascript" charset="utf-8">
$(document).ready(function(){
// This will run when the item of class 'button' is clicked
$(".button").click(function() {
// Grabs the text input
var name = $("input#txtValue").val();
var dataString = 'txtValue='+ name;
// This creates the AJAX connection
$.ajax({
type: "POST",
url: "/tutorial",
data: dataString,
success: function(data) {
$('#display').html(data.text);
}
});
return false;
});
});
</script>
</head>
<body>
<form name="contact" method="post" action="">
<label for="txtValue">Enter a value : </label>
<input type="text" name="txtValue" value="" id="txtValue">
<input type="submit" name="submit" class="button" id="submit_btn" value="Send" />
</form>
<div id="display">
</div>
</body>
</html>
Our JavaScript now sends some data to an external URL (in this case /tutorial) and inserts the returned data into the display div. Now all we need to do is code our Python script....
Our Python webapp
Create a file called tutorial.py and add the following code:
# The webapp2 framework
import webapp2
# The JSON library
import json
# Our JSON handler class
class JsonPage(webapp2.RequestHandler):
# The POST handler
def post(self):
# Our POST Input
txtinput = self.request.get('txtValue')
# Create an array
array = {'text': txtinput}
# Output the JSON
self.response.headers['Content-Type'] = 'application/json'
self.response.out.write(json.dumps(array))
# URL map the JSON function
app = webapp2.WSGIApplication([('/tutorial', JsonPage)], debug=True)
Now we just need to edit app.yaml so it can serve up our tutorial script:
application: almightynassar
version: 1
runtime: python27
api_version: 1
threadsafe: no
handlers:
- url: /(.*).html
static_files: \1.html
upload: (.*).html
- url: /(.*)
script: tutorial.app
Open up a new tab and navigate to http://localhost:8080/static.html. You should now see a text box with a button called 'Send'. Typing into this box and then clicking 'Send' will transmit the text to our tutorial script and then display the text in the space below.
References:
- Google's own getting started with webapp and Python.
- The official webapp2 reference
- The Google developer resource for GAE
- Google App Engine FAQs
- YAML reference
- app.yaml reference
- A JQuery tutorial from devirtuoso
- The JQuery AJAX API
- Refresh-less form validation from NetTutes
Nov 12, 2012
Get page width dynamically with Javascript (or JQuery)
This quick script will display the current width of your browser window (useful for creating responsive web designs).
To use this snippet, just download the JQuery library, link it into your page and add the following script:
The HTML
Simply make a <div> element, which we will be used by JQuery to print out the window width:<html>
<body>
<div id="dimensions">
<span class="width"></span>
</div>
</body>
</html>
JQuery method
If you already use JQuery, then this is for you. If you aren't using it, you should consider it; it makes programming in Javascript a breeze!To use this snippet, just download the JQuery library, link it into your page and add the following script:
<script>
$("#dimensions .width").html($(window).width());
$(window).resize(function(){
$("#dimensions .width").html($(window).width());
});
</script>
Javascript method
If you don't have JQuery this should work equally well:
<script>
window.onresize = displayWindowSize;
window.onload = displayWindowSize;
function displayWindowSize() {
// your size calculation code here
document.getElementById("dimensions").innerHTML = myWidth + "x" + myHeight;
};
</script>
Further reading
You can also browse my other content such as:Sep 11, 2012
Sending e-mail via PHP and MSMTP
Normally I would have set up a fully-fledged mailing systems like Postfix or Sendmail to handle my mailing needs. This means spending hours configuring, tweaking and securing another service running on my server. But I have found a simpler way.
MSMTP.
MSMTP.
- Install MSMTP. In Ubuntu you would do the following:
sudo apt-get install msmtp ca-certificates
- Edit the configuration file /etc/msmtprc with the following:
#set defaults
defaults
# Enable or disable TLS/SSL encryption
tls off
tls_starttls on
tls_trust_file /etc/ssl/certs/ca-certificates.crt
# account settings
account default
host mail.optusnet.com.au # CHANGE THIS!!!
port 25
auth off
from do-not-reply@domain.com.au
logfile /var/log/msmtp/msmtp.log - Edit the configuration of PHP to use MSMTP. Edit the configuration file with the following (in Ubuntu and PHP5, the file is stored in /etc/php5/cli/php.ini
sendmail_path = /usr/bin/msmtp -t
- Create the log file directory and set proper permissions (depends on how your machine is set up:
sudo mkdir /var/log/msmtp
sudo chown [user]:[group] /var/log/msmtp - Tell our system to rotate the logs so that they do not get too large by creating the file /etc/logrotate.d/msmtp:
/var/log/msmtp/*.log {
rotate 12
monthly
compress
missingok
notifempty
} - You can now test it with the following PHP script:
<?php
$to = "your-email@domain.com";
$subject = "Test mail";
$message = "Hello! This is a simple email message.";
if (mail($to,$subject,$message)) {
echo "Mail Sent.";
} else {
echo "NOT SENT.";
}
References:
Sep 1, 2012
CodeIgniter web app authentication with PostgreSQL and TankAuth
Some of you may note that I had earlier created my own simple authentication system in CodeIgniter. However there comes a time when you just need something fully featured by you don't have the time create it yourself; step into TankAuth (it is also a hosted project on GitHub).
This post will focus on how to get TankAuth working with PostgreSQL .
I have a series on PostgreSQL that include an installation guide for Ubuntu, useful functions and operators, a guide to improving Postgre performance, how to check for installed procedural languages, and an overview of some basic concepts and some more advanced ones.
You can also check out my notes on designing relational databases. As for PHP, you can check out my blog posts such as my AJAX with JQuery and PHP tricks. You can also check out this list of useful CodeIgniter Tutorials.
Use this script to check if you have correctly set-up PostgreSQL and PHP.
Open up a terminal and create a user and database for CodeIgniter and TankAuth like we did in this earlier post:
Once we have created a user and a database, connect to the database server via the terminal command:
Now create a schema for our project to use. In the psql terminal run the following:
PostgreSQL is now prepped and ready for CodeIgniter...
Now we will autoload the database (just because it is easier than calling it for every controller); edit ./application/config/autoload.php with:
And that's it! CodeIgniter will now connect to PostgreSQL.
This will create a table for recording login attempts AND a function and trigger for automatically updating the timestamp:
This will create a table for tracking users who choose to use the autologin feature (and the requisite triggers):
This will create our user profiles:
And that is TankAuth set up!!!
with:
This post will focus on how to get TankAuth working with PostgreSQL .
Some problems....
TankAuth was designed around a MySQL database, and hence some SQL commands won't work. This means that you will get some odd behavior; unfortunately fixing all of these issues is outside the scope of this post.Some Prerequisite knowledge...
I am going to dive right into the deep end here so if you don't know anything about PostgreSQL, PHP, CodeIgniter or Relational Database theory I strongly suggest you go and do some quick research.I have a series on PostgreSQL that include an installation guide for Ubuntu, useful functions and operators, a guide to improving Postgre performance, how to check for installed procedural languages, and an overview of some basic concepts and some more advanced ones.
You can also check out my notes on designing relational databases. As for PHP, you can check out my blog posts such as my AJAX with JQuery and PHP tricks. You can also check out this list of useful CodeIgniter Tutorials.
Setting up PostgreSQL
We are going to assume that you have already installed PostgreSQL and have correctly download and setup CodeIgniter (and it is correctly serving the default web page). Make sure you have installed the PHP PostgreSQL plug-ins (php5-pgsql and php5-odbc).Use this script to check if you have correctly set-up PostgreSQL and PHP.
Open up a terminal and create a user and database for CodeIgniter and TankAuth like we did in this earlier post:
NOTE: These commands assume that you have password protected the postgres user; if you have a default install you can ditch the -W trigger.createuser -h localhost -U postgres tankauth -W -S -D -R -Pcreatedb -h localhost -U postgres -O tankauth -W tankauthdb
Once we have created a user and a database, connect to the database server via the terminal command:
psql -h localhost -d tankauthdb -U tankauth -W
Now create a schema for our project to use. In the psql terminal run the following:
tankauthdb=> CREATE SCHEMA codeigniter;
PostgreSQL is now prepped and ready for CodeIgniter...
Setting up CodeIgniter
To get CodeIgniter working with PostgreSQL you will simply need to edit two configuration files. First, edit ./application/config/database.php with the following:$db['default']['hostname'] = 'localhost';
$db['default']['username'] = 'tankauth';
$db['default']['password'] = 'tankauth';
$db['default']['database'] = 'tankauthdb';
$db['default']['dbdriver'] = 'postgre';
$db['default']['dbprefix'] = 'codeigniter.';
Now we will autoload the database (just because it is easier than calling it for every controller); edit ./application/config/autoload.php with:
$autoload['libraries'] = array('view', 'database');
And that's it! CodeIgniter will now connect to PostgreSQL.
Setting up TankAuth
- Download and extract TankAuth.
- Copy the application folder content to your CI application folder.
- Copy the captcha folder to your CI folder. Make sure this folder is writable by web server.
- Open the application/config/config.php file in your CI installation and change $config['sess_use_database'] value to TRUE.
- Create an encryption key in application/config/config.php and editing $config['encryption_key']
- Finally turn off captcha's in by editing application/config/tank_auth.php with $config['captcha_registration'] = FALSE;
CREATE TABLE codeigniter.ci_sessions
(
session_id character varying(40) COLLATE pg_catalog."en_AU.utf8" NOT NULL DEFAULT '0',
ip_address character varying(16) COLLATE pg_catalog."en_AU.utf8" NOT NULL DEFAULT '0',
user_agent character varying(150) COLLATE pg_catalog."en_AU.utf8" NOT NULL,
last_activity integer NOT NULL DEFAULT 0,
user_data text COLLATE pg_catalog."en_AU.utf8" NOT NULL,
CONSTRAINT ci_sessions_pkey PRIMARY KEY (session_id )
)
WITH (
OIDS=FALSE
);
ALTER TABLE codeigniter.ci_sessions
OWNER TO tankauth;
This will create a table for recording login attempts AND a function and trigger for automatically updating the timestamp:
CREATE TABLE codeigniter.login_attempts
(
id serial NOT NULL,
ip_address character varying(40) COLLATE pg_catalog."en_AU.utf8" NOT NULL,
login character varying(50) COLLATE pg_catalog."en_AU.utf8" NOT NULL,
"time" timestamp without time zone NOT NULL DEFAULT now(),
CONSTRAINT login_attempts_pkey PRIMARY KEY (id )
)
WITH (
OIDS=FALSE
);
ALTER TABLE codeigniter.login_attempts
OWNER TO tankauth;
CREATE OR REPLACE FUNCTION codeigniter.update_modified_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.time = now();
RETURN NEW;
END;
$$ language 'plpgsql';
CREATE TRIGGER update_login_attempts_modtime BEFORE UPDATE ON codeigniter.login_attempts FOR EACH ROW EXECUTE PROCEDURE codeigniter.update_modified_column();
This will create a table for tracking users who choose to use the autologin feature (and the requisite triggers):
CREATE TABLE codeigniter.user_autologin
(
key_id character(32) COLLATE pg_catalog."en_AU.utf8" NOT NULL,
user_id integer NOT NULL DEFAULT 0,
user_agent character varying(150) COLLATE pg_catalog."en_AU.utf8" NOT NULL,
last_ip character varying(40) COLLATE pg_catalog."en_AU.utf8" NOT NULL,
last_login timestamp without time zone NOT NULL DEFAULT now(),
CONSTRAINT user_autologin_pkey PRIMARY KEY (key_id, user_id )
)
WITH (
OIDS=FALSE
);
ALTER TABLE codeigniter.user_autologin
OWNER TO tankauth;
CREATE OR REPLACE FUNCTION codeigniter.update_modified_login()
RETURNS TRIGGER AS $$
BEGIN
NEW.last_login = now();
RETURN NEW;
END;
$$ language 'plpgsql';
CREATE TRIGGER update_autologin_modtime BEFORE UPDATE ON codeigniter.user_autologin FOR EACH ROW EXECUTE PROCEDURE codeigniter.update_modified_login();
This will create our user profiles:
CREATE TABLE codeigniter.user_profilesAnd finally this is our users login table:
(
id serial NOT NULL,
user_id integer NOT NULL,
country character varying(20) COLLATE pg_catalog."en_AU.utf8" DEFAULT NULL,
website character varying(255) COLLATE pg_catalog."en_AU.utf8" DEFAULT NULL,
CONSTRAINT user_profiles_pkey PRIMARY KEY (id )
)
WITH (
OIDS=FALSE
);
ALTER TABLE codeigniter.user_profiles
OWNER TO tankauth;
CREATE TABLE codeigniter.users
(
id serial NOT NULL,
username character varying(50) COLLATE pg_catalog."en_AU.utf8" NOT NULL,
password character varying(255) COLLATE pg_catalog."en_AU.utf8" NOT NULL,
email character varying(100) COLLATE pg_catalog."en_AU.utf8" NOT NULL,
activated smallint NOT NULL DEFAULT 1,
banned smallint NOT NULL DEFAULT 1,
ban_reason character varying(255) COLLATE pg_catalog."en_AU.utf8" DEFAULT NULL,
new_password_key character varying(50) COLLATE pg_catalog."en_AU.utf8" DEFAULT NULL,
new_password_requested timestamp without time zone DEFAULT NULL,
new_email character varying(100) COLLATE pg_catalog."en_AU.utf8" DEFAULT NULL,
new_email_key character varying(50) COLLATE pg_catalog."en_AU.utf8" DEFAULT NULL,
last_ip character varying(40) COLLATE pg_catalog."en_AU.utf8" NOT NULL,
last_login timestamp without time zone NOT NULL DEFAULT now(),
created timestamp without time zone NOT NULL DEFAULT now(),
modified timestamp without time zone NOT NULL DEFAULT now(),
CONSTRAINT user_pkey PRIMARY KEY (id )
)
WITH (
OIDS=FALSE
);
ALTER TABLE codeigniter.users
OWNER TO tankauth;
CREATE TRIGGER update_users_login BEFORE UPDATE ON codeigniter.users FOR EACH ROW EXECUTE PROCEDURE codeigniter.update_modified_login();
CREATE OR REPLACE FUNCTION codeigniter.update_modified()
RETURNS TRIGGER AS $$
BEGIN
NEW.modified = now();
RETURN NEW;
END;
$$ language 'plpgsql';
CREATE TRIGGER update_users_modtime BEFORE UPDATE ON codeigniter.users FOR EACH ROW EXECUTE PROCEDURE codeigniter.update_modified();
And that is TankAuth set up!!!
One more thing...
For those keeping track, they may have noticed my warning about TankAuth using MySQL specific functions. To get the basic stuff working, you will need to do edit application/models/tank_auth/login_attempts.php and replace:$this->db->or_where('UNIX_TIMESTAMP(time) <', time() - $expire_period);
with:
$this->db->or_where('extract(epoch FROM time) <', time() - $expire_period);And you are good to go!
Labels:
API,
authentication,
codeigniter,
database,
postgresql,
webapp
Location:
Sydney NSW, Australia
Aug 31, 2012
HTML5 and CSS3 drop-down menu
Don't forget to visit more of CSS3 tips and tricks!
The HTML5
All you will need is a simple menu structure using <nav> and nested <ul> tags, like this:<nav>
<ul>
<li><a href="/">Home</a>
<ul>
<li><a href="/about">About</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</li>
<li><a href="/service">Services</a>
<ul>
<li><a href="/service/transport">Transport</a></li>
</ul>
</li>
<li><a href='/welcome/language'>Change Language</a></li>
</ul>
</nav>
The CSS3
I am just going to comment the CSS code; hopefully that is enough explanation for you.../********************* Nav elements ****************************/
/* Navigation menu HTML5 tag*/
nav
{
/* No border*/
border:none;
border:0px;
/* Ensures the text is aligned properly*/
text-align: left;
/* Margins and padding*/
margin:0px;
padding:0px;
}
/* Our top menu list */
nav ul
{
/* Set's how the element will interact with adjacent elements */
display:block;
/* Sets the height of the element (needs to be larger than the text) */
height: 35px;
/* Buffer space between other containers */
padding-top: 5px;
padding-bottom: 5px;
padding-left: 1%;
margin: 0;
/* Does not insert bullet points*/
list-style:none;
}
/* Float all <li> elements */
nav li{
float:left;
}
/* Display top menu <li> items as inline */
nav ul li
{
/* Set's how the element will interact with adjacent elements */
display:inline;
}
/* The main menu link */
nav ul li a
{
/* Changes the text to bold and uppercase */
font-weight: bold;
text-decoration:none;
text-transform: uppercase;
/* Default text colour */
color:#CCCCCC;
/* Pads the text so that it is not right up against the parent element*/
padding: 5px;
/* Specifies the line-height of the text */
line-height:30px;
/* Background colour */
background-color: #FFFFFF;
/* Border colour */
border: 2px solid #CCCCCC;
/* CSS3 Rounded Border */
-webkit-border-radius: 8px; /* Saf3-4, iOS 1-3.2, Android ≤1.6 */
-moz-border-radius: 8px; /* FF1-3.6 */
border-radius: 8px; /* Opera 10.5, IE9, Saf5, Chrome, FF4, iOS 4, Android 2.1+ */
/* useful if you don't want a bg color from leaking outside the border: */
-moz-background-clip: padding; -webkit-background-clip: padding-box; background-clip: padding-box;
}
/* When we hover over (or for mobile devices, click on the element) */
nav ul li a:focus, nav ul li:focus a, nav ul li a:hover, nav ul li:hover a
{
/* Underlines the text when we hover over it*/
text-decoration:underline;
/* Change the background colour*/
background-color: #CCCCCC;
/* Default text colour */
color:#336666;
/* Border colour */
border: 2px solid #336666;
}
/* Our sub menus */
nav li ul
{
/* Our background colour */
background-color: #CCCCCC;
/* HIDES the element until needed! */
display:none;
/* Let the browser determine the element height */
height:auto;
/* No padding or margins required */
padding:0px;
margin:0px;
/* Minimum width of 120px */
min-width: 120px;
width: auto;
/* Use the absolute positioning method*/
position:absolute;
/* Stack this element right at the front of all elements*/
z-index:200;
/* Default text colour */
color:#336666;
/* Border colour */
border: 2px solid #FFFFFF;
/* CSS3 Rounded Border */
-webkit-border-radius: 8px; /* Saf3-4, iOS 1-3.2, Android ≤1.6 */
-moz-border-radius: 8px; /* FF1-3.6 */
border-radius: 8px; /* Opera 10.5, IE9, Saf5, Chrome, FF4, iOS 4, Android 2.1+ */
/* useful if you don't want a bg color from leaking outside the border: */
-moz-background-clip: padding; -webkit-background-clip: padding-box; background-clip: padding-box;
}
nav li:focus ul, nav li:hover ul
{
/* Set's how the element will interact with adjacent elements */
display:block;
}
nav li li
{
/* Set's how the element will interact with adjacent elements */
display:block;
/* Do not allow the element to float around */
float:none;
/* Do not set margin */
margin: 0;
/* Take up all space given by parent element*/
/*width:100%;*/
/* Border colour */
border: none;
}
nav li:focus li a, nav li:hover li a
{
/* Turn off the background */
background:none;
border:none;
text-decoration:none;
}
nav li ul a
{
/* Set's how the element will interact with adjacent elements */
display:block;
/* Sets the height of the element (needs to be larger than the text) */
/*height:35px;*/
/* No margin */
margin:0px;
/*line-height: 20px;*/
/* No border or underlines*/
text-decoration:none;
border:none;
/* Default text colour */
color:#CCCCCC;
}
nav li ul a:hover, nav ul li ul li:hover a
{
/* Underlines the text when we hover over it*/
text-decoration:underline;
/* Change the background colour*/
background-color: #336666;
color: #CCCCCC;
}
nav p
{
/* No floating elements are allowed to the left of this element */
clear:left;
}
Aug 30, 2012
CodeIgniter: Dynamically setting the website's language
I have been writing up my own View subsystem for CodeIgniter that seperates the View more clearly from the controller. I have also designed it with Language support in mind, but for that I had to do some minor edits to the default Language class.
Even though you can load a specific language file in another language, the system will load error messages in the default language. This led to some cases where system errors would be in English, but the site language was something else.
With my new system I can have my default site language can be dynamically set by a user cookie… see the following code:
But to make the above code work I had to make some changes to the core:$lang_temp = $this->input->cookie('language');
if ( $lang_temp )
{
// NOTE: getDefault() is one of my core Lang.php modifications
if (!($lang_temp === $this->lang->getDefault()))
{
// NOTE: setDefault() is one of my core Lang.php modifications
$this->lang->setDefault($lang_temp);
}
} else {
// Set a cookie with system default
$this->input->set_cookie('language', $this->lang->getDefault(), '7200', '.' . $_SERVER['HTTP_HOST'], '/', NULL, FALSE);
}
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
/**
* CodeIgniter
*
* An open source application development framework for PHP 5.1.6 or newer
*
* @package CodeIgniter
* @author ExpressionEngine Dev Team
* @copyright Copyright (c) 2008 - 2011, EllisLab, Inc.
* @license http://codeigniter.com/user_guide/license.html
* @link http://codeigniter.com
* @since Version 1.0
* @filesource
*/
// ------------------------------------------------------------------------
/**
* Language Class
*
* @package CodeIgniter
* @subpackage Libraries
* @category Language
* @author ExpressionEngine Dev Team
* @link http://codeigniter.com/user_guide/libraries/language.html
*/
class CI_Lang {
/**
* List of translations
*
* @var array
*/
var $language = array();
/**
* List of loaded language files
*
* @var array
*/
var $is_loaded = array();
/**
* Default language
*
* @var array
*/
var $default_lang;
/**
* Constructor
*
* @access public
*/
function __construct()
{
$config =& get_config();
$this->default_lang = ( ! isset($config['language'])) ? 'en' : $config['language'];
log_message('debug', "Language Class Initialized");
}
// --------------------------------------------------------------------
/**
* Load a language file
*
* @access public
* @param mixed the name of the language file to be loaded. Can be an array
* @param string the language (english, etc.)
* @param bool return loaded array of translations
* @param bool add suffix to $langfile
* @param string alternative path to look for language file
* @return mixed
*/
function load($langfile = '', $idiom = '', $return = FALSE, $add_suffix = TRUE, $alt_path = '')
{
$langfile = str_replace('.php', '', $langfile);
if ($add_suffix == TRUE)
{
$langfile = str_replace('_lang.', '', $langfile).'_lang';
}
$langfile .= '.php';
if (in_array($langfile, $this->is_loaded, TRUE))
{
return;
}
if ($idiom == '')
{
$idiom = ($this->default_lang == '') ? 'en' : $this->default_lang;
}
// Determine where the language file is and load it
if ($alt_path != '' && file_exists($alt_path.'language/'.$idiom.'/'.$langfile))
{
include($alt_path.'language/'.$idiom.'/'.$langfile);
}
else
{
$found = FALSE;
foreach (get_instance()->load->get_package_paths(TRUE) as $package_path)
{
if (file_exists($package_path.'language/'.$idiom.'/'.$langfile))
{
include($package_path.'language/'.$idiom.'/'.$langfile);
$found = TRUE;
break;
}
}
if ($found !== TRUE)
{
show_error('Unable to load the requested language file: language/'.$idiom.'/'.$langfile);
}
}
if ( ! isset($lang))
{
log_message('error', 'Language file contains no data: language/'.$idiom.'/'.$langfile);
return;
}
if ($return == TRUE)
{
return $lang;
}
$this->is_loaded[] = $langfile;
$this->language = array_merge($this->language, $lang);
unset($lang);
log_message('debug', 'Language file loaded: language/'.$idiom.'/'.$langfile);
return TRUE;
}
// --------------------------------------------------------------------
/**
* Fetch a single line of text from the language array
*
* @access public
* @param string $line the language line
* @return string
*/
function line($line = '')
{
$value = ($line == '' OR ! isset($this->language[$line])) ? FALSE : $this->language[$line];
// Because killer robots like unicorns!
if ($value === FALSE)
{
log_message('error', 'Could not find the language line "'.$line.'"');
}
return $value;
}
/**
* Fetch Default language
*
* @access public
* @param void
* @return string Default language code
*/
function getDefault()
{
return $this->default_lang;
}
/**
* Set Default language
*
* @access public
* @param string The default language
* @return void
*/
function setDefault($language)
{
$this->default_lang = $language;
}
}
// END Language Class
/* End of file Lang.php */
/* Location: ./system/core/Lang.php */
I am sure you can find other uses for this code.....My other posts on CodeIgniter include:
Jul 18, 2012
AJAX, JQuery, CodeIgniter and PHP
This is an extension of an earlier blog of mine, but this time I have built it using the CodeIgniter PHP framework. I have also spiced it up with a dynamic JQuery form (kudos to Charlie Griefer and this blog post for the code!)
If you want to get your hands dirty doing something a little harder then I suggest you check out my earlier blog on creating a login system with CodeIgniter.
If you are just getting started, have a look at my other blog posts, 'Ubuntu, LAMP and CodeIgniter' and the 'Extended JQuery Tutorial'.
First, unzip the CodeIgniter source into your web-server's root directory (if you followed my LAMP guide above, this will default to /var/www/). Rename the extracted folder to test (so in the default example, the CodeIgniter framework will be stored in /var/www/test/).
Now delete ALL the php files in /var/www/test/application/controllers and /var/www/test/application/views. We are going to start completely fresh!
The final step is to configure the framework to use our new controller (once we create it...), so edit the file /var/www/test/application/config/routes.php with the following line:
If you want to get your hands dirty doing something a little harder then I suggest you check out my earlier blog on creating a login system with CodeIgniter.
If you are just getting started, have a look at my other blog posts, 'Ubuntu, LAMP and CodeIgniter' and the 'Extended JQuery Tutorial'.
Getting started
I am going to assume that you are working with a fresh installation of CodeIgniter that simply displays the welcome message. I am going to start from there so we don't get into any confusion!First, unzip the CodeIgniter source into your web-server's root directory (if you followed my LAMP guide above, this will default to /var/www/). Rename the extracted folder to test (so in the default example, the CodeIgniter framework will be stored in /var/www/test/).
Now delete ALL the php files in /var/www/test/application/controllers and /var/www/test/application/views. We are going to start completely fresh!
The final step is to configure the framework to use our new controller (once we create it...), so edit the file /var/www/test/application/config/routes.php with the following line:
$route['default_controller'] = "home";Now to make our main controller....
The Home page controller
- Create a file in /var/www/test/application/controllers named home.php
- Add in the following code:
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
class Home extends CI_Controller {
/**
* Index Page for the home controller.
*
* Maps to the following URL
* http://example.com/index.php
* - or -
* http://example.com/index.php/home
* - or -
* Since this controller is set as the default controller in
* config/routes.php, it's displayed at http://example.com/
*
* So any other public methods not prefixed with an underscore will
* map to /index.php/home/<method_name>
* @see http://codeigniter.com/user_guide/general/urls.html
*/
public function index()
{
$this->load->view('view');
}
}
/* End of file ajax.php */
/* Location: ./application/controllers/home.php */ - That's it. All this does is load up our view, which is our next step.
The Home page view
- Create a file in /var/www/test/application/views named view.php
- Add in the following code:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>Test</title>
<!-- JQuery code hosted by Google -->
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3/jquery.min.js"></script>
<!-- Adding and Deleting form buttons -->
<script type="text/javascript" src="form.js"></script>
<!-- AJAX functionality -->
<script type="text/javascript" src="ajax.js"></script>
</head>
<body>
<form id="myForm">
<div id="input1" style="margin-bottom:4px;" class="clonedInput">
Name: <input type="text" class="name" name="name1" id="name1" />
</div>
<div>
<input type="button" id="btnAdd" value="add another name" />
<input type="button" id="btnDel" value="remove name" />
<input type="button" name="submit" class="button" id="submit" value="Send" />
</div>
<div id="display">
</div>
</form>
</body>
</html> - Our basic HTML page includes three JavaScript files (One is the JQuery API, the other two we write ourselves; see below). We have a form with an input text box and three buttons. The first two buttons add and remove input boxes from our form, while the last button performs our AJAX operation.
form.js - Dynamically adding form elements
- Create a file called form.js in /var/www/test/
- Add the following code:
$(document).ready(function() {
$('#btnAdd').click(function() {
// how many "duplicatable" input fields we currently have
var num = $('.clonedInput').length; // the numeric ID of the new input field being added
var newNum = new Number(num + 1);
// create the new element via clone(),
// and manipulate it's ID using newNum value
var newElem = $('#input' + num).clone().attr('id', 'input' + newNum);
// manipulate the name/id values of the input inside the new element
newElem.children(':first').attr('id', 'name' + newNum).attr('name', 'name' + newNum);
// insert the new element after the last "duplicatable" input field
$('#input' + num).after(newElem);
// enable the "remove" button
$('#btnDel').attr('disabled','');
// business rule: you can only add 5 names
if (newNum == 5)
$('#btnAdd').attr('disabled','disabled');
});
$('#btnDel').click(function() {
// how many "duplicatable" input fields we currently have
var num = $('.clonedInput').length;
// remove the last element
$('#input' + num).remove();
// enable the "add" button
$('#btnAdd').attr('disabled','');
// if only one element remains, disable the "remove" button
if (num-1 == 1)
$('#btnDel').attr('disabled','disabled');
});
$('#btnDel').attr('disabled','disabled');
}); - This function attaches a JavaScript function to the add and remove button. The code is pretty well commented by Charlie Griefer, so if you want to know more I suggest you go visit his blog post.
ajax.js - AJAX with JQuery
- Create a file called ajax.js in /var/www/test/
- Add the following code:
$(document).ready(function() {
// This will run when the item of id 'submit' is clicked
$("#submit").click(function() {
// Grabs the text input from the form
var name = [];
$(".name").each(function() { name.push($(this).val()) });
// Create the key-value pair for POST
var dataString = 'string=' + name.join(' ') + '';
// This creates the AJAX connection
$.ajax({
type: "POST",
url: "index.php/ajax",
data: dataString,
dataType: 'json',
success: function(obj) {
$('#display').html(obj.message);
}
});
return false;
});
}); - This will send all of our dynamically generated form elements to index.php/ajax (which we will now create). Hopefully the code and comments are self explanatory; if you have any problems just shout out in the comments!
The AJAX controller
- Create a file in /var/www/test/application/controllers named ajax.php
- Add the following code:
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
class Ajax extends CI_Controller {
/**
* Index Page for this controller.
*
* Maps to the following URL
* http://example.com/index.php/ajax
* - or -
* http://example.com/index.php/ajax/index
*
* So any other public methods not prefixed with an underscore will
* map to /index.php/ajax/<method_name>
* @see http://codeigniter.com/user_guide/general/urls.html
*/
public function index()
{
$txtValue = "We have recieved: " . $this->input->post('string');
echo json_encode(array('message' => $txtValue));
}
}
/* End of file ajax.php */
/* Location: ./application/controllers/ajax.php */ - You should now be able to run this on your web-server and have a dynamic form that sends and recieves AJAX!
References:
- 'Dynamically adding form elements' by Charlie Griefer
- My post on AJAX, JQuery and PHP
- Refresh-less form validation from NetTutes
- Tutorial from PHP4Every1
Labels:
ajax,
codeigniter,
html,
javascript,
jquery,
php,
webapp
Jun 26, 2012
Handling HTTP GET requests with webapp2 and Google App Engine: Python
This is a continuation of my Python 2.7 and Google App Engine series. This particular blog post builds upon the code given in my previous posts URL Routing and Cron and Datastore in Google App Engine: Python, which in turn builds upon my earlier work.
If you don't understand parts of the code I highly suggest you browse
my earlier blog posts so you can understand some of the design decisions
I have made.
I have a script feed.py that I have mapped using app.yaml. A cron script (configured by cron.yaml) simply connects to my Twitter account and converts my status updates into an RSS feed. It then stores the RSS feed into a Google Datastore object.
The feed script takes the Datastore object and displays it. We use another script (entity.py) to define the Datastore object.
We will now configure the system so that it can convert multiple twitter accounts into an RSS feed. To display a particular RSS feed we will use a HTTP GET request.
app.yaml:
cron.yaml:
entity.py:
And that's it! You now have a fully functional application that just uses the webapp2 framework!
If you navigate to http://localhost:8080/?account=almightyolive you should now see the RSS feed. You can test if your mapping works by navigating to http://localhost:8080/?account=founding; you should see the Founding Institute twitter account instead!
A brief overview...
For those who are diving straight in, let me explain the old code and how I will update it:I have a script feed.py that I have mapped using app.yaml. A cron script (configured by cron.yaml) simply connects to my Twitter account and converts my status updates into an RSS feed. It then stores the RSS feed into a Google Datastore object.
The feed script takes the Datastore object and displays it. We use another script (entity.py) to define the Datastore object.
We will now configure the system so that it can convert multiple twitter accounts into an RSS feed. To display a particular RSS feed we will use a HTTP GET request.
The main application
We will create a file called feed.py. This script will be our controller; it simply gets the HTTP requests and maps them to certain classes. These classes will then call other functions to perform the required tasks.# The webapp2 framework
import webapp2
# Our datastore interface
from google.appengine.ext import db
# Our entity library
import entity
# Our XML2RSS library
import XML2RSS
# Output the XML in a HTML friendly manner
class Cron(webapp2.RequestHandler):
# Respond to a HTTP GET request
def get(self):
# A try-catch statement
try:
XML2RSS.getTweets("almightyolive")
XML2RSS.getTweets("founding")
XML2RSS.getTweets("ABCNews24")
XML2RSS.getTweets("SBSNews")
# Our exception code
except (TypeError, ValueError):
self.response.out.write("<html><body><p>Invalid inputs</p></body></html>")
# Fetches an XML document and parses it
class MainPage(webapp2.RequestHandler):
# Respond to a HTTP GET request
def get(self):
# A try-catch statement
try:
account = self.request.get('account')
feed = entity.Rss()
feed_k = db.Key.from_path('Rss', account)
feed = db.get(feed_k)
# Outputs the RSS
self.response.out.write(feed.content)
# Our exception code
except (TypeError,ValueError):
self.response.out.write("<html><body><p>Invalid inputs (Type Error)</p></body></html>")
except:
self.response.out.write("<html><body><p>Unspecified Error</p></body></html>")
# Create our application instance that maps the root to our
# MainPage handler
app = webapp2.WSGIApplication([('/', MainPage),('/cron', Cron)], debug=True)
The XML2RSS script
As you may have noticed,the feed.py script made reference to an XML2RSS object. This is a separate script that outsources the conversion of XML to RSS into easy-to-call functions. Create a new file called XML2RSS.py and add the following:# The minidom library for XML parsing
from xml.dom.minidom import parseString
# The URL Fetch library
from google.appengine.api import urlfetch
# Our entity library
import entity
# Detects if it is a URL link and adds the HTML tags
def linkify(text):
# If http is present in, add the link tag
if "http" in text:
text = "<a href='" + text + "'>" + text + "</a>"
elif "@" in text:
text = "<a href='http://twitter.com/#!/" + text.split("@")[1] + "'>" + text + "</a>"
elif "#" in text:
text = "<a href='https://twitter.com/#!/search/%23" + text.split("#")[1] + "'>" + text + "</a>"
return text
# Output the XML in a HTML friendly manner
def outputRSS(xml, account):
# The get the states list
statuses = xml.getElementsByTagName("status")
# Our return string
outputString = "<?xml version='1.0'?>\n<rss version='2.0'>\n\t<channel>\n\t\t<title>Twitter: " + account + "</title>\n\t\t"
outputString+= "<link>https://twitter.com/#!/almightyolive</link>\n\t\t<description>The twitter feed for " + account + "</description>"
# Cycled through the states
for status in statuses:
#Gets the statuses
text = status.getElementsByTagName("text")[0].firstChild.data
date = status.getElementsByTagName("created_at")[0].firstChild.data
tweet = status.getElementsByTagName("id")[0].firstChild.data
# Insert links into the text
words = text.split()
for i in range (len(words)):
words[i] = linkify(words[i])
# Recompile words
text = " ".join(words)
# Creates our output
string = "\n\t\t<item>\n\t\t\t<title>" + str(date) + "</title>\n\t\t\t<link>https://twitter.com/AlmightyOlive/status/" + tweet + "</link>\n\t\t\t<description>" + str(text) + "</description>\n\t\t</item>"
outputString+=string
# Output string
outputString += "\n\t</channel>\n</rss>"
return outputString
# Our RSS storage function
def getTweets(account):
# Grabs the XML
url = urlfetch.fetch('https://api.twitter.com/1/statuses/user_timeline.xml?screen_name=' + account + '&count=10&trim_user=true')
# Parses the document
xml = parseString(url.content)
# Converts the XML into RSS
content = outputRSS(xml, account)
# Our RSS storage entity
rssStore = entity.Rss(key_name='' + account)
# Elements of our RSS
rssStore.feed = '' + account
rssStore.content = content
# Stores our RSS Feed into the datastore
rssStore.put()
The pieces to make it all work
If you have been following on from my previous work, then you should already have most of this code. I won't bother explaining it here because it is mostly self-explanatory.app.yaml:
application: almightynassar
version: 1
runtime: python27
api_version: 1
threadsafe: yes
handlers:
- url: /cron
script: feed.app
login: admin
- url: /.*
script: feed.app
cron.yaml:
cron:
- description: daily summary job
url: /cron
schedule: every 1 hours
entity.py:
# Our datastore interface
from google.appengine.ext import db
# Our RSS entity object
class Rss(db.Model):
feed = db.StringProperty()
content = db.TextProperty()
And that's it! You now have a fully functional application that just uses the webapp2 framework!
If you navigate to http://localhost:8080/?account=almightyolive you should now see the RSS feed. You can test if your mapping works by navigating to http://localhost:8080/?account=founding; you should see the Founding Institute twitter account instead!
References:
- Google's own getting started with webapp and Python.
- The official webapp2 reference
- The Google developer resource for GAE
- Google App Engine FAQs
- YAML reference
- app.yaml reference
Jun 25, 2012
URL Routing through WebApp2 in Google App Engine: Python
This is a continuation of my Python 2.7 and Google App Engine series. This particular blog post builds upon the code given in my previous post Cron and Datastore in Google App Engine: Python, which in turns builds upon my earlier work. If you don't understand parts of the code I highly suggest you browse my earlier blog posts so you can understand some of the design decisions I have made.
I have two scripts (feed.py and cron.py) that I have mapped using app.yaml. The cron script simply connects to my Twitter account and converts my status updates into an RSS feed. It then stores the RSS feed into a Google Datastore object.
The feed script takes the Datastore object and displays it. Both scripts use a third script (entity.py) to define the Datastore object.
Currently the set-up is not thread-safe because I have to use two different scripts to handle my incoming requests. The plan is to replace this set-up with one that is thread-safe. Effectively, we will be using the URL routing functionality provided by the webapp2 framework.
The big changes are:
app.yaml:
cron.yaml:
entity.py:
And that's it! You now have a fully functional application that just uses the webapp2 framework!
A brief overview...
For those who are diving straight in, let me explain the old code and how I will update it:I have two scripts (feed.py and cron.py) that I have mapped using app.yaml. The cron script simply connects to my Twitter account and converts my status updates into an RSS feed. It then stores the RSS feed into a Google Datastore object.
The feed script takes the Datastore object and displays it. Both scripts use a third script (entity.py) to define the Datastore object.
Currently the set-up is not thread-safe because I have to use two different scripts to handle my incoming requests. The plan is to replace this set-up with one that is thread-safe. Effectively, we will be using the URL routing functionality provided by the webapp2 framework.
Combining the scripts
The first thing we will do is combine both cron.py and feed.py into one script. The following code should be saved to a file called feed.py:# The webapp2 framework
import webapp2
# Our datastore interface
from google.appengine.ext import db
# The minidom library for XML parsing
from xml.dom.minidom import parseString
# The URL Fetch library
from google.appengine.api import urlfetch
# Our entity library
import entity
# Detects if it is a URL link and adds the HTML tags
def linkify(text):
# If http is present in, add the link tag
if "http" in text:
text = "<a href='" + text + "'>" + text + "</a>"
elif "@" in text:
text = "<a href='http://twitter.com/#!/" + text.split("@")[1] + "'>" + text
text+= "</a>"
elif "#" in text:
text = "<a href='https://twitter.com/#!/search/%23" + text.split("#")[1] + "'>" + text + "</a>"
return text
# Output the XML in a HTML friendly manner
def outputRSS(xml):
# The get the states list
statuses = xml.getElementsByTagName("status")
# Our return string
outputString = "<?xml version='1.0'?>\n<rss version='2.0'>\n\t<channel>"
outputString+= "\n\t\t<title>Almightyolive Twitter</title>\n\t\t"
outputString+= "<link>https://twitter.com/#!/almightyolive</link>\n" outputString+= "\t\t<description>The twitter feed for the Almighty "
outputString+= "Olive</description>"
# Cycled through the states
for status in statuses:
#Gets the statuses
text = status.getElementsByTagName("text")[0].firstChild.data
date = status.getElementsByTagName("created_at")[0].firstChild.data
tweet = status.getElementsByTagName("id")[0].firstChild.data
# Insert links into the text
words = text.split()
for i in range (len(words)):
words[i] = linkify(words[i])
# Recompile words
text = " ".join(words)
# Creates our output
string = "\n\t\t<item>\n\t\t\t<title>" + str(date) + "</title>\n"
string+= "\t\t\t<link>https://twitter.com/AlmightyOlive/status/" + tweet
string+= "</link>\n\t\t\t<description>" + str(text) + "</description>\n"
string+= "\t\t</item>"
outputString+=string
# Output string
outputString += "\n\t</channel>\n</rss>"
return outputString
# Output the XML in a HTML friendly manner
class Cron(webapp2.RequestHandler):
# Respond to a HTTP GET request
def get(self):
# A try-catch statement
try:
# Grabs the XML
url = urlfetch.fetch('https://api.twitter.com/1/statuses/user_timeline.xml?screen_name=almightyolive&count=10&trim_user=true')
# Parses the document
xml = parseString(url.content)
content = outputRSS(xml)
# Our RSS storage entity
rssStore = entity.Rss(key_name='almightyolive')
# Elements of our RSS
rssStore.feed = "almightyolive"
rssStore.content = content
# Stores our RSS Feed into the datastore
rssStore.put()
# Our exception code
except (TypeError, ValueError):
self.response.out.write("<html><body><p>Invalid inputs</p></body></html>")
# Fetches an XML document and parses it
class MainPage(webapp2.RequestHandler):
# Respond to a HTTP GET request
def get(self):
# A try-catch statement
try:
feed = entity.Rss()
feed_k = db.Key.from_path('Rss', 'almightyolive')
feed = db.get(feed_k)
# Outputs the RSS
self.response.out.write(feed.content)
# Our exception code
except (TypeError, ValueError):
self.response.out.write("<html><body><p>Invalid inputs</p></body></html>")
# Create our application instance that maps the root to our
# MainPage handler
app = webapp2.WSGIApplication([('/', MainPage),('/cron', Cron)], debug=True)
The big changes are:
- We have added a new class called Cron, which included all of that loose code in cron.py
- We have added a new URL mapping to our WSGI Application. This will hand over any request for '/cron' to our new Cron class
The pieces to make it all work
If you have been following on from my previous work, then you should already have most of this code. The only thing you need to touch is one line in app.yaml, which is to map /cron to our feed webapp.app.yaml:
application: almightynassar
version: 1
runtime: python27
api_version: 1
threadsafe: yes
handlers:
- url: /cron
script: feed.app
login: admin
- url: /.*
script: feed.app
cron.yaml:
cron:
- description: daily summary job
url: /cron
schedule: every 1 hours
entity.py:
# Our datastore interface
from google.appengine.ext import db
# Our RSS entity object
class Rss(db.Model):
feed = db.StringProperty()
content = db.TextProperty()
And that's it! You now have a fully functional application that just uses the webapp2 framework!
References:
- Google's own getting started with webapp and Python.
- The official webapp2 reference
- The Google developer resource for GAE
- Google App Engine FAQs
- YAML reference
- app.yaml reference
Subscribe to:
Posts (Atom)