]> Cypherpunks repositories - gostls13.git/commitdiff
misc/dashboard: remove old python package dashboard
authorAndrew Gerrand <adg@golang.org>
Thu, 15 Mar 2012 21:20:02 +0000 (08:20 +1100)
committerAndrew Gerrand <adg@golang.org>
Thu, 15 Mar 2012 21:20:02 +0000 (08:20 +1100)
This leaves only the project page, which now resides at the web root.

R=golang-dev, bsiegert, rsc
CC=golang-dev
https://golang.org/cl/5833044

14 files changed:
misc/dashboard/godashboard/_multiprocessing.py [deleted file]
misc/dashboard/godashboard/app.yaml
misc/dashboard/godashboard/auth.py [deleted file]
misc/dashboard/godashboard/const.py
misc/dashboard/godashboard/cron.yaml [deleted file]
misc/dashboard/godashboard/fail-notify.txt [deleted file]
misc/dashboard/godashboard/key.py.dummy [deleted file]
misc/dashboard/godashboard/main.html [deleted file]
misc/dashboard/godashboard/package.html [deleted file]
misc/dashboard/godashboard/package.py [deleted file]
misc/dashboard/godashboard/project-edit.html
misc/dashboard/godashboard/project.html
misc/dashboard/godashboard/project.py [new file with mode: 0644]
misc/dashboard/godashboard/static/style.css

diff --git a/misc/dashboard/godashboard/_multiprocessing.py b/misc/dashboard/godashboard/_multiprocessing.py
deleted file mode 100644 (file)
index 8c66c06..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-# Copyright 2009 The Go Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style
-# license that can be found in the LICENSE file.
-
-import multiprocessing
index 8c767043716dc5c1693734dee5552bb03a19da16..ae28924975f9e5887b845e1ec713274a31708163 100644 (file)
@@ -11,16 +11,5 @@ handlers:
 - url: /static
   static_dir: static
 
-- url: /package
-  script: package.py
-
-- url: /package/daily
-  script: package.py
-  login: admin
-
-- url: /project.*
-  script: package.py
-
-- url: /
-  static_files: main.html
-  upload: main.html
+- url: /(|project(|/login|/edit))
+  script: project.py
diff --git a/misc/dashboard/godashboard/auth.py b/misc/dashboard/godashboard/auth.py
deleted file mode 100644 (file)
index 73a54c0..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-# Copyright 2011 The Go Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style
-# license that can be found in the LICENSE file.
-
-import hmac
-
-# local imports
-import key
-
-def auth(req):
-    k = req.get('key')
-    return k == hmac.new(key.accessKey, req.get('builder')).hexdigest() or k == key.accessKey
-
index b0110c6354de5bd08206f0fb4e81d720c511e6a6..d92d32aa974b7e8e98880e4e2091bd8f3079c602 100644 (file)
@@ -3,11 +3,5 @@
 # license that can be found in the LICENSE file.
 
 mail_from           = "Go Dashboard <builder@golang.org>"
-
 mail_submit_to      = "adg@golang.org"
 mail_submit_subject = "New Project Submitted"
-
-mail_fail_to       = "golang-dev@googlegroups.com"
-mail_fail_reply_to = "golang-dev@googlegroups.com"
-mail_fail_subject  = "%s broken by %s"
-
diff --git a/misc/dashboard/godashboard/cron.yaml b/misc/dashboard/godashboard/cron.yaml
deleted file mode 100644 (file)
index 953b6a1..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-cron:
-- description: daily package maintenance
-  url: /package/daily
-  schedule: every 24 hours
diff --git a/misc/dashboard/godashboard/fail-notify.txt b/misc/dashboard/godashboard/fail-notify.txt
deleted file mode 100644 (file)
index f75d09a..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-Change {{node}} broke the {{builder}} build:
-http://godashboard.appspot.com/log/{{loghash}}
-
-{{desc}}
-
-http://code.google.com/p/go/source/detail?r={{node}}
-
-$ tail -n 100 < log
-{{log}}
diff --git a/misc/dashboard/godashboard/key.py.dummy b/misc/dashboard/godashboard/key.py.dummy
deleted file mode 100644 (file)
index 5b8bab1..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-# Copyright 2009 The Go Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style
-# license that can be found in the LICENSE file.
-
-# Copy this file to key.py after substituting the real key.
-
-# accessKey controls private access to the build server (i.e. to record new
-# builds). It's tranmitted in the clear but, given the low value of the target,
-# this should be sufficient.
-accessKey = "this is not the real key"
diff --git a/misc/dashboard/godashboard/main.html b/misc/dashboard/godashboard/main.html
deleted file mode 100644 (file)
index 4cd98d8..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-  <head>
-    <title>Build Status - Go Dashboard</title>
-    <link rel="stylesheet" type="text/css" href="static/style.css">
-  </head>
-
-  <body>
-    <ul class="menu">
-      <li>Build Status</li>
-      <li><a href="/package">Packages</a></li>
-      <li><a href="/project">Projects</a></li>
-      <li><a href="http://golang.org/">golang.org</a></li>
-    </ul>
-    
-    <h1>Go Dashboard</h1>
-    
-    <h2>Build Status</h2>
-
-    <p class="notice">The build status dashboard has moved to <a href="http://build.golang.org">build.golang.org</a>.</p>
-
-  </body>
-</html>
diff --git a/misc/dashboard/godashboard/package.html b/misc/dashboard/godashboard/package.html
deleted file mode 100644 (file)
index e792162..0000000
+++ /dev/null
@@ -1,79 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-  <head>
-    <title>Packages - Go Dashboard</title>
-    <link rel="stylesheet" type="text/css" href="static/style.css">
-  </head>
-
-  <body>
-    <ul class="menu">
-      <li><a href="/">Build Status</a></li>
-      <li>Packages</li>
-      <li><a href="/project">Projects</a></li>
-      <li><a href="http://golang.org/">golang.org</a></li>
-    </ul>
-
-    <h1>Go Dashboard</h1>
-    
-    <p>
-    Packages listed on this page are written by third parties and 
-    may or may not build or be safe to use.
-    </p>
-
-<!--
-    <p>
-    An "ok" in the <b>build</b> column indicates that the package is
-    <a href="http://golang.org/cmd/goinstall/">goinstallable</a>
-    with the latest
-    <a href="http://golang.org/doc/devel/release.html">release</a> of Go.
-    </p>
--->
-
-    <p>
-    The <b>info</b> column shows the first paragraph from the
-    <a href="http://golang.org/doc/articles/godoc_documenting_go_code.html">package doc comment</a>.
-    </p>
-    
-    <h2>Most Installed Packages (this week)</h2>
-    <table class="alternate" cellpadding="0" cellspacing="0">
-      <tr><th>last install</th><th>count</th><th>build</th><th>path</th><th>info</th></tr>
-      {% for r in by_week_count %}
-        <tr>
-          <td class="time">{{r.last_install|date:"Y-M-d H:i"}}</td>
-          <td class="count">{{r.week_count}}</td>
-<!--          <td class="ok">{% if r.ok %}<a title="{{r.last_ok|date:"Y-M-d H:i"}}">ok</a>{% else %}&nbsp;{% endif %}</td> -->
-          <td class="path"><a href="{{r.web_url}}">{{r.path}}</a></td>
-          <td class="info">{% if r.info %}{{r.info|escape}}{% endif %}</td>
-        </tr>
-      {% endfor %}
-    </table>
-    
-    <h2>Recently Installed Packages</h2>
-    <table class="alternate" cellpadding="0" cellspacing="0">
-      <tr><th>last install</th><th>count</th><th>build</th><th>path</th><th>info</th></tr>
-      {% for r in by_time %}
-        <tr>
-          <td class="time">{{r.last_install|date:"Y-M-d H:i"}}</td>
-          <td class="count">{{r.count}}</td>
-<!--          <td class="ok">{% if r.ok %}<a title="{{r.last_ok|date:"Y-M-d H:i"}}">ok</a>{% else %}&nbsp;{% endif %}</td> -->
-          <td class="path"><a href="{{r.web_url}}">{{r.path}}</a></td>
-          <td class="info">{% if r.info %}{{r.info|escape}}{% endif %}</td>
-        </tr>
-      {% endfor %}
-    </table>
-
-    <h2>Most Installed Packages (all time)</h2>
-    <table class="alternate" cellpadding="0" cellspacing="0">
-      <tr><th>last install</th><th>count</th><th>build</th><th>path</th><th>info</th></tr>
-      {% for r in by_count %}
-        <tr>
-          <td class="time">{{r.last_install|date:"Y-M-d H:i"}}</td>
-          <td class="count">{{r.count}}</td>
-<!--          <td class="ok">{% if r.ok %}<a title="{{r.last_ok|date:"Y-M-d H:i"}}">ok</a>{% else %}&nbsp;{% endif %}</td> -->
-          <td class="path"><a href="{{r.web_url}}">{{r.path}}</a></td>
-          <td class="info">{% if r.info %}{{r.info|escape}}{% endif %}</td>
-        </tr>
-      {% endfor %}
-    </table>
-  </body>
-</html>
diff --git a/misc/dashboard/godashboard/package.py b/misc/dashboard/godashboard/package.py
deleted file mode 100644 (file)
index 7feac82..0000000
+++ /dev/null
@@ -1,429 +0,0 @@
-# Copyright 2010 The Go Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style
-# license that can be found in the LICENSE file.
-
-# This is the server part of the package dashboard.
-# It must be run by App Engine.
-
-from google.appengine.api import mail
-from google.appengine.api import memcache
-from google.appengine.api import taskqueue
-from google.appengine.api import urlfetch
-from google.appengine.api import users
-from google.appengine.ext import db
-from google.appengine.ext import webapp
-from google.appengine.ext.webapp import template
-from google.appengine.ext.webapp.util import run_wsgi_app
-import datetime
-import logging
-import os
-import re
-import sets
-import urllib2
-
-# local imports
-from auth import auth
-import toutf8
-import const
-
-template.register_template_library('toutf8')
-
-# Storage model for package info recorded on server.
-class Package(db.Model):
-    path = db.StringProperty()
-    web_url = db.StringProperty()           # derived from path
-    count = db.IntegerProperty()            # grand total
-    week_count = db.IntegerProperty()       # rolling weekly count
-    day_count = db.TextProperty(default='') # daily count
-    last_install = db.DateTimeProperty()
-
-    # data contributed by gobuilder
-    info = db.StringProperty()  
-    ok = db.BooleanProperty()
-    last_ok = db.DateTimeProperty()
-
-    def get_day_count(self):
-        counts = {}
-        if not self.day_count:
-            return counts
-        for d in str(self.day_count).split('\n'):
-            date, count = d.split(' ')
-            counts[date] = int(count)
-        return counts
-
-    def set_day_count(self, count):
-        days = []
-        for day, count in count.items():
-            days.append('%s %d' % (day, count))
-        days.sort(reverse=True)
-        days = days[:28]
-        self.day_count = '\n'.join(days)
-
-    def inc(self):
-        count = self.get_day_count()
-        today = str(datetime.date.today())
-        count[today] = count.get(today, 0) + 1
-        self.set_day_count(count)
-        self.update_week_count(count)
-        self.count += 1
-
-    def update_week_count(self, count=None):
-        if count is None:
-            count = self.get_day_count()
-        total = 0
-        today = datetime.date.today()
-        for i in range(7):
-            day = str(today - datetime.timedelta(days=i))
-            if day in count:
-                total += count[day]
-        self.week_count = total
-
-
-# PackageDaily kicks off the daily package maintenance cron job
-# and serves the associated task queue.
-class PackageDaily(webapp.RequestHandler):
-
-    def get(self):
-        # queue a task to update each package with a week_count > 0
-        keys = Package.all(keys_only=True).filter('week_count >', 0)
-        for key in keys:
-            taskqueue.add(url='/package/daily', params={'key': key.name()})
-
-    def post(self):
-        # update a single package (in a task queue)
-        def update(key):
-            p = Package.get_by_key_name(key)
-            if not p:
-                return
-            p.update_week_count()
-            p.put()
-        key = self.request.get('key')
-        if not key:
-            return
-        db.run_in_transaction(update, key)
-
-class Project(db.Model):
-    name = db.StringProperty(indexed=True)
-    descr = db.StringProperty()
-    web_url = db.StringProperty()
-    package = db.ReferenceProperty(Package)
-    category = db.StringProperty(indexed=True)
-    tags = db.ListProperty(str)
-    approved = db.BooleanProperty(indexed=True)
-
-
-re_bitbucket = re.compile(r'^(bitbucket\.org/[a-z0-9A-Z_.\-]+/[a-zA-Z0-9_.\-]+)(/[a-z0-9A-Z_.\-/]+)?$')
-re_googlecode = re.compile(r'^[a-z0-9\-]+\.googlecode\.com/(svn|hg|git)(/[a-z0-9A-Z_.\-/]+)?$')
-re_github = re.compile(r'^github\.com/[a-z0-9A-Z_.\-]+(/[a-z0-9A-Z_.\-]+)+$')
-re_launchpad = re.compile(r'^launchpad\.net/([a-z0-9A-Z_.\-]+(/[a-z0-9A-Z_.\-]+)?|~[a-z0-9A-Z_.\-]+/(\+junk|[a-z0-9A-Z_.\-]+)/[a-z0-9A-Z_.\-]+)(/[a-z0-9A-Z_.\-/]+)?$')
-
-def vc_to_web(path):
-    if re_bitbucket.match(path):
-        m = re_bitbucket.match(path)
-        check_url = 'http://' + m.group(1) + '/?cmd=heads'
-        web = 'http://' + m.group(1) + '/'
-    elif re_github.match(path):
-        m = re_github_web.match(path)
-        check_url = 'https://raw.github.com/' + m.group(1) + '/' + m.group(2) + '/master/'
-        web = 'http://github.com/' + m.group(1) + '/' + m.group(2) + '/'
-    elif re_googlecode.match(path):
-        m = re_googlecode.match(path)
-        check_url = 'http://'+path
-        if not m.group(2):  # append / after bare '/hg' or '/git'
-            check_url += '/'
-        web = 'http://code.google.com/p/' + path[:path.index('.')]
-    elif re_launchpad.match(path):
-        check_url = web = 'https://'+path
-    else:
-        return False, False
-    return web, check_url
-
-re_bitbucket_web = re.compile(r'bitbucket\.org/([a-z0-9A-Z_.\-]+)/([a-z0-9A-Z_.\-]+)')
-re_googlecode_web = re.compile(r'code.google.com/p/([a-z0-9\-]+)')
-re_github_web = re.compile(r'github\.com/([a-z0-9A-Z_.\-]+)/([a-z0-9A-Z_.\-]+)')
-re_launchpad_web = re.compile(r'launchpad\.net/([a-z0-9A-Z_.\-]+(/[a-z0-9A-Z_.\-]+)?|~[a-z0-9A-Z_.\-]+/(\+junk|[a-z0-9A-Z_.\-]+)/[a-z0-9A-Z_.\-]+)(/[a-z0-9A-Z_.\-/]+)?')
-re_striphttp = re.compile(r'https?://(www\.)?')
-
-def find_googlecode_vcs(path):
-    # Perform http request to path/hg or path/git to check if they're
-    # using mercurial or git.  Otherwise, assume svn.
-    for vcs in ['git', 'hg']:
-        try:
-            response = urlfetch.fetch('http://'+path+vcs, deadline=1)
-            if response.status_code == 200:
-                return vcs
-        except: pass
-    return 'svn'
-
-def web_to_vc(url):
-    url = re_striphttp.sub('', url)
-    m = re_bitbucket_web.match(url)
-    if m:
-        return 'bitbucket.org/'+m.group(1)+'/'+m.group(2)
-    m = re_github_web.match(url)
-    if m:
-        return 'github.com/'+m.group(1)+'/'+m.group(2)
-    m = re_googlecode_web.match(url)
-    if m:
-        path = m.group(1)+'.googlecode.com/'
-        vcs = find_googlecode_vcs(path)
-        return path + vcs
-    m = re_launchpad_web.match(url)
-    if m:
-        return m.group(0)
-    return False
-
-MaxPathLength = 100
-CacheTimeout = 3600
-
-class PackagePage(webapp.RequestHandler):
-    def get(self):
-        if self.request.get('fmt') == 'json':
-            return self.json()
-
-        html = memcache.get('view-package')
-        if not html:
-            tdata = {}
-
-            q = Package.all().filter('week_count >', 0)
-            q.order('-week_count')
-            tdata['by_week_count'] = q.fetch(50)
-
-            q = Package.all()
-            q.order('-last_install')
-            tdata['by_time'] = q.fetch(20)
-
-            q = Package.all()
-            q.order('-count')
-            tdata['by_count'] = q.fetch(100)
-
-            path = os.path.join(os.path.dirname(__file__), 'package.html')
-            html = template.render(path, tdata)
-            memcache.set('view-package', html, time=CacheTimeout)
-
-        self.response.headers['Content-Type'] = 'text/html; charset=utf-8'
-        self.response.out.write(html)
-
-    def json(self):
-        json = memcache.get('view-package-json')
-        if not json:
-            q = Package.all()
-            s = '{"packages": ['
-            sep = ''
-            for r in q:
-                s += '%s\n\t{"path": "%s", "last_install": "%s", "count": "%s"}' % (sep, r.path, r.last_install, r.count)
-                sep = ','
-            s += '\n]}\n'
-            json = s
-            memcache.set('view-package-json', json, time=CacheTimeout)
-        self.response.set_status(200)
-        self.response.headers['Content-Type'] = 'text/plain; charset=utf-8'
-        self.response.out.write(json)
-
-    def can_get_url(self, url):
-        try:
-            urllib2.urlopen(urllib2.Request(url))
-            return True
-        except:
-            return False
-
-    def is_valid_package_path(self, path):
-        return (re_bitbucket.match(path) or
-            re_googlecode.match(path) or
-            re_github.match(path) or
-            re_launchpad.match(path))
-
-    def record_pkg(self, path):
-        # sanity check string
-        if not path or len(path) > MaxPathLength or not self.is_valid_package_path(path):
-            return False
-
-        # look in datastore
-        key = 'pkg-' + path
-        p = Package.get_by_key_name(key)
-        if p is None:
-            # not in datastore - verify URL before creating
-            web, check_url = vc_to_web(path)
-            if not web:
-                logging.error('unrecognized path: %s', path)
-                return False
-            if not self.can_get_url(check_url):
-                logging.error('cannot get %s', check_url)
-                return False
-            p = Package(key_name = key, path = path, count = 0, web_url = web)
-
-        if auth(self.request):
-            # builder updating package metadata
-            p.info = self.request.get('info')
-            p.ok = self.request.get('ok') == "true"
-            if p.ok:
-                p.last_ok = datetime.datetime.utcnow()
-        else:
-            # goinstall reporting an install
-            p.inc()
-            p.last_install = datetime.datetime.utcnow()
-
-        # update package object
-        p.put()
-        return True
-
-    def post(self):
-        path = self.request.get('path')
-        ok = db.run_in_transaction(self.record_pkg, path)
-        if ok:
-            self.response.set_status(200)
-            self.response.out.write('ok')
-        else:
-            logging.error('invalid path in post: %s', path)
-            self.response.set_status(500)
-            self.response.out.write('not ok')
-
-class ProjectPage(webapp.RequestHandler):
-
-    def get(self):
-        admin = users.is_current_user_admin()
-        if self.request.path == "/project/login":
-            self.redirect(users.create_login_url("/project"))
-        elif self.request.path == "/project/logout":
-            self.redirect(users.create_logout_url("/project"))
-        elif self.request.path == "/project/edit" and admin:
-            self.edit()
-        elif self.request.path == "/project/assoc" and admin:
-            self.assoc()
-        else:
-            self.list()
-
-    def assoc(self):
-        projects = Project.all()
-        for p in projects:
-            if p.package:
-                continue
-            path = web_to_vc(p.web_url)
-            if not path:
-                continue
-            pkg = Package.get_by_key_name("pkg-"+path)
-            if not pkg:
-                self.response.out.write('no: %s %s<br>' % (p.web_url, path))
-                continue
-            p.package = pkg
-            p.put()
-            self.response.out.write('yes: %s %s<br>' % (p.web_url, path))
-
-    def post(self):
-        if self.request.path == "/project/edit":
-            self.edit(True)
-        else:
-            data = dict(map(lambda x: (x, self.request.get(x)), ["name","descr","web_url"]))
-            if reduce(lambda x, y: x or not y, data.values(), False):
-                data["submitMsg"] = "You must complete all the fields."
-                self.list(data)
-                return
-            p = Project.get_by_key_name("proj-"+data["name"])
-            if p is not None:
-                data["submitMsg"] = "A project by this name already exists."
-                self.list(data)
-                return
-            p = Project(key_name="proj-"+data["name"], **data)
-            p.put()
-               
-            path = os.path.join(os.path.dirname(__file__), 'project-notify.txt')
-            mail.send_mail(
-                sender=const.mail_from,
-                to=const.mail_submit_to,
-                subject=const.mail_submit_subject,
-                body=template.render(path, {'project': p}))
-
-            self.list({"submitMsg": "Your project has been submitted."})
-
-    def list(self, additional_data={}):
-        cache_key = 'view-project-data'
-        tag = self.request.get('tag', None)
-        if tag:
-            cache_key += '-'+tag
-        data = memcache.get(cache_key)
-        admin = users.is_current_user_admin()
-        if admin or not data:
-            projects = Project.all().order('category').order('name')
-            if not admin:
-                projects = projects.filter('approved =', True)
-            projects = list(projects)
-
-            tags = sets.Set()
-            for p in projects:
-                for t in p.tags:
-                    tags.add(t)
-
-            if tag:
-                projects = filter(lambda x: tag in x.tags, projects)
-
-            data = {}
-            data['tag'] = tag
-            data['tags'] = tags
-            data['projects'] = projects 
-            data['admin']= admin
-            if not admin:
-                memcache.set(cache_key, data, time=CacheTimeout)
-
-        for k, v in additional_data.items():
-            data[k] = v
-
-        self.response.headers['Content-Type'] = 'text/html; charset=utf-8'
-        path = os.path.join(os.path.dirname(__file__), 'project.html')
-        self.response.out.write(template.render(path, data))
-
-    def edit(self, save=False):
-        if save:
-            name = self.request.get("orig_name")
-        else:
-            name = self.request.get("name")
-
-        p = Project.get_by_key_name("proj-"+name)
-        if not p:
-            self.response.out.write("Couldn't find that Project.")
-            return
-
-        if save:
-            if self.request.get("do") == "Delete":
-                p.delete()
-            else:
-                pkg_name = self.request.get("package", None)
-                if pkg_name:
-                    pkg = Package.get_by_key_name("pkg-"+pkg_name)
-                    if pkg:
-                        p.package = pkg.key()
-                for f in ['name', 'descr', 'web_url', 'category']:
-                    setattr(p, f, self.request.get(f, None))
-                p.approved = self.request.get("approved") == "1"
-                p.tags = filter(lambda x: x, self.request.get("tags", "").split(","))
-                p.put()
-            memcache.delete('view-project-data')
-            self.redirect('/project')
-            return
-
-        # get all project categories and tags
-        cats, tags = sets.Set(), sets.Set()
-        for r in Project.all():
-            cats.add(r.category)
-            for t in r.tags:
-                tags.add(t)
-
-        self.response.headers['Content-Type'] = 'text/html; charset=utf-8'
-        path = os.path.join(os.path.dirname(__file__), 'project-edit.html')
-        self.response.out.write(template.render(path, { 
-            "taglist": tags, "catlist": cats, "p": p, "tags": ",".join(p.tags) }))
-
-    def redirect(self, url):
-        self.response.set_status(302)
-        self.response.headers.add_header("Location", url)
-
-def main():
-    app = webapp.WSGIApplication([
-        ('/package', PackagePage),
-        ('/package/daily', PackageDaily),
-        ('/project.*', ProjectPage),
-        ], debug=True)
-    run_wsgi_app(app)
-
-if __name__ == '__main__':
-    main()
index ce18fb3fbd8fb6b850c9f08050498d167aa02e2e..8ca5b22a319db40b09695cd4c1f29c796c59ddc1 100644 (file)
@@ -19,8 +19,6 @@ Tags: (comma-separated)<br/>
 <input type="text" id="tags" name="tags" value="{{tags}}"><br/>
 Web URL:<br/>
 <input type="text" name="web_url" value="{{p.web_url|escape}}"><br/>
-Package URL: (to link to a goinstall'd package)<br/>
-<input type="text" name="package" value="{{p.package.path|escape}}"><br/>
 Approved: <input type="checkbox" name="approved" value="1" {% if p.approved %}checked{% endif %}><br/>
 <br/>
 <input type="submit" name="do" value="Save">
index 4fe1741c6d79b904db739595dbc51f3bbe48a74b..9aa071cf293b507145b28751dc5c14454d525874 100644 (file)
@@ -1,23 +1,12 @@
 <!DOCTYPE HTML>
 <html>
   <head>
-    <title>Projects - Go Dashboard</title>
+    <title>Go Projects</title>
     <link rel="stylesheet" type="text/css" href="static/style.css">
-    <style>
-      .unapproved a.name { color: red }
-      .tag { font-size: 0.8em; color: #666 }
-    </style>
   </head>
 
   <body>
-    <ul class="menu">
-      <li><a href="/">Build Status</a></li>
-      <li><a href="/package">Packages</a></li>
-      <li>Projects</li>
-      <li><a href="http://golang.org/">golang.org</a></li>
-    </ul>
-
-    <h1>Go Dashboard</h1>
+    <h1>Go Projects</h1>
     
     <p>
     These are external projects and not endorsed or supported by the Go project.
@@ -80,6 +69,5 @@
       {% endfor %}
     </ul>
 
-
   </body>
 </html>
diff --git a/misc/dashboard/godashboard/project.py b/misc/dashboard/godashboard/project.py
new file mode 100644 (file)
index 0000000..7413b7d
--- /dev/null
@@ -0,0 +1,157 @@
+# Copyright 2010 The Go Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file.
+
+from google.appengine.api import mail
+from google.appengine.api import memcache
+from google.appengine.api import users
+from google.appengine.ext import db
+from google.appengine.ext import webapp
+from google.appengine.ext.webapp import template
+from google.appengine.ext.webapp.util import run_wsgi_app
+import os
+import sets
+
+# local imports
+import toutf8
+import const
+
+template.register_template_library('toutf8')
+
+class Project(db.Model):
+    name = db.StringProperty(indexed=True)
+    descr = db.StringProperty()
+    web_url = db.StringProperty()
+    package = db.ReferenceProperty(Package)
+    category = db.StringProperty(indexed=True)
+    tags = db.ListProperty(str)
+    approved = db.BooleanProperty(indexed=True)
+
+CacheTimeout = 3600
+
+class ProjectPage(webapp.RequestHandler):
+
+    def get(self):
+        admin = users.is_current_user_admin()
+        if self.request.path == "/project/login":
+            self.redirect(users.create_login_url("/project"))
+        elif self.request.path == "/project/edit" and admin:
+            self.edit()
+        else:
+            self.list()
+
+    def post(self):
+        if self.request.path == "/project/edit":
+            self.edit(True)
+        else:
+            data = dict(map(lambda x: (x, self.request.get(x)), ["name","descr","web_url"]))
+            if reduce(lambda x, y: x or not y, data.values(), False):
+                data["submitMsg"] = "You must complete all the fields."
+                self.list(data)
+                return
+            p = Project.get_by_key_name("proj-"+data["name"])
+            if p is not None:
+                data["submitMsg"] = "A project by this name already exists."
+                self.list(data)
+                return
+            p = Project(key_name="proj-"+data["name"], **data)
+            p.put()
+               
+            path = os.path.join(os.path.dirname(__file__), 'project-notify.txt')
+            mail.send_mail(
+                sender=const.mail_from,
+                to=const.mail_submit_to,
+                subject=const.mail_submit_subject,
+                body=template.render(path, {'project': p}))
+
+            self.list({"submitMsg": "Your project has been submitted."})
+
+    def list(self, additional_data={}):
+        cache_key = 'view-project-data'
+        tag = self.request.get('tag', None)
+        if tag:
+            cache_key += '-'+tag
+        data = memcache.get(cache_key)
+        admin = users.is_current_user_admin()
+        if admin or not data:
+            projects = Project.all().order('category').order('name')
+            if not admin:
+                projects = projects.filter('approved =', True)
+            projects = list(projects)
+
+            tags = sets.Set()
+            for p in projects:
+                for t in p.tags:
+                    tags.add(t)
+
+            if tag:
+                projects = filter(lambda x: tag in x.tags, projects)
+
+            data = {}
+            data['tag'] = tag
+            data['tags'] = tags
+            data['projects'] = projects 
+            data['admin']= admin
+            if not admin:
+                memcache.set(cache_key, data, time=CacheTimeout)
+
+        for k, v in additional_data.items():
+            data[k] = v
+
+        self.response.headers['Content-Type'] = 'text/html; charset=utf-8'
+        path = os.path.join(os.path.dirname(__file__), 'project.html')
+        self.response.out.write(template.render(path, data))
+
+    def edit(self, save=False):
+        if save:
+            name = self.request.get("orig_name")
+        else:
+            name = self.request.get("name")
+
+        p = Project.get_by_key_name("proj-"+name)
+        if not p:
+            self.response.out.write("Couldn't find that Project.")
+            return
+
+        if save:
+            if self.request.get("do") == "Delete":
+                p.delete()
+            else:
+                pkg_name = self.request.get("package", None)
+                if pkg_name:
+                    pkg = Package.get_by_key_name("pkg-"+pkg_name)
+                    if pkg:
+                        p.package = pkg.key()
+                for f in ['name', 'descr', 'web_url', 'category']:
+                    setattr(p, f, self.request.get(f, None))
+                p.approved = self.request.get("approved") == "1"
+                p.tags = filter(lambda x: x, self.request.get("tags", "").split(","))
+                p.put()
+            memcache.delete('view-project-data')
+            self.redirect('/project')
+            return
+
+        # get all project categories and tags
+        cats, tags = sets.Set(), sets.Set()
+        for r in Project.all():
+            cats.add(r.category)
+            for t in r.tags:
+                tags.add(t)
+
+        self.response.headers['Content-Type'] = 'text/html; charset=utf-8'
+        path = os.path.join(os.path.dirname(__file__), 'project-edit.html')
+        self.response.out.write(template.render(path, { 
+            "taglist": tags, "catlist": cats, "p": p, "tags": ",".join(p.tags) }))
+
+    def redirect(self, url):
+        self.response.set_status(302)
+        self.response.headers.add_header("Location", url)
+
+def main():
+    app = webapp.WSGIApplication([
+        ('/.*', ProjectPage),
+        ], debug=True)
+    run_wsgi_app(app)
+
+if __name__ == '__main__':
+    main()
index 0ce583a54c98f62e1ad396a4c6e3504e2bba899d..ad7038c3d26ee42faa53d1fd790061a55c6a1faa 100644 (file)
@@ -127,3 +127,10 @@ td.time {
 .notice a {
        color: #FF6;
 }
+.unapproved a.name {
+       color: red;
+}
+.tag {
+       font-size: 0.8em;
+       color: #666;
+}