1
by Stefano Rivera
Initial commit |
1 |
#!/usr/bin/env python
|
2 |
||
3 |
import cgi |
|
4 |
import cgitb |
|
5 |
import re |
|
6
by Stefano Rivera
Missing import, type -> render |
6 |
import urllib |
1
by Stefano Rivera
Initial commit |
7 |
|
8 |
import genshi.template |
|
9 |
import genshi.template.text |
|
10 |
import psycopg2 |
|
11 |
import psycopg2.extensions |
|
12 |
||
13 |
URL = 'ubuntu-sponsorships.cgi' |
|
2
by Stefano Rivera
Reorganise |
14 |
STATIC_URL = '../ubuntu-sponsorships' |
15 |
STATIC_PATH = '../htdocs/ubuntu-sponsorships' |
|
1
by Stefano Rivera
Initial commit |
16 |
DB = 'service=udd' |
15
by Stefano Rivera
Reorganise corner case code, to use defaults in main(). Limit queries |
17 |
LIMIT = 1000 |
1
by Stefano Rivera
Initial commit |
18 |
|
19 |
class AttrDict(dict): |
|
20 |
"""Dictionary with attribute access"""
|
|
21 |
||
22 |
def __init__(self, **kwargs): |
|
23 |
for key, value in kwargs.iteritems(): |
|
24 |
self[key] = value |
|
25 |
||
26 |
def __getattr__(self, name): |
|
27 |
try: |
|
28 |
return self[name] |
|
29 |
except KeyError, e: |
|
30 |
raise AttributeError(e) |
|
31 |
||
32 |
||
33 |
def get_template(name, type_='html'): |
|
34 |
"""Load a template"""
|
|
2
by Stefano Rivera
Reorganise |
35 |
loader = genshi.template.TemplateLoader(STATIC_PATH) |
1
by Stefano Rivera
Initial commit |
36 |
kwargs = {} |
37 |
if type_ == 'text': |
|
38 |
kwargs['cls'] = genshi.template.text.NewTextTemplate |
|
39 |
return loader.load(name, **kwargs) |
|
40 |
||
7
by Stefano Rivera
Styled form, search options |
41 |
def locate_sponsorships(sponsor, sponsor_search, sponsoree, sponsoree_search): |
1
by Stefano Rivera
Initial commit |
42 |
"""Query UDD for sponsorhips. Yields rows"""
|
15
by Stefano Rivera
Reorganise corner case code, to use defaults in main(). Limit queries |
43 |
|
44 |
# String-substituted into query:
|
|
7
by Stefano Rivera
Styled form, search options |
45 |
assert sponsor_search in ('name', 'email') |
46 |
assert sponsoree_search in ('name', 'email') |
|
47 |
||
1
by Stefano Rivera
Initial commit |
48 |
# Make sure we are dealing with Unicode:
|
49 |
psycopg2.extensions.register_type(psycopg2.extensions.UNICODE) |
|
50 |
psycopg2.extensions.register_type(psycopg2.extensions.UNICODEARRAY) |
|
51 |
conn = psycopg2.connect(DB) |
|
52 |
conn.set_client_encoding('UTF-8') |
|
53 |
cur = conn.cursor() |
|
54 |
||
55 |
sponsor = sponsor.replace('*', '%') |
|
13
by Stefano Rivera
Accept null sponsor / sponsoree |
56 |
if sponsor == '': |
57 |
sponsor = '%' |
|
1
by Stefano Rivera
Initial commit |
58 |
sponsoree = sponsoree.replace('*', '%') |
13
by Stefano Rivera
Accept null sponsor / sponsoree |
59 |
if sponsoree == '': |
60 |
sponsoree = '%' |
|
1
by Stefano Rivera
Initial commit |
61 |
|
62 |
cur.execute(""" |
|
63 |
SELECT source, version, date, changed_by, changed_by_name,
|
|
64 |
changed_by_email, signed_by, signed_by_name, signed_by_email,
|
|
65 |
distribution,
|
|
66 |
array_to_string(ARRAY(
|
|
67 |
SELECT bug
|
|
68 |
FROM ubuntu_upload_history_launchpad_closes
|
|
69 |
WHERE source = ubuntu_upload_history.source
|
|
70 |
AND version = ubuntu_upload_history.version
|
|
71 |
), ' ') AS fixed
|
|
72 |
FROM ubuntu_upload_history
|
|
7
by Stefano Rivera
Styled form, search options |
73 |
WHERE lower(changed_by_%s) LIKE lower(%%s) |
14
by Stefano Rivera
Sponsorships only |
74 |
AND lower(signed_by_%s) LIKE lower(%%s) |
15
by Stefano Rivera
Reorganise corner case code, to use defaults in main(). Limit queries |
75 |
AND signed_by_name <> changed_by_name
|
17
by Stefano Rivera
ORDER by date |
76 |
ORDER BY date
|
15
by Stefano Rivera
Reorganise corner case code, to use defaults in main(). Limit queries |
77 |
LIMIT %i; |
78 |
""" % (sponsoree_search, sponsor_search, LIMIT) |
|
7
by Stefano Rivera
Styled form, search options |
79 |
, (sponsoree, sponsor)) |
1
by Stefano Rivera
Initial commit |
80 |
keys = ('source version date changed_by changed_by_name changed_by_email ' |
81 |
'signed_by signed_by_name signed_by_email distribution fixed'
|
|
82 |
).split() |
|
83 |
for row in cur.fetchall(): |
|
84 |
upload = AttrDict(**dict(zip(keys, row))) |
|
85 |
yield upload |
|
86 |
||
87 |
def mine_sponsorships(sponsorships): |
|
88 |
"""Iterate over sponsorships and guess what we can
|
|
89 |
yields sponsorhips
|
|
90 |
"""
|
|
91 |
resyncable_re = re.compile(r'[-\d](fakesync|build)\d+$') |
|
92 |
ubuver_re = re.compile(r'[-\d](\d+)?ubuntu(\d+)$') |
|
93 |
for upload in sponsorships: |
|
94 |
upload['fixed'] = upload['fixed'].split() |
|
95 |
upload['release'] = upload.distribution.split('-')[0] |
|
96 |
info = set() |
|
97 |
||
98 |
if upload.distribution.endswith('-proposed'): |
|
99 |
info.add('sru') |
|
100 |
if upload.distribution.endswith('-security'): |
|
101 |
info.add('security') |
|
102 |
||
103 |
m = resyncable_re.search(upload.version) |
|
104 |
if m is not None: |
|
105 |
if m.group(1) == 'fakesync': |
|
106 |
info.add('sync') |
|
107 |
elif m.group(1) == 'build': |
|
108 |
info.add('rebuild') |
|
109 |
else: |
|
110 |
info.add(m.group(1)) |
|
111 |
||
112 |
m = ubuver_re.search(upload.version) |
|
113 |
if m is not None: |
|
114 |
if m.group(2) == '1': |
|
115 |
if m.group(1) == '0': |
|
116 |
info.add('upgrade') |
|
117 |
||
118 |
upload['info'] = sorted(info) |
|
119 |
# Things we can't determine without changelog / lpapi:
|
|
120 |
# sync, merge
|
|
121 |
yield upload |
|
122 |
||
7
by Stefano Rivera
Styled form, search options |
123 |
def display_sponsorships(sponsor, sponsor_search, sponsoree, sponsoree_search, |
124 |
type_): |
|
1
by Stefano Rivera
Initial commit |
125 |
"""Return rendering of sponsorships"""
|
126 |
if type_ == 'html': |
|
127 |
template = get_template('sponsorships.xml') |
|
128 |
else: |
|
129 |
template = get_template('sponsorships.txt', 'text') |
|
7
by Stefano Rivera
Styled form, search options |
130 |
|
1
by Stefano Rivera
Initial commit |
131 |
params = { |
3
by Stefano Rivera
Provide static URL for templates |
132 |
'static': STATIC_URL, |
1
by Stefano Rivera
Initial commit |
133 |
'url': URL, |
5
by Stefano Rivera
Correct script tag, provide links on HTML search |
134 |
'text_url': URL + '?' + urllib.urlencode({ |
7
by Stefano Rivera
Styled form, search options |
135 |
'render': 'text', |
136 |
'sponsor': sponsor, |
|
137 |
'sponsor_search': sponsor_search, |
|
138 |
'sponsoree': sponsoree, |
|
12
by Stefano Rivera
Carry sponsoree_search through correctly |
139 |
'sponsoree_search': sponsoree_search, |
7
by Stefano Rivera
Styled form, search options |
140 |
}),
|
8
by Stefano Rivera
Missing parameters |
141 |
'sponsorships': mine_sponsorships(locate_sponsorships( |
142 |
sponsor, |
|
143 |
sponsor_search, |
|
144 |
sponsoree, |
|
145 |
sponsoree_search, |
|
146 |
)),
|
|
1
by Stefano Rivera
Initial commit |
147 |
}
|
148 |
return template.generate(**params).render(type_) |
|
149 |
||
150 |
def display_form(): |
|
151 |
"""Return rendered search form"""
|
|
152 |
template = get_template('form.xml') |
|
153 |
params = { |
|
3
by Stefano Rivera
Provide static URL for templates |
154 |
'static': STATIC_URL, |
1
by Stefano Rivera
Initial commit |
155 |
'url': URL, |
156 |
}
|
|
157 |
return template.generate(**params).render('html') |
|
158 |
||
159 |
def main(): |
|
160 |
cgitb.enable() |
|
161 |
form = cgi.FieldStorage() |
|
15
by Stefano Rivera
Reorganise corner case code, to use defaults in main(). Limit queries |
162 |
|
1
by Stefano Rivera
Initial commit |
163 |
body = 'ERROR: No body' |
15
by Stefano Rivera
Reorganise corner case code, to use defaults in main(). Limit queries |
164 |
|
165 |
render = form.getfirst('render', '') |
|
166 |
sponsor = form.getfirst('sponsor', '') |
|
167 |
sponsoree = form.getfirst('sponsoree', '') |
|
168 |
if render and (sponsor or sponsoree): |
|
169 |
sponsor_search = form.getfirst('sponsor_search') |
|
170 |
if sponsor_search not in ('name', 'email'): |
|
171 |
sponsor_search = 'name' |
|
172 |
sponsoree_search = form.getfirst('sponsoree_search') |
|
173 |
if sponsoree_search not in ('name', 'email'): |
|
174 |
sponsoree_search = 'name' |
|
175 |
if render not in ('html', 'text'): |
|
176 |
render = 'html' |
|
177 |
body = display_sponsorships(sponsor, sponsor_search, sponsoree, |
|
178 |
sponsoree_search, render) |
|
1
by Stefano Rivera
Initial commit |
179 |
else: |
15
by Stefano Rivera
Reorganise corner case code, to use defaults in main(). Limit queries |
180 |
render = 'html' |
1
by Stefano Rivera
Initial commit |
181 |
body = display_form() |
182 |
||
15
by Stefano Rivera
Reorganise corner case code, to use defaults in main(). Limit queries |
183 |
if render == 'text': |
184 |
print "Content-Type: text/plain; charset=utf-8" |
|
185 |
else: |
|
1
by Stefano Rivera
Initial commit |
186 |
print "Content-Type: text/html; charset=utf-8" |
187 |
print "" |
|
188 |
print body |
|
189 |
||
190 |
if __name__ == '__main__': |
|
191 |
main() |