summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--README16
-rw-r--r--TODO13
-rw-r--r--__init__.py (renamed from templates/__init__.py)0
-rw-r--r--django.wsgi9
-rwxr-xr-xgen-patch-info.py18
-rwxr-xr-xmanage.py11
-rw-r--r--patchtracker/CacheObject.py63
-rw-r--r--patchtracker/ComplexQueries.py29
-rw-r--r--patchtracker/DB.py193
-rw-r--r--patchtracker/DebTarHandler.py27
-rw-r--r--patchtracker/DiffGzHandler.py75
-rw-r--r--patchtracker/Patch.py76
-rw-r--r--patchtracker/PtsIndex.py4
-rwxr-xr-xpatchtracker/ReqHandler.py222
-rwxr-xr-xpatchtracker/SourceArchive.py79
-rwxr-xr-xpatchtracker/Templates.py103
-rw-r--r--patchtracker/admin.py7
-rw-r--r--patchtracker/models.py89
-rw-r--r--patchtracker/tests.py23
-rw-r--r--patchtracker/urls.py18
-rw-r--r--patchtracker/views.py75
-rwxr-xr-xreprepro/conf/diffsonly.py8
-rw-r--r--settings.py88
-rw-r--r--templates/base.html (renamed from templates/skeleton.tmpl)18
-rw-r--r--templates/frontpage.html (renamed from templates/frontpage.tmpl)30
-rw-r--r--templates/letter_toc.tmpl34
-rw-r--r--templates/package_vers.html148
-rwxr-xr-xtemplates/package_vers.tmpl149
-rw-r--r--templates/patch_view.html24
-rw-r--r--templates/patch_view.tmpl33
-rw-r--r--templates/searchresults.html33
-rw-r--r--templates/searchresults.tmpl34
-rw-r--r--urls.py15
34 files changed, 733 insertions, 1032 deletions
diff --git a/.gitignore b/.gitignore
index f58475c..45b9ab4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,6 +4,7 @@ templates/*.py.bak
.*.swp
*.db
/localconfig.py
+/localsettings.py
/cache/
pts-index.json.gz
profile.out
diff --git a/README b/README
index b2cce72..6ff74e5 100644
--- a/README
+++ b/README
@@ -8,10 +8,9 @@ required software:
* (for now) patchutils
* (for now) diffstat
-* python
-* python-cheetah
+* python-django
* python-pygments
-* python-pysqlite2
+* python-simplejson
* python-debian
* (optional) reprepro
* an httpd with wsgi support (i.e. apache2+libapache2-mod-wsgi)
@@ -37,6 +36,9 @@ of placing your site config in a file "localconfig.py" in the top level
directory (the one containing this README). The Conf module will override
its built in settings with settings in this location.
+similarly, any changes needed to the default django settings.py can be done
+in a localsettings.py (i.e. override the path of the database file, etc).
+
--------------------
overall design/scheme
--------------------
@@ -60,11 +62,3 @@ of this script helpful (UTSL)
the individual patches and files are still currently taken directly
from the source archives and mangled via utlities in the diffstat
and patchutils package.
-
-the primary port-of-call for all requests of dynamic data go through
-pagehandler.py, which then delegates the request to a CmdHandler, which
-in turn resolves the request to a specific Cmd subclass, which usually
-corresponds to a specific view.
-
-the webpages are generated from cheetah based templates in ./templates.
-
diff --git a/TODO b/TODO
index f0cce67..b0bda09 100644
--- a/TODO
+++ b/TODO
@@ -1,5 +1,18 @@
welcome to the TODO file.
+== django-rewrite TODO ==
+
+ - this is still a work in progress
+ - breadcrumbs on django implemented templates
+ - throw an error if the diff.gz isn't available
+ - when only one package matches at the package/foo$ url, redirect to pkg/vers
+ - Last-Modified header would be good
+ - catch notfound exceptions in gen-patch-info
+ - handle pages where the argument isn't passed in the url (i.e. email/$)
+ - Server 500 page
+ - jump command
+ - finish django impl with gen-patch-info
+
== short-range TODO ==
better handling of more exotic patch systems
diff --git a/templates/__init__.py b/__init__.py
index e69de29..e69de29 100644
--- a/templates/__init__.py
+++ b/__init__.py
diff --git a/django.wsgi b/django.wsgi
new file mode 100644
index 0000000..5b30e89
--- /dev/null
+++ b/django.wsgi
@@ -0,0 +1,9 @@
+import os
+import sys
+
+os.environ['DJANGO_SETTINGS_MODULE'] = 'settings'
+if 'PWD' in os.environ:
+ sys.path = [os.environ['PWD']] + sys.path
+
+import django.core.handlers.wsgi
+application = django.core.handlers.wsgi.WSGIHandler()
diff --git a/gen-patch-info.py b/gen-patch-info.py
index 3b5847b..17b2a2a 100755
--- a/gen-patch-info.py
+++ b/gen-patch-info.py
@@ -8,14 +8,14 @@ import gzip
import errno
import simplejson
+os.environ['DJANGO_SETTINGS_MODULE'] = 'settings'
+import patchtracker.models
+
import patchtracker.Conf as Conf
-from patchtracker.SourceArchive import Archive, SourcePackage
-from patchtracker.DB import PatchTrackerDB
+from patchtracker.SourceArchive import Archive
from patchtracker.PtsIndex import PtsIndexFile
if __name__ == '__main__':
- db = PatchTrackerDB()
- os.system("cheetah compile templates/skeleton")
opts,args = getopt.getopt(sys.argv[1:], "ais:p:")
suites = None
packages = None
@@ -43,15 +43,15 @@ if __name__ == '__main__':
print a
for s in a.suites(filter=suites):
print "suite: ",s
- db.saveSuite(s)
+ suite = patchtracker.models.RepositorySuite.objects.get(name=s)
for c in a.components(s):
print "\tcomponent:",c
- db.saveComponent(c)
+ component = patchtracker.models.RepositoryComponent.objects.get(name=c)
for p in a.sourcepackages(s, c, filter=packages):
print "\t\tpackage:",p
- db.saveSourcePackage(p)
- db.relateSourcePackage(name=p.name, version=p.version, suite=s,
- component=c)
+ m = patchtracker.models.SourcePackageMapping.objects.get(suite=suite, component=component, package__name=p.name)
+ m.package = p
+ m.save()
if gen_pts_index:
pts_idx.add(p)
diff --git a/manage.py b/manage.py
new file mode 100755
index 0000000..bcdd55e
--- /dev/null
+++ b/manage.py
@@ -0,0 +1,11 @@
+#!/usr/bin/python
+from django.core.management import execute_manager
+try:
+ import settings # Assumed to be in the same directory.
+except ImportError:
+ import sys
+ sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)
+ sys.exit(1)
+
+if __name__ == "__main__":
+ execute_manager(settings)
diff --git a/patchtracker/CacheObject.py b/patchtracker/CacheObject.py
deleted file mode 100644
index c37c3b5..0000000
--- a/patchtracker/CacheObject.py
+++ /dev/null
@@ -1,63 +0,0 @@
-import errno
-import gzip
-import md5
-import os
-
-import Conf
-
-class CacheMissException (Exception):
- pass
-
-class CacheObject:
- """ A CacheObject is a compressed on-disk version of a serialized object.
- """
- def __init__ (self, key=None, keys=None):
- self.obj = None
- #print "CacheObject: key=%s keys=%s"%(key, keys)
- if not key and not keys:
- raise "CacheObject needs at least one of key, keys"
- checksum = md5.md5()
- if key:
- checksum.update(key)
- if keys:
- for k in keys:
- checksum.update(str(k))
- self.path = os.path.sep.join([Conf.cachedir, checksum.hexdigest()])
-
- def get(self):
- if not self.obj:
- try:
- if Conf.cachecompress:
- self.obj = gzip.GzipFile(self.path).read()
- else:
- self.obj = file(self.path).read()
- except IOError, e:
- if e.errno != errno.ENOENT:
- raise e
- else:
- #print "CacheObject: cache miss"
- raise CacheMissException("Object not present in cache")
- #print "CacheObject: cache hit"
- return self.obj
-
- def put(self, obj):
- self.obj = obj
- #print "CacheObject: cache put"
- try:
- if Conf.cachecompress:
- gzip.GzipFile(self.path, "wb").write(str(self.obj))
- else:
- file(self.path, "wb").write(str(self.obj))
- except Exception, e:
- os.unlink(self.path)
- raise e
-
-if __name__ == '__main__':
- co = CacheObject( key="magic" )
- try:
- print "going to try to read an object before it exists"
- print "first line:", co.get().split()[0]
- except CacheMissException:
- print "file was missing as expected. now let's try to put it and fetch it"
- co.put(file("/etc/passwd").read())
- print "first line:", co.get().split()[0]
diff --git a/patchtracker/ComplexQueries.py b/patchtracker/ComplexQueries.py
new file mode 100644
index 0000000..be7e30f
--- /dev/null
+++ b/patchtracker/ComplexQueries.py
@@ -0,0 +1,29 @@
+import models
+
+class PackageIndexQuery:
+ def __init__( self, queryobj ):
+ self.dists = [d.name for d in models.RepositorySuite.objects.all()]
+ self.index = {}
+ self.packages = []
+ self.queryobj = queryobj
+
+ for mapping in queryobj:
+ self._addPackageMapping(mapping.package, mapping.suite)
+
+ def _addPackageMapping( self, package, suite ):
+ if package.name not in self.index:
+ self.index[package.name] = [None] * len(self.dists)
+ self.packages.append(package)
+ self.index[package.name][self.dists.index(suite.name)] = package
+
+ def __iter__( self ):
+ for pkg in self.packages:
+ yield { 'package':pkg, 'entries':self.index[pkg.name] }
+
+class PackageIndex (PackageIndexQuery):
+ def __init__( self, index ):
+ mappings = models.SourcePackageMapping.objects.order_by('package__name').filter( package__name=index )
+ if not mappings:
+ mappings = models.SourcePackageMapping.objects.order_by('package__name').filter( package__name__startswith=index )
+
+ PackageIndexQuery.__init__(self, mappings)
diff --git a/patchtracker/DB.py b/patchtracker/DB.py
deleted file mode 100644
index 0bb0966..0000000
--- a/patchtracker/DB.py
+++ /dev/null
@@ -1,193 +0,0 @@
-from pysqlite2 import dbapi2 as sqlite
-import Conf
-import os
-import errno
-
-import patchtracker.SourceArchive as SourceArchive
-
-def srcpkg_factory(cursor, row):
- d = {}
- for idx, col in enumerate(cursor.description):
- d[col[0]] = row[idx]
-
- info = {}
- info['Files'] = []
- if d['diffgz_name']:
- diffgz = {'name':d['diffgz_name'], 'size':d['diffgz_size'],
- 'md5sum':d['diffgz_md5sum'] }
- info['Files'].append(diffgz)
- if d['debtar_name']:
- debtar = {'name':d['debtar_name'], 'size':d['debtar_size'],
- 'md5sum':d['debtar_md5sum'] }
- info['Files'].append(debtar)
-
- colmap = {'name':'Package','version':'Version','format':'Format',
- 'loc':'Directory','maintainer':'Maintainer','uploaders':'Uploaders'}
-
- for col,field in colmap.iteritems():
- info[field] = d[col]
- return SourceArchive.SourcePackage(info)
-
-def srcpkg_collection_factory(cursor, row):
- d = {}
- rest = {}
- for idx, col in enumerate(cursor.description):
- if not d.has_key(col[0]):
- d[col[0]] = row[idx]
- else:
- rest[col[0]] = row[idx]
-
- info = {}
- info['Files'] = []
- if d['diffgz_name']:
- diffgz = {'name':d['diffgz_name'], 'size':d['diffgz_size'],
- 'md5sum':d['diffgz_md5sum'] }
- info['Files'].append(diffgz)
- if d['debtar_name']:
- debtar = {'name':d['debtar_name'], 'size':d['debtar_size'],
- 'md5sum':d['debtar_md5sum'] }
- info['Files'].append(debtar)
-
- colmap = {'name':'Package','version':'Version','format':'Format',
- 'loc':'Directory','maintainer':'Maintainer','uploaders':'Uploaders'}
-
- for col,field in colmap.iteritems():
- info[field] = d[col]
- return (SourceArchive.SourcePackage(info), rest)
-
-
-class PatchTrackerDB:
- def __init__(self, dbname=Conf.database):
- self.db = sqlite.connect(dbname)
- cursor = self.db.cursor()
- cursor.execute("SELECT * FROM sqlite_master WHERE name='packages'")
- if not cursor.fetchone():
- print "repopulating empty database..."
- nextcmd = ""
- for l in file(Conf.sqlschema).readlines():
- if len(l.strip()):
- nextcmd += " " + l.strip()
- else:
- cursor.execute(nextcmd)
- nextcmd = ""
-
- def setFactory(self, factory):
- self.db.row_factory = factory
-
- def saveSourcePackage(self, srcpkg):
- cursor = self.db.cursor()
- #print "creating new record for",srcpkg
- q = "INSERT OR IGNORE INTO packages \
- (name,format,loc,version,diffgz_name,diffgz_size,diffgz_md5sum,debtar_name,debtar_size,debtar_md5sum,maintainer,uploaders) \
- VALUES (?,?,?,?,?,?,?,?,?,?,?,?)"
- cursor.execute(q, (srcpkg.name,srcpkg.format,srcpkg.loc,
- srcpkg.version,srcpkg.diffgz_name,srcpkg.diffgz_size,
- srcpkg.diffgz_md5sum,
- srcpkg.debtar_name,srcpkg.debtar_size,
- srcpkg.debtar_md5sum,
- srcpkg.maintainers,srcpkg.uploaders))
-
- def saveSuite(self, suite):
- q = "INSERT INTO suites (name) VALUES (?)"
- cursor = self.db.cursor()
- cursor.execute(q, (suite,))
-
- def saveComponent(self, component):
- q = "INSERT INTO components (name) VALUES (?)"
- cursor = self.db.cursor()
- cursor.execute(q, (component,))
-
- def findCollection(self, package="%", version=None, email=None):
- oldfactory = self.db.row_factory
- self.db.row_factory = srcpkg_collection_factory
- cursor = self.db.cursor()
- toc = SourceArchive.SourcePackageIndex()
- q = "SELECT * FROM packages AS p,package_rel_map AS m,suites AS s \
- WHERE p.name LIKE ? AND p.id = m.package_id AND m.suite_id = s.id"
- qargs = (package,)
- if version:
- q += " AND p.version = ?"
- qargs += (version,)
- if email:
- q += " AND ( p.maintainer like ? OR p.uploaders like ? )"
- qargs += ("%%%s%%"%(email),"%%%s%%"%(email))
-
- cursor.execute(q, qargs)
- # use srcpkg_factory to fetch sourcepackages, once per suite
- for srcpkg,rest in cursor.fetchall():
- toc.ins(srcpkg, rest["name"])
- self.db.row_factory = oldfactory
- return toc
-
- def findLetterToc(self, letter):
- return self.findCollection(package=letter+"%").getletter(letter)
-
- def findIndices(self):
- indices = []
- cursor = self.db.cursor()
- q1 = "SELECT DISTINCT SUBSTR(name,1,1) FROM packages \
- WHERE name NOT LIKE 'lib%'"
- q2 = "SELECT DISTINCT SUBSTR(name,1,4) FROM packages \
- WHERE name LIKE 'lib%'"
-
- for q in [q1, q2]:
- cursor.execute(q)
- for idx in cursor.fetchall():
- indices.append(idx[0])
-
- indices.sort()
- return indices
-
- def relateSourcePackage(self, name, version, suite, component):
- q = "INSERT INTO package_rel_map \
- (package_id,suite_id,component_id,marked) \
- VALUES ((SELECT id FROM packages WHERE name=? AND version=?), \
- (SELECT id FROM suites WHERE name=?), \
- (SELECT id FROM components WHERE name=?), 1)"
- cursor = self.db.cursor()
- cursor.execute(q, (name, version, suite, component))
-
- def findDiffGz(self, pkgname, version):
- q = "SELECT diffgz_name,loc FROM packages WHERE name=? AND version=?"
- cursor = self.db.cursor()
- cursor.execute(q, (pkgname, version))
- try:
- diffgz,loc = cursor.fetchone()
- return os.sep.join([Conf.archive_root, loc, diffgz])
- except:
- return None
-
- def findDebTar(self, pkgname, version):
- q = "SELECT debtar_name,loc FROM packages WHERE name=? AND version=?"
- cursor = self.db.cursor()
- cursor.execute(q, (pkgname, version))
- try:
- debtar,loc = cursor.fetchone()
- return os.sep.join([Conf.archive_root, loc, debtar])
- except:
- return None
-
- def prune(self):
- q = "DELETE FROM package_rel_map WHERE marked != 1"
- cursor = self.db.cursor()
- cursor.execute(q)
-
- def unmark(self):
- q = "UPDATE package_rel_map SET marked=0"
- cursor = self.db.cursor()
- cursor.execute(q)
-
- def finalize(self):
- self.db.commit()
-
- def __del__(self):
- self.finalize()
-
-
-if __name__ == "__main__":
- print "patch tracker db testing"
- d = PatchTrackerDB(dbname="foo.db")
- sp = SourceArchive.SourcePackage({'Package':'foo','Format':'blah','Directory':'/foo','Version':'1.2.3.4','Files':[]})
- d.saveSourcePackage(sp)
- d.finalize()
-
diff --git a/patchtracker/DebTarHandler.py b/patchtracker/DebTarHandler.py
deleted file mode 100644
index 311e39b..0000000
--- a/patchtracker/DebTarHandler.py
+++ /dev/null
@@ -1,27 +0,0 @@
-import os
-import stat
-import sys
-
-import Patch
-
-class DebTarHandler:
- diff = None
- def __init__(self,fname):
- self.tarfile = fname
- self.size = os.stat(fname)[stat.ST_SIZE]
-
- def series(self):
- return Patch.Quilt30PatchSeries(self.tarfile)
-
-if __name__ == "__main__":
- print "DebTarHandler testing"
- try:
- dh = DebTarHandler(sys.argv[1])
- except IndexError:
- print "usage: %s <diffgz>"%(sys.argv[0])
- sys.exit(1)
-
- print "series:"
- for f,p in dh.series():
- print f
- print p
diff --git a/patchtracker/DiffGzHandler.py b/patchtracker/DiffGzHandler.py
deleted file mode 100644
index 515b3ed..0000000
--- a/patchtracker/DiffGzHandler.py
+++ /dev/null
@@ -1,75 +0,0 @@
-import tempfile
-import sys
-import os
-import stat
-
-from Patch import Patch, PatchSeries
-
-class DiffGzException(Exception):
- pass
-
-class DiffGzHandler:
- diff = None
- def __init__(self,fname):
- self.diff = fname
- self.size = os.stat(fname)[stat.ST_SIZE]
-
- def filterdiff(self, include=None, exclude=None):
- cmd = ["filterdiff","-z","-p","1"]
- if include:
- cmd += [ "-i", include]
- elif exclude:
- cmd += [ "-x", exclude]
- else:
- raise Exception("DiffGzHandler.filterdiff called w/o include/exclude")
- i,o,e=os.popen3(cmd+[self.diff])
- i.close()
- p = Patch(o)
- err = e.read()
- if len(err):
- raise DiffGzException("filterdiff gave errors: "+err)
- return p
-
- def debiandir(self):
- return self.filterdiff(include='debian/*')
-
- def nondebiandir(self):
- return self.filterdiff(exclude='debian/*')
-
- def series(self):
- patches = None
- embedded = self.filterdiff(include='debian/patches*')
-
- # XXX *cough* cache *cough*
- if embedded.lines():
- td = tempfile.mkdtemp()
- i,o,e=os.popen3("patch -d %s -p3"%(td))
- o.close()
- i.write(str(embedded))
- i.close()
- err = e.read()
- if len(err):
- raise Exception("unable to extract series patches:\n"+err)
- patches = PatchSeries(td)
- os.system("rm -rf %s"%(td))
-
- return patches
-
-if __name__ == "__main__":
- print "DiffGzHandler testing"
- try:
- dh = DiffGzHandler(sys.argv[1])
- except IndexError:
- print "usage: %s <diffgz>"%(sys.argv[0])
- sys.exit(1)
-
- print "debian dir:"
- print dh.debiandir().diffstat()
- print "nondebian dir:"
- print dh.nondebiandir().diffstat()
- print "series:"
- s = dh.series()
- print s
- for name,patch in s.iterpatches():
- print "patch:",name
- print patch.diffstat()
diff --git a/patchtracker/Patch.py b/patchtracker/Patch.py
index 6533910..3078214 100644
--- a/patchtracker/Patch.py
+++ b/patchtracker/Patch.py
@@ -1,7 +1,12 @@
-import sys
-import os
import errno
+import os
+import stat
+import sys
+import tempfile
from glob import glob
+import pygments
+import pygments.lexers
+import pygments.formatters
import tarfile
class Diffstat:
@@ -39,6 +44,14 @@ class Patch:
def diffstat(self):
return Diffstat(self)
+ def highlight(self):
+ for enc in ['utf-8', 'latin-1']:
+ try:
+ return pygments.highlight(str(self).decode(enc), pygments.lexers.DiffLexer(),
+ pygments.formatters.HtmlFormatter(style='colorful', noclasses=True, encoding=enc, nobackground=True))
+ except UnicodeDecodeError:
+ pass
+
class GenericPatchSeries (list):
def blank(self):
self.names = []
@@ -164,6 +177,65 @@ class Quilt30PatchSeries (GenericPatchSeries):
for name in self.names:
self.patches[name] = Patch(self.tarfh.extractfile("debian/patches/"+name))
+class DebTarHandler:
+ diff = None
+ def __init__(self,fname):
+ self.tarfile = fname
+ self.size = os.stat(fname)[stat.ST_SIZE]
+
+ def series(self):
+ return Quilt30PatchSeries(self.tarfile)
+
+class DiffGzException(Exception):
+ pass
+
+class DiffGzHandler:
+ diff = None
+ def __init__(self,fname):
+ self.diff = fname
+ self.size = os.stat(fname)[stat.ST_SIZE]
+
+ def filterdiff(self, include=None, exclude=None):
+ cmd = ["filterdiff","-z","-p","1"]
+ if include:
+ cmd += [ "-i", include]
+ elif exclude:
+ cmd += [ "-x", exclude]
+ else:
+ raise Exception("DiffGzHandler.filterdiff called w/o include/exclude")
+ i,o,e=os.popen3(cmd+[self.diff])
+ i.close()
+ p = Patch(o)
+ err = e.read()
+ if len(err):
+ raise DiffGzException("filterdiff gave errors: "+err)
+ return p
+
+ def debiandir(self):
+ return self.filterdiff(include='debian/*')
+
+ def nondebiandir(self):
+ return self.filterdiff(exclude='debian/*')
+
+ def series(self):
+ patches = None
+ embedded = self.filterdiff(include='debian/patches*')
+
+ # XXX *cough* cache *cough*
+ if embedded.lines():
+ td = tempfile.mkdtemp()
+ i,o,e=os.popen3("patch -d %s -p3"%(td))
+ o.close()
+ i.write(str(embedded))
+ i.close()
+ err = e.read()
+ if len(err):
+ raise Exception("unable to extract series patches:\n"+err)
+ patches = PatchSeries(td)
+ os.system("rm -rf %s"%(td))
+
+ return patches
+
if __name__ == "__main__":
print "Patch.py testing"
try:
diff --git a/patchtracker/PtsIndex.py b/patchtracker/PtsIndex.py
index 08a6e68..831b4b6 100644
--- a/patchtracker/PtsIndex.py
+++ b/patchtracker/PtsIndex.py
@@ -1,7 +1,7 @@
import os
import Conf
-from DiffGzHandler import DiffGzHandler
+import Patch
class PtsIndexPackageInfo ( dict ):
def __init__ (self, srcpkg):
@@ -11,7 +11,7 @@ class PtsIndexPackageInfo ( dict ):
if srcpkg.diffgz_name:
diffgz = os.sep.join([Conf.archive_root,srcpkg.loc,srcpkg.diffgz_name])
- dh = DiffGzHandler( diffgz )
+ dh = Patch.DiffGzHandler( diffgz )
ser = dh.series()
if ser:
self['series-patches'] = len(ser)
diff --git a/patchtracker/ReqHandler.py b/patchtracker/ReqHandler.py
deleted file mode 100755
index 90fc75a..0000000
--- a/patchtracker/ReqHandler.py
+++ /dev/null
@@ -1,222 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import cgi
-import os
-import sys
-
-import patchtracker.Conf as Conf
-from patchtracker.Templates import ErrorTemplate, PatchTemplate, PackageVersTemplate, LetterTocTemplate, FrontPageTemplate, SearchResultsTemplate
-from patchtracker.DiffGzHandler import DiffGzHandler, DiffGzException
-from patchtracker.DebTarHandler import DebTarHandler
-from patchtracker.CacheObject import CacheObject, CacheMissException
-import patchtracker.DB as DB
-from patchtracker.DB import PatchTrackerDB
-import pygments
-from pygments.lexers import DiffLexer
-from pygments.formatters import HtmlFormatter
-import patchtracker.SourceArchive as SourceArchive
-
-class ReqHandlerException(Exception):
- def __init__(self, msg, code="500 Oh noes"):
- Exception.__init__(self, msg)
- self.status=code
-
-class Cmd:
- def __init__(self):
- self.content_type = 'text/html'
- self.status = "200 OK"
-
-class ErrorCmd(Cmd):
- def __init__(self, msg, code="500 Oh noes"):
- Cmd.__init__(self)
- self.status = code
- self.msg = msg
-
- def output(self):
- return str(ErrorTemplate(self.msg))
-
-class PatchCmd(Cmd):
- def __init__(self, args):
- Cmd.__init__(self)
- self.db = PatchTrackerDB()
- self.patchtype,mode,pkgname,version = args[0:4]
- self.parsemode(mode)
- dh = self.make_diffhandler(pkgname,version)
- if self.patchtype == "series":
- self.patchname = os.sep.join(args[4:])
- self.content = dh.series().fetch(self.patchname)
- elif self.patchtype == "debianonly":
- self.patchname = "debian-dir only changes"
- self.content = dh.debiandir()
- elif self.patchtype == "nondebian":
- self.patchname = "direct (non packaging) changes"
- self.content = dh.nondebiandir()
- elif self.patchtype == "misc":
- self.patchname = os.sep.join(args[4:])
- self.content = dh.filterdiff(include=self.patchname)
- else:
- raise ReqHandlerException("unhandled patch type '%s'"%(self.patchtype))
- self.pkgname = pkgname
- self.version = version
-
- def parsemode(self, mode):
- if mode == "view" or mode == "dl":
- self.mode = mode
- else:
- raise ReqHandlerException("unhandled display mode '%s'"%(mode))
- if mode == "dl":
- self.content_type = "text/x-diff"
-
- # XXX this is kinda ugly...
- def make_diffhandler(self, pkgname, vers):
- dfile = self.db.findDiffGz(pkgname,vers)
- if dfile:
- return DiffGzHandler(dfile)
- else:
- dfile = self.db.findDebTar(pkgname, vers)
- if dfile:
- return DebTarHandler(dfile)
- else:
- raise ReqHandlerException("can not find diff file for %s / %s"%(pkgname,vers))
-
- def output(self):
- if self.mode == "dl":
- return str(self.content)
- else:
- return str(PatchTemplate(pkg=self.pkgname,vers=self.version,
- patch=self.content,name=self.patchname,
- patchtype=self.patchtype))
-
-class PackageCmd(Cmd):
- def __init__(self, args):
- Cmd.__init__(self)
- db = PatchTrackerDB()
- self.name = args[0]
- if len(args) > 1:
- version = args[1]
- else:
- version = None
- self.toc = db.findCollection(package=self.name, version=version)
-
- # if there's no match, try with a wildcard match
- if not self.toc.size():
- # ... but don't allow pathologically short names
- if len(self.name) < 3:
- raise ReqHandlerException("search terms must be 3 or more letters...")
- else:
- self.toc = db.findCollection(package="%"+self.name+"%", version=version)
-
- plist = self.toc.getletter(self.name)
- if not plist or len(plist) == 0:
- raise ReqHandlerException("can't find any package named or containing '%s'"%self.name, code="404 ENOPKG kthxbye")
-
- def output(self):
- p = self.toc.getpackage(self.name)
- # if there is no match, or if multiple versions were returned
- if not p or len(set(map(lambda x: x.version, p.values()))) > 1:
- querydesc = "package name contains"
- return str(SearchResultsTemplate(self.name, querydesc, self.toc))
- else:
- return str(PackageVersTemplate(p.popitem()[1]))
-
-class IndexCmd(Cmd):
- def __init__(self, args):
- Cmd.__init__(self)
- if len(args) < 1 or not len(args[0]):
- raise ReqHandlerException("please provide a letter on which to index")
- else:
- self.db = PatchTrackerDB()
- self.letter = args[0]
- self.toc = self.db.findLetterToc(self.letter)
-
- def output(self):
- return str(LetterTocTemplate(self.letter, self.toc))
-
-class MaintCmd(Cmd):
- def __init__(self, args):
- Cmd.__init__(self)
- if len(args) < 1 or not len(args[0]):
- raise ReqHandlerException("please provide a email address on which to index")
- else:
- self.db = PatchTrackerDB()
- self.email = args[0]
- self.toc = self.db.findCollection(email=self.email)
-
- def output(self):
- return str(SearchResultsTemplate(self.email, "maintainer email", self.toc))
-
-class JumpCmd(Cmd):
- def __init__(self, env):
- Cmd.__init__(self)
- form = cgi.FieldStorage(fp=env['wsgi.input'],environ=env)
- self.name = form.getfirst("package")
- self.uri = "%s/package/%s"%(Conf.root_url, self.name)
- self.status = "302 Try this other place kthx"
-
- def output(self):
- return ""
-
-class FrontPageCmd(Cmd):
- def __init__(self):
- Cmd.__init__(self)
- self.db = PatchTrackerDB()
- self.index = self.db.findIndices()
-
- def output(self):
- return str(FrontPageTemplate(self.index))
-
-class CmdHandler:
- def __init__(self, env):
- self.headers = []
- uri = Conf.root_url+env['PATH_INFO']
- self.cacheobj = None
- #print "Accept:",env['HTTP_ACCEPT']
-
- args = uri[len(Conf.root_url)+1:].split("/")
- cmdarg = args[0]
- cacheable = False
- if cmdarg == "patch":
- self.cmd = PatchCmd(args[1:])
- cacheable = True
- elif cmdarg == "package":
- self.cmd = PackageCmd(args[1:])
- if len(args[1:]) > 1:
- cacheable = True
- elif cmdarg == "index":
- self.cmd = IndexCmd(args[1:])
- elif cmdarg == "jump":
- self.cmd = JumpCmd(env)
- self.headers.append( ('Location', self.cmd.uri) )
- elif cmdarg == "email":
- self.cmd = MaintCmd(args[1:])
- elif not len(cmdarg):
- self.cmd = FrontPageCmd()
- else:
- self.cmd = ErrorCmd("invalid command/location '%s'"%(cmdarg), "404 Not found")
-
- if Conf.caching and cacheable:
- self.cacheobj = CacheObject(key=uri)
-
- self.headers.append( ('Content-type', self.cmd.content_type) )
- self.status = self.cmd.status
-
- def output(self):
- result = None
- try:
- if self.cacheobj:
- result = self.cacheobj.get()
- else:
- result = self.cmd.output()
- except CacheMissException:
- result = self.cmd.output()
- self.cacheobj.put(result)
- except DiffGzException, e:
- return ErrorCmd(str(e), "500 Oh Noez!!1!").output()
-
- return result
-
-if __name__ == '__main__':
- fake_env = { 'PATH_INFO': sys.argv[1] }
- cmdh = CmdHandler(fake_env)
- print "Status: %s\nHeaders: %s"%(cmdh.status, cmdh.headers)
- print cmdh.output()
diff --git a/patchtracker/SourceArchive.py b/patchtracker/SourceArchive.py
index 0cdeb10..ce3459a 100755
--- a/patchtracker/SourceArchive.py
+++ b/patchtracker/SourceArchive.py
@@ -5,6 +5,8 @@ from gzip import GzipFile
from debian_bundle import deb822
import difflib
+import models
+
class Archive:
def __init__(self, dir, suitefilter=None, pkgfilter=None):
self.root = None
@@ -65,7 +67,33 @@ class Archive:
if filter and not fdict.has_key(ent['Package']):
continue
else:
- yield SourcePackage(ent)
+ pkg = models.SourcePackage()
+ # some other defaults
+ pkg.name = ent['Package']
+ pkg.format = ent['Format']
+ pkg.loc = ent['Directory']
+ pkg.version = ent['Version']
+ try:
+ pkg.maintainers = unicode(ent['Maintainer'])
+ except UnicodeDecodeError:
+ pkg.maintainers = unicode(ent['Maintainer'], 'latin-1')
+ if ent.has_key('Uploaders'):
+ try:
+ pkg.uploaders = unicode(ent['Uploaders'])
+ except UnicodeDecodeError:
+ pkg.uploaders = unicode(ent['Uploaders'], 'latin-1')
+
+ for f in ent['Files']:
+ if fnmatch(f['name'], '*.diff.gz'):
+ pkg.diffgz_name=f['name']
+ pkg.diffgz_size=f['size']
+ pkg.diffgz_md5sum=f['md5sum']
+ elif fnmatch(f['name'], '*.debian.tar.*'):
+ pkg.debtar_name=f['name']
+ pkg.debtar_size=f['size']
+ pkg.debtar_md5sum=f['md5sum']
+
+ yield pkg
def __str__(self):
return "Archive rooted at "+self.root
@@ -89,55 +117,6 @@ def getidx(letter):
else:
return name[0:4]
-class SourcePackage:
- def __init__(self, info):
- # attributes for debian .diff.gz source packages
- self.diffgz_name = None
- self.diffgz_size = None
- self.diffgz_md5sum = None
- self.diffgz = None
-
- # attributes for debian .debian.tar.gz source packages
- self.debtar_name = None
- self.debtar_size = None
- self.debtar_md5sum = None
- self.debtar = None
-
- # some other defaults
- self.type = "Native"
- self.name = info['Package']
- self.format = info['Format']
- self.loc = info['Directory']
- self.version = info['Version']
- self.uploaders = None
-
- try:
- self.maintainers = unicode(info['Maintainer'])
- except UnicodeDecodeError:
- self.maintainers = unicode(info['Maintainer'], 'latin-1')
- if info.has_key('Uploaders'):
- try:
- self.uploaders = unicode(info['Uploaders'])
- except UnicodeDecodeError:
- self.uploaders = unicode(info['Uploaders'], 'latin-1')
-
- self.idx = getidx(self)
-
- for f in info['Files']:
- if fnmatch(f['name'], '*.diff.gz'):
- self.diffgz_name=f['name']
- self.diffgz_size=f['size']
- self.diffgz_md5sum=f['md5sum']
- self.type = "Debian-diff"
- elif fnmatch(f['name'], '*.debian.tar.*'):
- self.debtar_name=f['name']
- self.debtar_size=f['size']
- self.debtar_md5sum=f['md5sum']
- self.type = "Debian-diff"
-
- def __str__(self):
- return self.name
-
class SourcePackageIndex:
def __init__(self):
self.pkgs = {}
diff --git a/patchtracker/Templates.py b/patchtracker/Templates.py
deleted file mode 100755
index 42140a8..0000000
--- a/patchtracker/Templates.py
+++ /dev/null
@@ -1,103 +0,0 @@
-from patchtracker import Conf, DB
-from patchtracker.DiffGzHandler import DiffGzHandler
-from patchtracker.DebTarHandler import DebTarHandler
-from patchtracker.SourceArchive import ReleaseList
-
-from Cheetah.Template import Template
-from Cheetah.Compiler import Compiler
-import os, errno
-import re
-
-class OurTemplate(Template):
- def __init__(self, file, searchList=None):
- ourSearchList={"conf":Conf, "crumbs":[]}
- if searchList:
- for k,v in searchList.iteritems():
- ourSearchList[k]=v
- self.escape_name = self._escape_name
- self.wrappable_version = self._wrappable_version
- self.link = self._link
- Template.__init__(self, file=file, searchList=ourSearchList)
-
- def _link(self, where, name):
- return str("<a href=\"%s\">%s</a>"%(where,name))
-
- def _escape_name(self, name):
- return re.sub("([^a-zA-Z0-9-])", (lambda x: "_%d"%(ord(x.group(1)))), name)
-
- def _wrappable_version(self, version):
- """ output a version that can be broken up by a web browser using
- the unicode zero-whitespace-break character (&#8203;) """
- return '&#8203;'.join([version[i:i+12] for i in range(0,len(version),12)])
-
-class PackageVersTemplate(OurTemplate):
- def __init__(self, srcpkg):
- self.src = srcpkg
- tpl=os.sep.join([Conf.template_dir, "package_vers.tmpl"])
- sl = {}
- # XXX c/p from PatchCmd.make_diffhandler
- db = DB.PatchTrackerDB()
- dfile = db.findDiffGz(srcpkg.name,srcpkg.version)
- if dfile:
- sl['diffhandler'] = DiffGzHandler(dfile)
- else:
- dfile = db.findDebTar(srcpkg.name, srcpkg.version)
- if dfile:
- sl['diffhandler'] = DebTarHandler(dfile)
- sl['crumbs'] = [("package/"+srcpkg.name,srcpkg.name),
- ("package/"+srcpkg.name+"/"+srcpkg.version,
- srcpkg.name+"/"+srcpkg.version)]
- OurTemplate.__init__(self, file=tpl, searchList=sl)
-
-class FrontPageTemplate(OurTemplate):
- def __init__(self, indices):
- tpl = os.sep.join([Conf.template_dir, "frontpage.tmpl"])
- OurTemplate.__init__(self, file=tpl)
- self.indices = indices
-
-class LetterTocTemplate(OurTemplate):
- def __init__(self, letter, collection):
- self.pkgs = collection
- self.idx = letter
- self.dists = ReleaseList("debian")
- sl = {}
- sl['crumbs'] = [("index/"+letter,"index for "+letter)]
- tpl = os.sep.join([Conf.template_dir, "letter_toc.tmpl"])
- OurTemplate.__init__(self, file=tpl, searchList=sl)
-
-class SearchResultsTemplate(OurTemplate):
- def __init__(self, search, searchtype, collection):
- self.pkgs = {}
- self.idx = search
- self.searchtype = searchtype
- self.dists = ReleaseList("debian")
- for idx in collection.indices():
- for name,packagelist in collection.getletter(idx).iteritems():
- self.pkgs[name] = packagelist
- tpl = os.sep.join([Conf.template_dir, "searchresults.tmpl"])
- OurTemplate.__init__(self, file=tpl)
-
-
-class ErrorTemplate(OurTemplate):
- def __init__(self, msg):
- tpl = os.sep.join([Conf.template_dir, "cgi_error.tmpl"])
- OurTemplate.__init__(self, file=tpl, searchList={'error':msg})
-
-class PatchTemplate(OurTemplate):
- def __init__(self, pkg=None, vers=None, name=None, patch=None,patchtype=None):
- tpl = os.sep.join([Conf.template_dir, "patch_view.tmpl"])
- sl = {'package':pkg, 'version':vers, 'name':name,
- 'patch':patch, 'patchtype':patchtype}
- sl['crumbs'] = [("package/"+pkg,pkg),
- ("package/"+pkg+"/"+vers, pkg+"/"+vers)]
- if patchtype == "debianonly":
- sl['crumbs'].append( ("patch/%s/view/%s/%s"%(patchtype,pkg,vers),
- "./debian-dir only patch") )
- elif patchtype == "series":
- sl['crumbs'].append(("patch/%s/view/%s/%s/%s"%(patchtype,pkg,vers,name),
- "series patch "+name) )
- elif patchtype == "misc":
- sl['crumbs'].append(("patch/%s/view/%s/%s/%s"%(patchtype,pkg,vers,name),
- "direct patch of "+name) )
- OurTemplate.__init__(self, file=tpl, searchList=sl)
-
diff --git a/patchtracker/admin.py b/patchtracker/admin.py
new file mode 100644
index 0000000..0fbd418
--- /dev/null
+++ b/patchtracker/admin.py
@@ -0,0 +1,7 @@
+import models
+import django.contrib.admin
+
+django.contrib.admin.site.register(models.SourcePackage)
+django.contrib.admin.site.register(models.RepositorySuite)
+django.contrib.admin.site.register(models.RepositoryComponent)
+django.contrib.admin.site.register(models.SourcePackageMapping)
diff --git a/patchtracker/models.py b/patchtracker/models.py
new file mode 100644
index 0000000..1758609
--- /dev/null
+++ b/patchtracker/models.py
@@ -0,0 +1,89 @@
+import os
+import re
+
+import Conf
+import Patch
+
+from django.db import models
+
+class SourcePackage (models.Model):
+ name = models.CharField(max_length=1024)
+ version = models.CharField(max_length=1024)
+ format = models.CharField(max_length=64)
+ loc = models.CharField(max_length=4096)
+ diffgz_name = models.CharField(max_length=4096, null=True, blank=True)
+ diffgz_size = models.IntegerField(null=True, blank=True)
+ diffgz_md5sum = models.CharField(max_length=32, null=True, blank=True)
+ debtar_name = models.CharField(max_length=4096, null=True, blank=True)
+ debtar_size = models.IntegerField(null=True, blank=True)
+ debtar_md5sum = models.CharField(max_length=32, null=True, blank=True)
+ maintainer = models.CharField(max_length=1024)
+ uploaders = models.CharField(max_length=4096, null=True, blank=True)
+
+ def type( self ):
+ """ Source package "type". Not incredibly useful, i guess... """
+ if self.diffgz_name or self.debtar_name:
+ return "Debian-diff"
+ else:
+ return "Native"
+
+ def diffhandler( self ):
+ """ Return a diffhandler object too the caller """
+ if self.diffgz_name:
+ dgz = os.sep.join([Conf.archive_root, self.loc, self.diffgz_name])
+ return Patch.DiffGzHandler(dgz)
+ elif self.debtar_name:
+ dtar = os.sep.join([Conf.archive_root, self.loc, self.debtar_name])
+ return Patch.DebTarHandler(dtar)
+
+ def anchor_name(self):
+ """ output a version of the package name suitable for use as an
+ anchor name (i.e. <a name="foo">) """
+ return re.sub("([^a-zA-Z0-9-])", (lambda x: "_%d"%(ord(x.group(1)))), self.name)
+
+ def wrappable_version(self):
+ """ output a version that can be broken up by a web browser using
+ the unicode zero-whitespace-break character (&#8203;) """
+ return '&#8203;'.join([self.version[i:i+12] for i in range(0,len(self.version),12)])
+
+ def __unicode__( self ):
+ return "%s (%s)"%(self.name, self.version)
+
+ class Meta:
+ managed = False
+ db_table = 'packages'
+ unique_together = (('name','version'),)
+
+class RepositorySuite (models.Model):
+ name = models.CharField(max_length=1024, unique=True)
+
+ def __unicode__( self ):
+ return unicode(self.name)
+
+ class Meta:
+ managed = False
+ db_table = 'suites'
+
+class RepositoryComponent (models.Model):
+ name = models.CharField(max_length=1024, unique=True)
+
+ def __unicode__( self ):
+ return unicode(self.name)
+
+ class Meta:
+ managed = False
+ db_table = 'components'
+
+class SourcePackageMapping (models.Model):
+ rowid = models.AutoField(primary_key=True)
+ package = models.ForeignKey(SourcePackage)
+ suite = models.ForeignKey(RepositorySuite)
+ component = models.ForeignKey(RepositoryComponent)
+
+ def __unicode__( self ):
+ return "%s / %s / %s"%(unicode(self.package),unicode(self.suite),unicode(self.component))
+
+ class Meta:
+ managed = False
+ db_table = 'package_rel_map'
+ unique_together = (('package','suite','component'),)
diff --git a/patchtracker/tests.py b/patchtracker/tests.py
new file mode 100644
index 0000000..2247054
--- /dev/null
+++ b/patchtracker/tests.py
@@ -0,0 +1,23 @@
+"""
+This file demonstrates two different styles of tests (one doctest and one
+unittest). These will both pass when you run "manage.py test".
+
+Replace these with more appropriate tests for your application.
+"""
+
+from django.test import TestCase
+
+class SimpleTest(TestCase):
+ def test_basic_addition(self):
+ """
+ Tests that 1 + 1 always equals 2.
+ """
+ self.failUnlessEqual(1 + 1, 2)
+
+__test__ = {"doctest": """
+Another way to test that 1 + 1 is equal to 2.
+
+>>> 1 + 1 == 2
+True
+"""}
+
diff --git a/patchtracker/urls.py b/patchtracker/urls.py
new file mode 100644
index 0000000..b174272
--- /dev/null
+++ b/patchtracker/urls.py
@@ -0,0 +1,18 @@
+from django.conf.urls.defaults import *
+import views
+
+urlpatterns = patterns('',
+ url(r'^package/(?P<package>\w+)/(?P<version>[^/]+)$',
+ views.package_vers),
+ url(r'^package/(?P<index>\w+)$',
+ views.display_toc),
+ url(r'^index/(?P<index>[^/]+)$',
+ views.display_toc),
+ url(r'^email/(?P<maintainer>.*)$',
+ views.maintainer_search),
+ url(r'^patch/(?P<patchType>(debianonly|misc|nondebian|series))/view/(?P<package>\w+)/(?P<version>[^/]+)/(?P<patchName>.*)$',
+ views.display_patch),
+ url(r'^patch/(?P<patchType>(debianonly|misc|nondebian|series))/dl/(?P<package>\w+)/(?P<version>[^/]+)/(?P<patchName>.*)$',
+ views.download_patch),
+ url(r'^$', views.frontpage),
+ )
diff --git a/patchtracker/views.py b/patchtracker/views.py
new file mode 100644
index 0000000..0a316eb
--- /dev/null
+++ b/patchtracker/views.py
@@ -0,0 +1,75 @@
+import os
+
+import django.db.models
+import django.http
+import django.shortcuts
+import django.template
+
+import ComplexQueries
+import Conf
+import models
+
+def package_vers(request, package, version):
+ pkg = models.SourcePackage.objects.get( name=package, version=version )
+ ctx = django.template.RequestContext(request)
+ tmpl = 'package_vers.html'
+ extra = { 'pkg':pkg, 'ctx':ctx, 'conf':Conf }
+ return django.shortcuts.render_to_response(tmpl, extra, context_instance=ctx)
+
+def display_toc(request, index):
+ packageIndex = ComplexQueries.PackageIndex(index)
+ tmpl = 'searchresults.html'
+ searchDescription = "Packages by index"
+ extra = { 'index':index, 'packageIndex':packageIndex, 'conf':Conf,
+ 'searchDescription':searchDescription, 'searchKey':index }
+ return django.shortcuts.render_to_response(tmpl, extra)
+
+def maintainer_search(request, maintainer):
+ mappings = models.SourcePackageMapping.objects.order_by('package__name').filter( django.db.models.Q(package__maintainer__contains=maintainer) | django.db.models.Q(package__uploaders__contains=maintainer ) )
+ packageIndex = ComplexQueries.PackageIndexQuery( mappings )
+ tmpl = 'searchresults.html'
+ searchDescription = "Maintainer email"
+ extra = { 'index':maintainer, 'packageIndex':packageIndex, 'conf':Conf,
+ 'searchDescription':searchDescription, 'searchKey':maintainer }
+ return django.shortcuts.render_to_response(tmpl, extra)
+
+def display_patch(request, patchType, package, version, patchName):
+ pkg = models.SourcePackage.objects.get( name=package, version=version )
+ ctx = django.template.RequestContext(request)
+ tmpl = 'patch_view.html'
+
+ if patchType == "debianonly":
+ patch = pkg.diffhandler().debiandir()
+ patchTitle = "debian-dir only changes"
+ elif patchType == "misc":
+ patch = pkg.diffhandler().filterdiff(include=patchName)
+ patchTitle = patchName
+ elif patchType == "nondebian":
+ patch = pkg.diffhandler().nondebiandir()
+ patchTitle = "direct (non-packaging) changes"
+ elif patchType == "series":
+ patch = pkg.diffhandler().series().fetch(patchName)
+ patchTitle = patchName
+
+ extra = { 'pkg':pkg, 'patch':patch, 'patchType':patchType, 'conf':Conf,
+ 'patchName':patchName, 'patchTitle':patchTitle }
+ return django.shortcuts.render_to_response(tmpl, extra, context_instance=ctx)
+
+def download_patch(request, patchType, package, version, patchName):
+ pkg = models.SourcePackage.objects.get( name=package, version=version )
+ if patchType == "debianonly":
+ patch = pkg.diffhandler().debiandir()
+ elif patchType == "misc":
+ patch = pkg.diffhandler().filterdiff(include=patchName)
+ elif patchType == "nondebian":
+ patch = pkg.diffhandler().nondebiandir()
+ elif patchType == "series":
+ patch = pkg.diffhandler().series().fetch(patchName)
+ return django.http.HttpResponse(patch, mimetype="text/plain")
+
+def frontpage(request):
+ nonlibs = [idx for idx in models.SourcePackage.objects.filter(~django.db.models.Q(name__startswith='lib')).extra(select={'index' : "SUBSTR(name, 1, 1)"}).distinct().values_list('index', flat=True)]
+ libs = [idx for idx in models.SourcePackage.objects.filter(name__startswith='lib').extra(select={'index' : "SUBSTR(name, 1, 4)"}).distinct().values_list('index', flat=True)]
+ indices = sorted(nonlibs + libs)
+ extra = { 'indices':indices, 'conf':Conf }
+ return django.shortcuts.render_to_response('frontpage.html', extra)
diff --git a/reprepro/conf/diffsonly.py b/reprepro/conf/diffsonly.py
index d2f02ea..460cb2b 100755
--- a/reprepro/conf/diffsonly.py
+++ b/reprepro/conf/diffsonly.py
@@ -1,6 +1,10 @@
#!/usr/bin/python
-from debian_bundle import deb822
+try:
+ from debian import deb822
+except ImportError:
+ from debian_bundle import deb822
+
from gzip import GzipFile
from fnmatch import fnmatch
import sys
@@ -39,5 +43,5 @@ if __name__ == '__main__':
if fnmatch(f['name'], wanted_glob):
newfiles.append(f)
ent[k] = newfiles
- outf.write(str(ent))
+ outf.write(unicode(ent).encode('utf-8'))
outf.write("\n")
diff --git a/settings.py b/settings.py
new file mode 100644
index 0000000..2c42ec4
--- /dev/null
+++ b/settings.py
@@ -0,0 +1,88 @@
+# Django settings for debianpatchtracker project.
+
+DEBUG = False
+TEMPLATE_DEBUG = DEBUG
+
+ADMINS = (
+ # ('Your Name', 'your_email@domain.com'),
+)
+
+MANAGERS = ADMINS
+
+DATABASE_ENGINE = 'sqlite3' # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
+DATABASE_NAME = 'pt.db' # Or path to database file if using sqlite3.
+DATABASE_USER = '' # Not used with sqlite3.
+DATABASE_PASSWORD = '' # Not used with sqlite3.
+DATABASE_HOST = '' # Set to empty string for localhost. Not used with sqlite3.
+DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3.
+
+# Local time zone for this installation. Choices can be found here:
+# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
+# although not all choices may be available on all operating systems.
+# If running in a Windows environment this must be set to the same as your
+# system time zone.
+TIME_ZONE = 'America/Chicago'
+
+# Language code for this installation. All choices can be found here:
+# http://www.i18nguy.com/unicode/language-identifiers.html
+LANGUAGE_CODE = 'en-us'
+
+SITE_ID = 1
+
+# If you set this to False, Django will make some optimizations so as not
+# to load the internationalization machinery.
+USE_I18N = True
+
+# Absolute path to the directory that holds media.
+# Example: "/home/media/media.lawrence.com/"
+MEDIA_ROOT = ''
+
+# URL that handles the media served from MEDIA_ROOT. Make sure to use a
+# trailing slash if there is a path component (optional in other cases).
+# Examples: "http://media.lawrence.com", "http://example.com/media/"
+MEDIA_URL = ''
+
+# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
+# trailing slash.
+# Examples: "http://foo.com/media/", "/media/".
+ADMIN_MEDIA_PREFIX = '/media/'
+
+# Make this unique, and don't share it with anybody.
+# set in non-versioned localsettings.py
+#SECRET_KEY = ''
+
+# List of callables that know how to import templates from various sources.
+TEMPLATE_LOADERS = (
+ 'django.template.loaders.filesystem.load_template_source',
+ 'django.template.loaders.app_directories.load_template_source',
+# 'django.template.loaders.eggs.load_template_source',
+)
+
+MIDDLEWARE_CLASSES = (
+ 'django.middleware.common.CommonMiddleware',
+ 'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
+)
+
+ROOT_URLCONF = 'urls'
+
+TEMPLATE_DIRS = (
+ # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
+ # Always use forward slashes, even on Windows.
+ # Don't forget to use absolute paths, not relative paths.
+ '/home/sean/debian/patch-tracker/templates'
+)
+
+INSTALLED_APPS = (
+ 'django.contrib.auth',
+ 'django.contrib.contenttypes',
+ 'django.contrib.sessions',
+ 'django.contrib.sites',
+ 'django.contrib.admin',
+ 'patchtracker'
+)
+
+try:
+ from localsettings import *
+except ImportError:
+ pass
diff --git a/templates/skeleton.tmpl b/templates/base.html
index 745dbcf..aa832ad 100644
--- a/templates/skeleton.tmpl
+++ b/templates/base.html
@@ -2,23 +2,21 @@
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
- <title>$title</title>
+ <title>{% block title %}{% endblock %}</title>
<link rel="stylesheet" type="text/css"
- href="$conf.root_url/static/css/patches.css"/>
- <base href="$conf.root_url/" />
+ href="/static/css/patches.css"/>
</head>
<body>
<div class="pageheader">
- <img alt="[patchlogo]" src="$conf.root_url/static/img/swirlpatch.png"/>
- <p> <a href="$conf.root_url/">Debian patch tracking system </a> </p>
+ <img alt="[patchlogo]" src="/static/img/swirlpatch.png"/>
+ <p> <a href="/">Debian patch tracking system </a> </p>
<div class="breadcrumb">
- #set $crumblinks = [$link($conf.root_url+"/"+b[0],b[1]) for b in $crumbs]
- #for $i in $crumblinks
- - $i
- #end for
+ {% for crumb in crumbs %}
+ {{ crumb }}
+ {% endfor %}
</div> <!-- breadcrumb -->
</div>
- $body
+ {% block content %}{% endblock %}
<div class="pagefooter">
page code/design/content is copyright (c) 2008 sean finney
&lt;seanius@debian.org&gt;. <br/>
diff --git a/templates/frontpage.tmpl b/templates/frontpage.html
index 7b103a0..517feb7 100644
--- a/templates/frontpage.tmpl
+++ b/templates/frontpage.html
@@ -1,9 +1,8 @@
-#import templates
-#extends templates.skeleton
-#def title
+{% extends "base.html" %}
+{% block title %}
Debian Project patch tracking system
-#end def
-#def body
+{% endblock %}
+{% block content %}
<h1>Debian Project patch tracking system</h1>
<h2>Introduction</h2>
<p>
@@ -12,18 +11,24 @@ Debian Project patch tracking system
</p>
<h2>Quick access</h2>
<ul>
- <li><span class="url">$conf.root_url/package/&lt;p&gt;</span>
+ <li><span class="url">{{ conf.root_url }}/package/&lt;p&gt;</span>
- search for package p</li>
- <li><span class="url">$conf.root_url/package/&lt;p&gt;/&lt;v&gt;</span>
+ <li><span class="url">{{ conf.root_url }}/package/&lt;p&gt;/&lt;v&gt;</span>
- go directly to the page for package p, version v</li>
- <li><span class="url">$conf.root_url/email/&lt;u&gt;</span>
+ <li><span class="url">{{ conf.root_url }}/email/&lt;u&gt;</span>
- show packages with maintainer address containing u.</li>
</ul>
<h3>Browse patches by package name</h3>
-#set $links = [ "<a href=\"index/"+k+"\">"+k+"</a>" for k in $indices ]
-#set $out = " - ".join($links)
<div class="quicklinks">
- $out
+ {% for index in indices %}
+ <a href="{% url patchtracker.views.display_toc index=index %}">{{ index }}</a>
+ {% if not forloop.last %}
+ -
+ {% endif %}
+ {% empty %}
+ Error fetching indices of available packages
+ {% endfor %}
+ <!-- <a href="index/"{{ k }}">{{ k }}</a> -->
</div>
<h3>Jump to package / Package search</h3>
<div class="framed">
@@ -32,5 +37,4 @@ Debian Project patch tracking system
<input type="submit" name="submit" />
</form>
</div>
-
-#end def
+{% endblock %}
diff --git a/templates/letter_toc.tmpl b/templates/letter_toc.tmpl
deleted file mode 100644
index a7bf9a1..0000000
--- a/templates/letter_toc.tmpl
+++ /dev/null
@@ -1,34 +0,0 @@
-#import templates
-#extends templates.skeleton
-#def title
-Debian Project patch tracking system
-#end def
-#def body
-<h1>Debian Project patch tracking system</h1>
-<h2>Packaging patches by index - $idx</h2>
-<table class="packagelisting">
-<tr>
-<th>package</th>
-#for $d in $dists
- <th>$d</th>
-#end for
-</tr>
-#for $p in $sorted($pkgs.iterkeys)
-<tr>
- <td>
- <a name="$escape_name($p)" />
- <a href="http://packages.debian.org/$p">$p</a>
- </td>
- #for $d in $dists
- <td>
- #if $pkgs[$p].has_key($d)
- <a href="$conf.root_url/package/$p/$pkgs[$p][$d].version">$wrappable_version($pkgs[$p][$d].version)</a>
- #else
- <i>n/a</i>
- #end if
- </td>
- #end for
-</tr>
-#end for
-</table>
-#end def
diff --git a/templates/package_vers.html b/templates/package_vers.html
new file mode 100644
index 0000000..7e4c1cb
--- /dev/null
+++ b/templates/package_vers.html
@@ -0,0 +1,148 @@
+{% extends "base.html" %}
+{% block title %}
+debian specific patch information for {{ pkg.name }} / {{ pkg.version }}
+{% endblock %}
+{% block content %}
+ <h1>
+ debian specific patch information for {{ pkg.name }} / {{ pkg.version }}
+ </h1>
+ <div class="diffsummary">
+ <a name="diff-summary"></a>
+ <h2> Summary </h2>
+ <table class="summary">
+ <tr>
+ <th>Package Version</th>
+ <th>Package Type</th>
+ <th>Source Package Format</th>
+ </tr>
+ <tr>
+ <td>{{ pkg.version }}</td>
+ <td>{{ pkg.type }}</td>
+ <td>{{ pkg.format }}</td>
+ </tr>
+ </table>
+ </div> <!-- diffsummary -->
+
+{% if pkg.debtar_name %}
+ <div class="debdiff">
+ <a name="debian-tarball"></a>
+ <h2> Debian packaging Information </h2>
+ <table class="patchlisting">
+ <tr>
+ <th>Debian changes tarfile</th>
+ <td colspan="2">
+ <a href="{{ conf.archive_root_url }}/{{ pkg.loc }}/{{ pkg.debtar_name }}">
+ {{ pkg.debtar_name }}
+ </a>
+ </td>
+ </tr>
+ <tr>
+ <th>Size</th><td colspan="2">{{ pkg.debtar_size }}</td>
+ </tr>
+ <tr>
+ <th>MD5sum</th><td colspan="2">{{ pkg.debtar_md5sum }}</td>
+ </tr>
+ </table>
+ </div> <!-- debdiff -->
+{% endif %}
+{% if pkg.diffgz_name %}
+ <div class="debdiff">
+ <a name="debian-patches"></a>
+ <h2> Debian packaging Information </h2>
+ <table class="patchlisting">
+ <tr>
+ <th>Diff file</th>
+ <td colspan="2">
+ <a href="{{ conf.archive_root_url }}/{{ pkg.loc }}/{{ pkg.diffgz_name }}">
+ {{ pkg.diffgz_name }}
+ </a>
+ </td>
+ </tr>
+ <tr>
+ <th>Size</th><td colspan="2">{{ pkg.diffgz_size }}</td>
+ </tr>
+ <tr>
+ <th>MD5sum</th><td colspan="2">{{ pkg.diffgz_md5sum }}</td>
+ </tr>
+ <tr>
+ <th>./debian only changes</th>
+ <td>
+ <a href="{{ conf.root_url }}/patch/debianonly/view/{{ pkg.name }}/{{ pkg.version }}">view</a>
+ </td>
+ <td>
+ <a href="{{ conf.root_url }}/patch/debianonly/dl/{{ pkg.name }}/{{ pkg.version }}">download</a>
+ </td>
+ </tr>
+ {% if pkg.diffgz_name and pkg.diffhandler.nondebiandir.lines %}
+ <tr>
+ <th>non packaging (i.e. not ./debian) changes</th>
+ <td>
+ <a href="{{ conf.root_url }}/patch/nondebian/view/{{ pkg.name }}/{{ pkg.version }}">view</a>
+ </td>
+ <td>
+ <a href="{{ conf.root_url }}/patch/nondebian/dl/{{ pkg.name }}/{{ pkg.version }}">download</a>
+ </td>
+ </tr>
+ {% endif %}
+ </table>
+ </div> <!-- debdiff -->
+{% endif %}
+
+{% if pkg.diffgz_name or pkg.debtar_name %}
+ {% if pkg.diffhandler.series %}
+ <div class="debseries">
+ <a name="series-patches"></a>
+ <h2> "series" style patches </h2>
+ <table class="patchlisting">
+ <tr>
+ <th>patch</th>
+ <th>summary</th>
+ <th>view</th>
+ <th>raw</th>
+ </tr>
+ {% for name,patch in pkg.diffhandler.series.iterpatches %}
+ <tr>
+ <td>{{ name }}</td>
+ <td class="diffstat"><pre>{{ patch.diffstat }}</pre></td>
+ <td>
+ <a href="{{ conf.root_url }}/patch/series/view/{{ pkg.name }}/{{ pkg.version }}/{{ name }}">view</a>
+ </td>
+ <td>
+ <a href="{{ conf.root_url }}/patch/series/dl/{{ pkg.name }}/{{ pkg.version }}/{{ name }}">download</a>
+ </td>
+ </tr>
+ {% endfor %}
+ </table>
+ </div> <!-- debseries -->
+ {% endif %}
+{% endif %}
+
+{% if pkg.diffgz_name and pkg.diffhandler.nondebiandir.lines %}
+ <div class="nondebdiff">
+ <a name="direct-patches"></a>
+ <h2> Misc. Non-packaging "direct" style patches </h2>
+ <table class="patchlisting">
+ <tr>
+ <th>file</th>
+ <th>inserted</th>
+ <th>deleted</th>
+ <th>modified</th>
+ <th>view</th>
+ <th>download</th>
+ </tr>
+ {% for insd,deld,modd,f in pkg.diffhandler.nondebiandir.diffstat.stats %}
+ <tr>
+ <td>{{ f }}</td><td>{{ insd }}</td><td>{{ deld }}</td><td>{{ modd }}</td>
+ <td>
+ <a href="{{ conf.root_url }}/patch/misc/view/{{ pkg.name }}/{{ pkg.version }}/{{ f }}">view</a>
+ </td>
+ <td>
+ <a href="{{ conf.root_url }}/patch/misc/dl/{{ pkg.name }}/{{ pkg.version }}/{{ f }}">download</a>
+ </td>
+ </tr>
+ {% endfor %}
+ </table>
+ </div> <!-- nondebdiff -->
+{% endif %}
+
+{% endblock %}
diff --git a/templates/package_vers.tmpl b/templates/package_vers.tmpl
deleted file mode 100755
index 23ab83e..0000000
--- a/templates/package_vers.tmpl
+++ /dev/null
@@ -1,149 +0,0 @@
-#import templates
-#extends templates.skeleton
-#def title
-debian specific patch information for $src.name / $src.version
-#end def
-#def body
- #set $u = $conf.root_url
- <h1>debian specific patch information for $src.name / $src.version</h1>
- <div class="diffsummary">
- <a name="diff-summary"></a>
- <h2> Summary </h2>
- <table class="summary">
- <tr>
- <th>Package Version</th>
- <th>Package Type</th>
- <th>Source Package Format</th>
- </tr>
- <tr>
- <td>$src.version</td>
- <td>$src.type</td>
- <td>$src.format</td>
- </tr>
- </table>
- </div> <!-- diffsummary -->
-
-#if $src.debtar_name
- <div class="debdiff">
- <a name="debian-tarball"></a>
- <h2> Debian packaging Information </h2>
- <table class="patchlisting">
- <tr>
- <th>Debian changes tarfile</th>
- <td colspan="2">
- <a href="$conf.archive_root_url/$src.loc/$src.debtar_name">
- $src.debtar_name
- </a>
- </td>
- </tr>
- <tr>
- <th>Size</th><td colspan="2">$src.debtar_size</td>
- </tr>
- <tr>
- <th>MD5sum</th><td colspan="2">$src.debtar_md5sum</td>
- </tr>
- </table>
- </div> <!-- debdiff -->
-#end if
-
-#if $src.diffgz_name
- <div class="debdiff">
- <a name="debian-patches"></a>
- <h2> Debian packaging Information </h2>
- <table class="patchlisting">
- <tr>
- <th>Diff file</th>
- <td colspan="2">
- <a href="$conf.archive_root_url/$src.loc/$src.diffgz_name">
- $src.diffgz_name
- </a>
- </td>
- </tr>
- <tr>
- <th>Size</th><td colspan="2">$src.diffgz_size</td>
- </tr>
- <tr>
- <th>MD5sum</th><td colspan="2">$src.diffgz_md5sum</td>
- </tr>
- <tr>
- <th>./debian only changes</th>
- <td>
- <a href="$u/patch/debianonly/view/$src.name/$src.version">view</a>
- </td>
- <td>
- <a href="$u/patch/debianonly/dl/$src.name/$src.version">download</a>
- </td>
- </tr>
- #if $src.diffgz_name and $diffhandler.nondebiandir.lines
- <tr>
- <th>non packaging (i.e. not ./debian) changes</th>
- <td>
- <a href="$u/patch/nondebian/view/$src.name/$src.version">view</a>
- </td>
- <td>
- <a href="$u/patch/nondebian/dl/$src.name/$src.version">download</a>
- </td>
- </tr>
- #end if
- </table>
- </div> <!-- debdiff -->
- #end if
-
-#if $src.diffgz_name and $diffhandler.nondebiandir.lines
- <div class="nondebdiff">
- <a name="direct-patches"></a>
- <h2> Misc. Non-packaging "direct" style patches </h2>
- <table class="patchlisting">
- <tr>
- <th>file</th>
- <th>inserted</th>
- <th>deleted</th>
- <th>modified</th>
- <th>view</th>
- <th>download</th>
- </tr>
- #for $insd,$deld,$modd,$f in $diffhandler.nondebiandir.diffstat.stats
- <tr>
- <td>$f</td><td>$insd</td><td>$deld</td><td>$modd</td>
- <td>
- <a href="$u/patch/misc/view/$src.name/$src.version/$f">view</a>
- </td>
- <td>
- <a href="$u/patch/misc/dl/$src.name/$src.version/$f">download</a>
- </td>
- </tr>
- #end for
- </table>
- </div> <!-- nondebdiff -->
-#end if
-
-#if $src.diffgz_name or $src.debtar_name
-#set $series = $diffhandler.series
-#if $series
- <div class="debseries">
- <a name="series-patches"></a>
- <h2> "series" style patches </h2>
- <table class="patchlisting">
- <tr>
- <th>patch</th>
- <th>summary</th>
- <th>view</th>
- <th>raw</th>
- </tr>
- #for $name,$patch in $series.iterpatches()
- <tr>
- <td>$name</td>
- <td class="diffstat"><pre>$patch.diffstat</pre></td>
- <td>
- <a href="$u/patch/series/view/$src.name/$src.version/$name">view</a>
- </td>
- <td>
- <a href="$u/patch/series/dl/$src.name/$src.version/$name">download</a>
- </td>
- </tr>
- #end for
- </table>
- </div> <!-- debseries -->
-#end if
-#end if
-#end def
diff --git a/templates/patch_view.html b/templates/patch_view.html
new file mode 100644
index 0000000..743c536
--- /dev/null
+++ b/templates/patch_view.html
@@ -0,0 +1,24 @@
+{% extends "base.html" %}
+{% block title %}
+Patch information for {{ pkg.name }} {{ pkg.version }}: {{ patchTitle }}
+{% endblock %}
+{% block content %}
+ <h1>{{ pkg.name }} {{ pkg.version }}: {{ patchTitle }}</h1>
+ <h2>Summary</h2>
+ <div>
+ <code class="diffstat">
+ <pre>
+{{ patch.diffstat|escape }}
+ </pre>
+ </code>
+ </div>
+ <div>
+ <a href="{{ conf.root_url }}/patch/{{ patchType }}/dl/{{ pkg.name }}/{{ pkg.version }}/{{ patchName }}">
+ download this patch
+ </a>
+ </div>
+ <h2>Patch contents</h2>
+ <div class="patch">
+{{ patch.highlight|safe }}
+ </div>
+{% endblock %}
diff --git a/templates/patch_view.tmpl b/templates/patch_view.tmpl
deleted file mode 100644
index 0a715b9..0000000
--- a/templates/patch_view.tmpl
+++ /dev/null
@@ -1,33 +0,0 @@
-#import templates
-#extends templates.skeleton
-#import pygments
-#from pygments.lexers import DiffLexer
-#from pygments.formatters import HtmlFormatter
-#from cgi import escape
-#def title
-Patch information for $package ($version) $name
-#end def
-#def body
- <h1>$package ($version) $name</h1>
- <h2>Summary</h2>
- <div>
- <code class="diffstat">
- <pre>
-$escape($str($patch.diffstat))
- </pre>
- </code>
- </div>
- <div>
- <a href="$conf.root_url/patch/$patchtype/dl/$package/$version/$name">
- download this patch
- </a>
- </div>
- <h2>Patch contents</h2>
- <div class="patch">
- #try
- $pygments.highlight($str($patch).decode('utf-8'), $DiffLexer(), $HtmlFormatter(style='colorful', noclasses=True, encoding='utf-8'))
- #except UnicodeDecodeError
- $pygments.highlight($str($patch).decode('latin-1'), $DiffLexer(), $HtmlFormatter(style='colorful', noclasses=True, encoding='latin-1'))
- #end try
- </div>
-#end def
diff --git a/templates/searchresults.html b/templates/searchresults.html
new file mode 100644
index 0000000..6f5abe5
--- /dev/null
+++ b/templates/searchresults.html
@@ -0,0 +1,33 @@
+{% extends "base.html" %}
+{% block title %}
+Debian Project patch tracking system
+{% endblock %}
+{% block content %}
+<h1>Debian Project patch tracking system</h1>
+<h2>Search Results: {{ searchDescription }} - {{ searchKey }}</h2>
+<table class="packagelisting">
+<tr>
+<th>package</th>
+{% for dist in packageIndex.dists %}
+ <th>{{ dist }}</th>
+{% endfor %}
+</tr>
+{% for row in packageIndex %}
+<tr>
+ <td>
+ <a name="{{ row.package.anchor_name }}" />
+ <a href="http://packages.debian.org/{{ row.package.name }}">{{ row.package.name }}</a>
+ </td>
+ {% for pkg in row.entries %}
+ <td>
+ {% if pkg %}
+ <a href="{{ conf.root_url }}/package/{{ pkg.name }}/{{ pkg.version }}">{{ pkg.wrappable_version|safe }}</a>
+ {% else %}
+ <i>n/a</i>
+ {% endif %}
+ </td>
+ {% endfor %}
+</tr>
+{% endfor %}
+</table>
+{% endblock %}
diff --git a/templates/searchresults.tmpl b/templates/searchresults.tmpl
deleted file mode 100644
index 76bf5ff..0000000
--- a/templates/searchresults.tmpl
+++ /dev/null
@@ -1,34 +0,0 @@
-#import templates
-#extends templates.skeleton
-#def title
-Debian Project patch tracking system - Search Results
-#end def
-#def body
-<h1>Debian Project patch tracking system</h1>
-<h2>Search Results - $searchtype: $idx</h2>
-<table class="packagelisting">
-<tr>
-<th>package</th>
-#for $d in $dists
- <th>$d</th>
-#end for
-</tr>
-#for $p in $sorted($pkgs.iterkeys)
-<tr>
- <td>
- <a name="$p" />
- <a href="http://packages.debian.org/$p">$p</a>
- </td>
- #for $d in $dists
- <td>
- #if $pkgs[$p].has_key($d)
- <a href="$conf.root_url/package/$p/$pkgs[$p][$d].version">$pkgs[$p][$d].version</a>
- #else
- <i>n/a</i>
- #end if
- </td>
- #end for
-</tr>
-#end for
-</table>
-#end def
diff --git a/urls.py b/urls.py
new file mode 100644
index 0000000..402c19c
--- /dev/null
+++ b/urls.py
@@ -0,0 +1,15 @@
+import os
+
+from django.conf.urls.defaults import *
+from django.contrib import admin
+admin.autodiscover()
+
+urlpatterns = patterns('',
+ # admin interface (for debugging, mostly)
+ (r'^admin/', include(admin.site.urls)),
+ # static content
+ (r'^static/(?P<path>.*)$', 'django.views.static.serve',
+ {'document_root': os.path.sep.join([os.curdir, "static"])}),
+
+ (r'^', include('patchtracker.urls')),
+)