GAEでアクセスカウンター


Google App Engine でアクセスカウンターを作ってみました。

#counter.py
#encoding:shuft-jis
from __future__ import with_statement, division
import os
import cgi
import datetime
import wsgiref.handlers

from google.appengine.ext import db
from google.appengine.api import users
from google.appengine.ext import webapp
from google.appengine.ext.webapp import template

class JST(datetime.tzinfo):
    def utcoffset(self, dt):return datetime.timedelta(hours=9)
    def dst(self, dt):return datetime.timedelta(0)
    def tzname(self, dt):return "JDT"

class UTC(datetime.tzinfo):
    def utcoffset(self, dt):return datetime.timedelta(0)
    def tzname(self, dt):return "UTC"
    def dst(self, dt):return datetime.timedelta(0)

UTC = UTC()
TZ = JST()

class Accessing(db.Model):
  user = db.UserProperty()
  datetime = db.DateTimeProperty(auto_now_add=False)
  date     = db.DateProperty(auto_now_add=False)
  ip   = db.StringProperty()

class CountOfDate(db.Model):
  date = db.DateProperty(auto_now_add=False)
  count = db.IntegerProperty(required=True, default=0)

class TotalDelta(db.Model):
  value = db.IntegerProperty(required=True, default=0)

class Counter(object):
  def append(self, _ip=None):
    accessing = Accessing()
    now = datetime.datetime.now(TZ)
    accessing.datetime = now
    accessing.date = now.date()
    accessing.ip = os.environ['REMOTE_ADDR'] if _ip is None else _ip
    accessing.put()
    self._update_today_count()
  
  def set_total(self, v):
    m = self._total_delta_model()
    m.value += v - self.total()
    m.put()
  
  def total(self):
    query = CountOfDate.all()
    return sum(c.count for c in query) + self._total_delta()
  
  def _total_delta_model(self):
    totals = list(TotalDelta.all())
    if not totals:
      t = TotalDelta()
      t.value = 0
      t.put()
      return t
    else:
      assert len(totals) == 1
      return totals[0]
  
  def _total_delta(self):
    return self._total_delta_model().value
  
  def _today_model(self):
    return self._theday_model(self._today_date())
  
  def _update_today_count(self):
    self._update_theday_count(self._today_date())
  
  def _update_theday_count(self, date):
    from operator import attrgetter
    query = Accessing.all().filter("date =", date)
    count = len(set(q.ip for q in query))
    m = self._theday_model(date)
    m.count = count
    m.put()
  
  def _theday_model(self, date):
    query = CountOfDate.all().filter("date =", date)
    q = list(query)
    if not q:
      c = CountOfDate()
      c.date = date
      c.count = 0
      c.put()
      return c
    else:
      assert len(list(q)) == 1
      return q[0]
  
  def _today_date(self):
    return datetime.datetime.now(TZ).date()
  
  def today(self):
    return self._today_model().count
  
  def yesterday(self):
    d = self._today_date() + datetime.timedelta(days=1)
    return self._theday_model(d).count
        
  def ithis_week(self):
    for i in xrange(-6, 1):
      d = self._today_date() + datetime.timedelta(days=i)
      yield self._theday_model(d).count
  
  def this_week(self):
    return list(self.ithis_week())

class Hidden(webapp.RequestHandler):
  "カウントアップのみ、表示しない"
  def get(self):
    Counter().append(self.request.get("_ip"))
    path = os.path.join(os.path.dirname(__file__), 'counter-hidden.html')
    self.response.out.write(template.render(path, {}))
  
class Page(webapp.RequestHandler):
  def get(self):
    Counter().append(self.request.get("_ip"))
    counts = Counter().this_week()
    m = max(counts)
    graph = [{"value":c, "height":(100 * c) // m} for c in counts]
    
    template_values = dict(
      total=Counter().total(),
      today=Counter().today(),
      yesterday=Counter().yesterday(),
      graph=graph,
    )
    path = os.path.join(os.path.dirname(__file__), 'counter.html')
    self.response.out.write(template.render(path, template_values))

class SetTotal(webapp.RequestHandler):
  def get(self):
    Counter().set_total(int(self.request.get('value')))
    self.redirect('/')

application = webapp.WSGIApplication([
  ("/", Page),
  ("/hidden", Hidden),
  ("/settotal", SetTotal)
], debug=True)

def main():
  wsgiref.handlers.CGIHandler().run(application)


if __name__ == '__main__':
  main()
#app.yaml
application: counter-is-none
version: 1
runtime: python
api_version: 1

handlers:
- url: /css
  static_dir: css
- url: .*
  script: counter.py



上記に加え、テンプレートにcounter-hidden.htmlとcounter.htmlが必要です。



キャッシュとかの使い方をまだ読んでいません。
ので、上のコードは遅いかも知れないです。