Python Notes: Picture-of-the-Day
- The information presented here is intended for educational use.
- The information presented here is provided free of charge, as-is, with no warranty of any kind.
- Edit: 2024-07-14
Overview
- The original picture-of-the-day feature (see the top right corner of this page: https://neilrieck.net/)
was written entirely in JavaScript. When you passed the main function a month-day pair in mmdd format, it returned the
URL (either local or remote) of an image, along with some displayable text, and an optional hyperlink to some article or site.
- Adding support for more than a dozen images was a pain, so I moved all the logic into a server-side Python program, while the
data was moved into a MariaDB (a fork of MySQL) relational database. The whole thing is designed to return "escaped data" in one
of three formats: XML, JSON, or plain text. Click the next few links to see a simple example for "0212" (Feb-12) which
celebrates the birthday of Charles Darwin
- detailed steps:
- started with this html test file (tests my server-side code; can cycle from 0101 to 1231)
- added more JavaScript to this html production file
- broke out the JavaScript into this java script file
- bash script (server side launcher - need for just-in-time Python compile)
- python file (server-side application)
- database schema
hack1.pixofday (database: hack1 table: pixofday) |
field name |
use |
mmdd0 |
first month-day to show a given picture (mmdd) |
mmdd1 |
last month-day to show a given picture (mmdd) |
comment |
text used in debugging |
src |
local or remote location of the desired image; insert this into an img tag |
calc |
date used to do an anniversary (or season) calculation:
div template |
date |
sample output |
+calc+ |
1969 |
is the zeroth anniversary of Apollo-11 |
+calc1+ |
1975 |
is the first season of Quirks + Quarks |
|
text1 |
preformatted html |
text2 |
preformatted html (optional) |
text3 |
preformatted html (optional) |
- database other:
- so entering "0212" into mmdd0, and "0220" into mmdd1, will cause the picture of Charles Darwin to be displayed between
Feb-12 and Feb-20
- be sure to enter two limiting records using "0000" and "9999" to ensure that something is always returned
- some of special purpose entries exist between "8000" and "8099"
- clicking a button on the web page calls a JavaScript routine
- one JavaScript function named doReset(), works entirely client-side without annoying the server
- two JavaScript functions named setDefaults1() and setDefaults2() will
call the server (via AJAX) expecting some sort of form initialization
- one JavaScript function named runDemo() reads all the fields defined in the HTML form, tests them,
then passes their contents on to the server (via AJAX) for processing
- because this is a simple how-to demo, many of the routines are simplistic (e.g. no reliance on JavaScript libraries: jQuery,
AngularJS, ReactJS)
- in a future release I will replace my home-brew AJAX with something in jQuery
- there are numerous ways to pass data back to HTML, but here I used XML because all modern browsers contain routines to perform
XML parsing
- I also added JSON support since it has been available with JavaScript-1.5 (ECMAscript-5)
file-1: HTML test page
<!DOCTYPE html>
<html lang="en">
<head>
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta content="#ffffff" name="theme-color">
<title>Test Pix</title>
<link href="css/nsr-20170909.css" rel="stylesheet" type="text/css">
<script src="js/picture-viewer72.js"></script>
</head>
<body lang="en-ca" >
<h4>Test-Pix</h4>
<table class="tbl-blue" style="width:100%">
<tr><td>msg</td> <td id="msg"></td></tr>
<tr><td>src</td> <td id="src"></td></tr>
<tr><td style="width:50px">com</td>
<td id="comment" style="min-width:90%"></td>
</tr>
<tr><td>txt</td>
<td id="text" style="text-align: center; font-weight: bold;height:280px"></td>
</tr>
<tr><td>calc</td><td id="calc"></td></tr>
<tr><td>mmdd</td><td id="mmdd"></td></tr>
</table>
<script>
testYear();
</script>
</body>
</html>
file-2: HTML production page
<!DOCTYPE html>
<html lang="en-ca">
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Hackerspace - STEMspace</title>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.slim.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
<link href="css/nsr-20170909.css" rel="stylesheet" type="text/css">
<script src="js/picture-viewer74.js"></script>
</head>
<body>
...snip...
<div class="col-xs-12 col-sm-5 col-md-4 col-lg-3" id="pixoftheday"
style="text-align: center; font-weight: bold;height:280px">
<img alt="400ky of atmospheric CO2" src="images/carbon_dioxide_400kyr.png" style="height:80%">
<div style="text-align:center;margin-top:5px;font-weight:bold">
CO2 levels cycle between<br>180 and 280 ppm every 120K years</div>
</div>
</div>
...snip...
<script>
//
// use a namespace technique for declaring functions
// p.s. my implementation is way too complicated for most sites
//
// create an object called nsNSR1
//
var nsNSR1 = function(){
//
// show/hide the xmas alert
//
function xmas_support(){
var snap = new Date();
var ccyy = snap.getFullYear();
var mm = snap.getMonth();
//var dow = snap.getDay();
var dd = snap.getDate();
if (mm == 0){
ccyy--;
}
// display for Western Christmas
document.getElementById("xmasyear").outerHTML = (ccyy);
if ((mm == 11) || ((mm == 0) && (dd <= 2))){
document.getElementById("xmasmsg").hidden = false;
}else{
document.getElementById("xmasmsg").hidden = true;
}
}
//
// enable a hyperlink (old site only)
//
function moving(){
var this_host = window.location.hostname;
var caveat = this_host.indexOf('neilrieck.net');
console.log('moving-caveat:' + this_host);
console.log('moving-caveat:' + caveat);
if (caveat<0){
// enable HTML warning on the page
document.getElementById("moving9").hidden = false;
}
}
//
// pod (picture-of-day) notes:
// 1) Rotate between 4 images (3 environmental and 1 scientist)
// 2) this routine calls things in namespace "nsNSR7" defined in picture-viewer75.js
// 3) image "co2-400kyr" is already on the screen in case of failures here
// 4) steps:
// 1: load "local co2-400kyr" (into slot 0)
// load "NOAA co2-data" (into slot 1)
// load "NOAA co2-trend" (into slot 2)
// load "local Scientist" (into slot 3)
// 2-5: kill 3 seconds
// 6: test if NOAA images loaded
// load "local co2-data" (into slot 1 only if it is empty)
// load "local co2-trend" (into slot 2 only if it is empty)
// 7-10: kill 3 seconds (only if necessary)
// 11: stop timer9 then check if we have four images;
// if all is well then start timer8 to rotate between four prefetched images
//
function pod(){ // pod = picture-of-day
var pix9 = 0; // init
console.log("starting pod()");
function init(){
console.log("pod-init()");
try{
console.log("init1: ", JSON.stringify( Object.assign ( {}, nsNSR7.myTxt )));
nsNSR7.initArrays(10);
console.log("init2: ", JSON.stringify( Object.assign ( {}, nsNSR7.myTxt )));
for (i=1;i<10;i++){
console.log("hack"+i, JSON.stringify( Object.assign ( {}, nsNSR7.myTxt[i] )));
}
}catch(e){
console.log("init-error:",e);
}
}
// init();
// start timer-9
var intervalId9 = setInterval(
function(){
pix9++;
console.log("pod-pix9: "+pix9);
if (pix9==1){
nsNSR7.fetchDefault(0,8000); // co2 400kyr (local)
nsNSR7.fetchDefault(1,8002); // co2-data (NOAS)
nsNSR7.fetchDefault(2,8004); // co2-trend (NOAS)
nsNSR7.fetchToday(3); // scientist picture-of-the-day
return;
}
if (pix9<6){
// kill some time
return;
}
if (pix9==6){
// test if NOAA images loaded
// image1: if no 8002 then load 8001
var work = 0;
var tmp;
try{
tmp=nsNSR7.imageArray[1].width;
}catch(e){
console.log("-e-image1: ",e);
tmp=0;
}
if (tmp==0){
console.log("pod: 8002 fubar so loading 8001");
nsNSR7.fetchDefault(1,8001); // co2-data (local)
work += 1; // did some work
}
// image2: if no 8004 then load 8003
var tmp;
try{
tmp=nsNSR7.imageArray[2].width;
}catch(e){
console.log("-e-image2: ",e);
tmp=0;
}
if (tmp==0){
console.log("pod: 8004 fubar so loading 8003");
nsNSR7.fetchDefault(2,8003); // co2-trend-local
work += 1; // did some work
}
if (work==0){ // if no need to wait
pix9=11;
console.log("pod-pix9 jumps to: "+pix9);
}else{
return;
}
}
if (pix9<11){ // killing time
return;
}
if (pix9==11){
console.log("pod: stopping timer #9");
clearInterval(intervalId9); // stop this timer
var recipe = 0; // init
try{
// make sure we have four good images
for (var i=0;i<4;i++){
console.log("image: "+i+" size:",nsNSR7.imageArray[i].width);
if (nsNSR7.imageArray[i].width==0){
console.log("oops: image "+i+" is zero length");
return;
}else{
recipe++;
}
}
}catch(e){
console.log("recipe error: ",e);
recipe = 99;
}
console.log("recipe: "+recipe);
if (recipe != 4){
console.log("pod: recipe error so will not rotate any images");
}else{
console.log("pod: arming timer #8");
var pix8=0; // init
var intervalId8 = setInterval(
function (){
pix8++; //
pix = pix8%4; // only 4 pictures: 0-3
console.log("pod-pix8: " +pix8+" index: "+pix);
if (pix8 >= 24){
console.log("pod: stopping timer #8");
clearInterval(intervalId8);
}else{
console.log("pod-inject:" + nsNSR7.myTxt[pix]);
document.getElementById("pixoftheday").innerHTML = nsNSR7.myTxt[pix];
}
}, 6000); // 6 seconds
}
}
}, 1000); // 1 second
}
//
// expose anything we want externally referenced
//
return{
xmas_support:xmas_support,
moving:moving,
pod:pod
}
}();
// now execute these tasks
console.log("-i-local javascript tasks begin");
function show_answer(element) {
element.parentNode.children[2].style.visibility = "visible";
}
function hide_answer(element) {
element.parentNode.children[2].style.visibility = "hidden";
}
// nsNSR1.xmas_support();
// nsNSR1.moving();
// odyssey_viewer();
console.log("-i-queueing a 2-second delay");
setTimeout(function() {
console.log("-i-timer expired");
nsNSR1.pod();
}, 2000);
// ##############################
nsNSR1.pod(); // execute my function
</script>
</body>
</html>
file-3: JavaScript
// =======================================================================
// title : picture-viewer75.js
// author: Neil Rieck (Waterloo, Ontario, Canada)
// notes :
// 1) In order to avoid name clashes with other javascript routines, this
// code block declares functions and variables inside a namespace. So
// any labels you wish published must be added to the return statment
// at the end of this file
// 2) this file currently supports default date codes: 8000-8004
//
// ver when what
// --- ---------- --------------------------------------------------------
// 4 2019-11-28 modified to work with my Python-based cgi
// 5 2020-04-04 code cleanup
// 6 2020-10-29 moved everything into a namespace
// 71 2021-10-30 better overlapping AJAX (pulling from slow sites)
// 72 2021-10-31 XmlDoc is no longer global (what was I thinking?)
// 73 2021-12-01 removed protocol from url
// 74 2022-08-20 changes to allow 3-5 default pictures
// 75 2024-07-06 experiments with async fetches. See: myAsync[]
// =======================================================================
//
// start of name-space
//
var nsNSR7 = (function() {
//
'use strict'; // no kid-stuff here
//
// some private variables
//
var debug=0; //
var slot=0; //
var mySrc=[]; // image source
var myTxt=[]; // image text (from payload)
var imageArray=[]; // image data
var myAsync=[]; // array of objects
//
// store data as well as preload the image
//
function preloadImage(url,txt,slot){
if (typeof slot == "undefined"){
var slot=9;
}
console.log("preload url: "+url+" slot: "+slot);
try{
mySrc[slot] = url;
myTxt[slot] = txt;
imageArray[slot] = new Image();
imageArray[slot].src = url;
}catch(e){
console.error("preload url: "+url+" slot: "+slot+" error: ",e);
}
}
//
// start_ajax (start an AJAX transaction)
// Note: use new to create copies of this object
//
function start_ajax(msg,slot){
console.log("start ajax slot: "+slot);
// still some Microsoft browsers out there
if (typeof XMLHttpRequest == "undefined")
XMLHttpRequest=function(){
try { return new ActiveXObject("Msxml2.XMLHTTP.6.0") } catch (e) {}
try { return new ActiveXObject("Msxml2.XMLHTTP.3.0") } catch (e) {}
try { return new ActiveXObject("Msxml2.XMLHTTP") } catch (e) {}
try { return new ActiveXObject("Microsoft.XMLHTTP") } catch (e) {}
throw new Error("This browser does not support XMLHttpRequest.")
}
if (msg != ""){
var xhr=new XMLHttpRequest();
if (xhr != null){
xhr.open("GET", msg, true); // async=true
xhr.timeout=3000; // 3 seconds max
xhr.ontimeout = function(){
console.log("Timeout:",slot);
}
xhr.onreadystatechange=function(){
if (xhr.readyState == 4 && xhr.status == 200){
var resp$=xhr.responseText;
ajax_xml_extract(resp$);
}
}
console.log("sending ajax request for slot: "+slot)
xhr.send(null); // not null for POST
}
}
}
//
// ajax_xml_extract
//
function ajax_xml_extract(resp$){
// console.log("ajax-xml-extract raw:",resp$);
try{
if (window.DOMParser) {
var parser=new DOMParser();
var xml=parser.parseFromString(resp$,"text/xml");
}else{
var xml=new ActiveXObject("Microsoft.XMLDOM");
xml.async=false;
xml.loadXML(resp$);
}
var src = get_xml_data("src",xml);
var txt = get_xml_data("text",xml);
var slot= get_xml_data("slot",xml);
console.log("calling preloadImage for slot: "+slot)
preloadImage(src,txt,slot);
}catch(e){
console.error("ajax_xml_extract:",e)
}
}
//
// does string 'x' represent a positive integer?
//
function isStrInt(x){
x=x.replace(/\s/g,'');
if (x=="") return false;
try{
y = parseInt(x);
}catch(e){
y = -1;
}
if(y>0){
return true;
}else{
return false;
}
}
function htmlEncode(str) {
return String(str).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"');
}
function htmlDecode(str) {
return String(str).replace(/</g, '<').replace(/>>/g, '>').replace(/"/g, '"').replace(/&/g, '&');
}
//
// get_xml_data (look for one item)
//
function get_xml_data(tag,xml){
var x;
try{
x = xml.getElementsByTagName(tag)[0].childNodes[0].nodeValue;
}catch(e){
console.error("get-xml-data: ",e);
x = null;
}
return(x);
}
//
// get number of days per month
//
function monthSize(x){
switch(x) {
case 2:
return(28);
case 9:
case 4:
case 6:
case 11:
return(30);
default:
return(31);
}
}
function pad(num, size) {
var s = num+"";
while (s.length < size) s = "0" + s;
return s;
}
//
// test picture-of-the-day logic for the whole year
// caveat: only used in testing
//
function testYear(){
debug=1; // change global
var mm=0; // month
var dd=0; // day
var mmdd="";
var intervalId = setInterval(function(){
if (mm==0){
mm=1;
dd=1;
}else{
dd++;
limit=monthSize(mm);
if (dd>limit){
mm++;
dd=1;
}
}
if (mm>12){
clearInterval(intervalId);
}else{
mmdd=junk=pad(mm,2)+pad(dd,2);
console.log("mmdd:"+mmdd);
document.getElementById("mmdd").innerHTML="mmdd: "+mmdd;
msg = "https://neilrieck.net/cgi-bin/daily_pix?xml=1&slot=0&mmdd="+mmdd;
start_ajax(msg,0);
}
}, 1000);
}
//
// get one picture for today
//
function fetchToday(slot){
var d = new Date();
var mm = d.getMonth(); // 0..11
var dd = d.getDate(); // 1..31
var mmdd= pad(mm+1,2)+pad(dd,2);
console.log("fetchToday-mmdd: "+mmdd);
var msg = "https://neilrieck.net/cgi-bin/daily_pix?xml=1&slot="+slot+"&mmdd="+mmdd;
// start_ajax(msg,slot);
myAsync.push(new start_ajax(msg,slot)); // create one-time use object
}
//
// get default picture
// 8000: co2_400kyr
// 8001: co2_levels (static-local)
// 8002: co2_levels (dynamic-remote)
//
function fetchDefault(slot,mmdd){
if (typeof mmdd == "undefined"){
var slot=8000;
}
if ((mmdd<8000)||(mmdd>8002)){
slot=8000;
}
console.log("fetchDefault-mmdd: "+mmdd+" slot: "+slot);
var msg = "https://neilrieck.net/cgi-bin/daily_pix?xml=1&slot="+slot+"&mmdd="+mmdd;
// start_ajax(msg,slot);
myAsync.push(new start_ajax(msg,slot)); // create one-time use object
}
//
// end-of-name-space logic
// now publish the names of anything referenced externally
//
return{
preloadImage:preloadImage,
fetchToday:fetchToday,
fetchDefault:fetchDefault,
mySrc:mySrc,
myTxt:myTxt,
imageArray:imageArray
}
// end of name-space
}());
// end of file
file-4: Python3 (optional launcher)
note: this file is usually located in the cgi-bin folder of your web server
#!/usr/bin/python3
# title : daily_pix
# author: Neil Rieck (Waterloo, Ontario, Canada)
# edit : 2022-09-30
# notes : forces Python3 to do a one-time compile of whatever is imported so
# 'file_100.py' is compiled to '__pycache__/file_100.cpython-36.pyc'
import daily_pix_133
daily_pix_133.main()
file-5: Python3 (program)
note: in this example, this file is found under cgi-bin (but you could move it anywhere else provided Apache can access it)
#!/usr/bin/python3
'''
===================================================================================
title : daily_pix_133.py
author : Neil Rieck
history:
ver when what
100 2019-11-23 calls daily_pix_mysql.py
101 2019-12-15 cleanup
102 2020-04-18 added JSON support
130 2020-05-09 moved db into here; changed the connector; cursor now returns a dict
2020-07-11 tiny fix in anniversary/season calc (see: 'calc' vs 'calc1')
131 2020-07-11 replaced format strings with f strings
2021-12-01 removed 'http:' from urls (to properly support CORS)
132 2022-08-22 now also encode 'comment' in output_xml
133 2022-09-30 added support for pattern: +ccyy+
purpose:
1) for a given month-day (format: mmdd) return a picture along with associated text
2) optionally, return the data in XML or JSON format
3) these PEP8 rules are disabled: E305,E501 so I can code in a traditional way
4) I used vim8 along with the pymode plugin
===================================================================================
'''
import cgi
import datetime
import json
import sys
import html
import mysql.connector as db
#
# constants
#
HOST = "neilrieck.net"
#
# common function to BAIL (will be visible in the browser's console)
#
def errorExit(msg):
print("content-type: text/plain; charset=utf-8")
print("")
print(msg)
sys.exit()
#
# logIt (a simple file-based logger)
# caveat: if SELinux is enabled then write to /var/log/daily_pix_msg_log.txt
#
def logIt(msg):
try:
fn = "daily_pix_msg_log.txt"
f = open(fn, "a")
f.write(f"msg: {msg}\r\n")
f.close()
except Exception:
pass
#
# do that database thing
#
def get_record(parm):
try:
con = db.connect(host='127.0.0.1', user='read123', passwd='read123', database='hack1')
cur = con.cursor(dictionary=True) # force return of dict
cmd = ("select * from pixofday "
f"where (mmdd1 <= {parm}) and (mmdd2 >= {parm}) "
"order by concat(mmdd2,mmdd1) limit 1")
cur.execute(cmd)
# row = cur.fetchall() # returns a list of tuples (or dicts)
row = cur.fetchone() # returns a tuple (or dict)
return row
except Exception as e:
msg = f"Exception: {e} while opening database"
logIt(msg)
errorExit(msg)
'''
===============================================================
<<< general description >>>
1) database field definitions
0: mmdd1 - start date
1: mmdd2 - end date
2: comment -
3: calc - used to do an anniversary (or season) calculation
4: src - image source
5: text1 - usually a title associated with the photo
6: text2
7: text3
2) data:
1: each record will indicate that a certain photo be shown between mmmdd1 and mmdd2
2: one special record has mmdd1="0000" and mmdd2="9999" so something will always be returned
3) logic:
1: the 'src' field contains an image location like these two examples:
1) local: /images/Edmond_Halley.jpg
2) remote: https://www.esrl.noaa.gov/gmd/webdata/ccgg/trends/co2_data_mlo.png
2: any entry not containing "http" will need a local prefix
3: text1 to text3 contain HTML so merge them into one string then test for the following patterns:
4: if we detect the string "+xxx+" then replace it with the resultant 'src' string seen at the end of step 2
5: if we detect the string "+host+" then replace it with the default HOST of this site
6: if we detect the string "+calc+" then do an season calculation (eg. 45th season of Quirks+Quarks)
7: if we detect the string "+calc1+" then do an offset+1 calculation (eg. 50th Anniversary of Oktoberfest)
8: if we detect the string "+ccyy+" then we will replace it with the current year
9: return data to caller via XML or JSON (this means the data must be properly escaped)
4) Cross-Origin Resource Sharing (CORS)
For AJAX use you must add a few Cross-Origin Resource Sharing (CORS) directives to Apache config like so:
<Directory "/var/www/html/cgi-bin">
AllowOverride None
Options None
Require all granted
# 1) CORS for initial AJAX testing.
#Header set Access-Control-Allow-Origin "*"
# 2) CORS for single second site support (this Apache instance runs on "https://neilrieck.net"
# but we want to allow AJAX access from "http://www3.sympatico.ca/n.rieck/")
Header set Access-Control-Allow-Origin http://www3.sympatico.ca
Header set Access-Control-Allow-Headers "Content-Type,Accept"
Header merge Vary Origin
Header set MyHeader "Neil's Hack"
</Directory>
===============================================================
'''
#
# raw data in :: cooked data out
#
def process_data(row):
imgSrc = row['src'] # LOGIC-1
if (imgSrc.find('http') == -1): # LOGIC-2 (local or remote)
imgSrc = ".." + imgSrc # would '/images' be better?
allText = row['text1'] + row['text2'] + row['text3'] # LOGIC-3
temp = allText.find('+xxx+') # LOGIC-4
if (temp != -1): # if +xxx+ was found...
allText = allText[:temp] + imgSrc + allText[temp+5:] #
temp = allText.find('+host+') # LOGIC-5
if (temp != -1): # if +host+ was found...
allText = allText[:temp] + HOST + allText[temp+6:] #
temp = allText.find('+calc+') # LOGIC-6 (season number)
if (temp != -1): # if +calc+ was found...
year = datetime.datetime.now().strftime('%Y') #
try: #
season = int(row['calc']) # eg. ccyy
except Exception as e: #
season = 1900 #
msg = f"error: {e}" #
logIt(msg) #
diff = int(year) - season #
allText = allText[:temp] + str(diff) + allText[temp+6:] #
temp = allText.find('+calc1+') # LOGIC-7 (anniversary)
if (temp != -1): # if +calc1+ was found...
year = datetime.datetime.now().strftime('%Y') #
try: #
anniv = int(row['calc']) # eg. ccyy
except Exception as e: #
anniv = 1900 #
msg = f"error: {e}" #
logIt(msg) #
diff1 = int(year) - anniv + 1 #
allText = allText[:temp] + str(diff1) + allText[temp+7:] #
temp = allText.find('+ccyy+') # LOGIC-8
if (temp != -1): # if +ccyy+ was found...
year = datetime.datetime.now().strftime('%Y') #
allText = allText[:temp] + str(year) + allText[temp+6:] #
return allText, imgSrc #
#
# output xml
#
def output_xml(row, mmdd, slot):
allText, imgSrc = process_data(row) #
escText = html.escape(allText) # LOGIC-9
comment = row['comment'] #
escComm = html.escape(comment) #
calc = row['calc'] #
print('Content-Type: text/xml; charset=utf-8') # we're returning an XML response
print('') # end of HTTP header
print('<?xml version="1.0" encoding="utf-8"?>') # start of XML content
print('<result>') # user-defined by me (see JavaScript)
print('<status>1</status>') #
print(f'<debug>{mmdd}</debug>') # the parameter passed in
print(f'<comment>{escComm}</comment>') #
print(f'<calc>{calc}</calc>') #
print(f'<src>{imgSrc}</src>') #
print(f'<text>{escText}</text>') #
print(f'<slot>{slot}</slot>') #
print('</result>') #
#
# output json
#
def output_json(row, mmdd, slot):
allText, imgSrc = process_data(row) # cook the data
print('Content-Type: text/json; charset=utf-8') # we're returning an XML response
print('') # end of HTTP header
del row['text3'] # remove raw
del row['text2']
del row['text1']
row['imgSrc'] = imgSrc # append cooked
row['text'] = allText # append cooked
print(json.dumps(row, indent=1)) # magic happens here
#
def output_plain(row, mmdd, slot): #
allText, imgSrc = process_data(row) #
print('Content-Type: text/plain; charset=utf-8') #
print('') #
print("debug : " + mmdd) #
print("comment: " + row['comment']) # output: comment field
print("calc : " + row['calc']) #
print("src : " + imgSrc) # output: IMG src field
print("text : " + allText) # output: text fields
# ==============================================================
# main block
#
# example URLs:
# Dec-25) https://neilrieck.net/cgi-bin/daily_pix?mmdd=1225&xml=1
# Jan-01) https://neilrieck.net/cgi-bin/daily_pix?mmdd=0101&xml=1
# CO2 ) https://neilrieck.net/cgi-bin/daily_pix?mmdd=8000&xml=1
# ==============================================================
#
# locate named data in cgi field storage
#
def getFld(cfs, label, dflt):
if label not in cfs:
return dflt
else:
return cfs.getvalue(label)
def main():
cfs = cgi.FieldStorage() # convert CGI data into a Python data
xml = getFld(cfs, 'xml', '0') # default to "0" if missing
json = getFld(cfs, 'json', '0') #
slot = getFld(cfs, 'slot', '0') # slot may be useful when caching an image
mmdd = getFld(cfs, 'mmdd', '0212') # default to Charles Darwin's birthday
try:
x = int(mmdd) #
if (x < 0): #
mmdd = "0000" #
if (x > 9999): #
mmdd = "9999" #
except Exception: #
mmdd = "0212" #
#
# start by logging this event (default privs may not allow this)
# caveat: if SELinux is enabled then we may need to write to /var/log
#
try:
snap = datetime.datetime.now() # snapshot of the current time
date = snap.strftime('%Y%m%d') #
fn = f"dp-log/dp_{date}.txt" # all logs go into a daily file
f = open(fn, "a") #
dt = snap.strftime('%Y%m%d%H%M%S') #
f.write(f"time: {dt}\r\n") #
f.write(f"mmdd: {mmdd} xml: {xml} json: {json})\r\n") #
f.close() #
except Exception as e: #
msg = f"error: {e} in logger" #
logIt(msg) #
pass #
#
row = get_record(mmdd) # fetch one record from our database
#
# return data in the requested format
#
if (json == "1"):
output_json(row, mmdd, slot) # output JSON data
elif (xml == "1"):
output_xml(row, mmdd, slot) # output xml data
else:
output_plain(row, mmdd, slot) # output plain data
#
# catch-all
#
if __name__ == '__main__':
main()
#
# === end-of-file
External Links
Back to
Home
Neil Rieck
Waterloo, Ontario, Canada.