Skip to content

Commit fe34d1b

Browse files
committed
merge unintnetional fork
2 parents bc04e57 + 90c0a5b commit fe34d1b

File tree

5 files changed

+265
-17
lines changed

5 files changed

+265
-17
lines changed

.circleci/config.yml

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -31,22 +31,23 @@ jobs:
3131
name: setup databases
3232
command: |
3333
psql -d postgresql://postgres@localhost/circle_test -c "CREATE ROLE rounduptest WITH CREATEDB LOGIN PASSWORD 'rounduptest';"
34-
mysql -h 127.0.0.1 -u root -e "CREATE USER 'rounduptest'@'127.0.0.1' IDENTIFIED BY 'rounduptest'; GRANT ALL ON rounduptest.* TO 'rounduptest'@'127.0.0.1';"
35-
# patch host to 127.0.0.1 to force TCP connection to MySQL
36-
sed -i -e 's/\(config[.]RDBMS_HOST =\) "localhost"/\1 "127.0.0.1"/' test/db_test_base.py
34+
echo -e "[client]\nprotocol=tcp\n" >~/.my.cnf
35+
mysql --print-defaults
36+
mysql -u root -e "CREATE USER 'rounduptest'@'127.0.0.1' IDENTIFIED BY 'rounduptest'; GRANT ALL ON rounduptest.* TO 'rounduptest'@'127.0.0.1';"
3737
3838
- run:
3939
name: run tests
4040
command: |
41-
py.test-3 -v test/ --cov=roundup
42-
environment:
43-
MYSQL_HOST: 127.0.0.1
41+
py.test-3 -v test/ --cov=roundup --junitxml test-results/ubuntu.xml
4442
4543
- run:
4644
name: run coverage
4745
command: |
4846
python3-coverage html -i
4947
48+
- store_test_results:
49+
path: test-results
50+
5051
- store_artifacts:
5152
path: htmlcov
5253
destination: roundup-ubuntu
@@ -82,22 +83,23 @@ jobs:
8283
name: setup databases
8384
command: |
8485
psql -d postgresql://postgres@localhost/circle_test -c "CREATE ROLE rounduptest WITH CREATEDB LOGIN PASSWORD 'rounduptest';"
85-
mysql -h 127.0.0.1 -u root -e "GRANT ALL ON rounduptest.* TO rounduptest@'127.0.0.1' IDENTIFIED BY \"rounduptest\";"
86-
# patch host to 127.0.0.1 to force TCP connection to MySQL
87-
sed -i -e 's/\(config[.]RDBMS_HOST =\) "localhost"/\1 "127.0.0.1"/' test/db_test_base.py
86+
echo -e "[client]\nprotocol=tcp\n" >~/.my.cnf
87+
mysql --print-defaults
88+
mysql -u root -e "CREATE USER 'rounduptest'@'127.0.0.1' IDENTIFIED BY 'rounduptest'; GRANT ALL ON rounduptest.* TO 'rounduptest'@'127.0.0.1';"
8889
8990
- run:
9091
name: run tests
9192
command: |
92-
py.test-3 -v test/ --cov=roundup
93-
environment:
94-
MYSQL_HOST: 127.0.0.1
93+
py.test-3 -v test/ --cov=roundup --junitxml test-results/ubuntu-old.xml
9594
9695
- run:
9796
name: run coverage
9897
command: |
9998
python3-coverage html -i
10099
100+
- store_test_results:
101+
path: test-results
102+
101103
- store_artifacts:
102104
path: htmlcov
103105
destination: roundup-ubuntu-old
@@ -133,22 +135,26 @@ jobs:
133135
name: setup databases
134136
command: |
135137
psql -d postgresql://postgres@localhost/circle_test -c "CREATE ROLE rounduptest WITH CREATEDB LOGIN PASSWORD 'rounduptest';"
136-
mysql -h 127.0.0.1 -u root -e "GRANT ALL ON rounduptest.* TO rounduptest@'127.0.0.1' IDENTIFIED BY \"rounduptest\";"
138+
echo -e "[client]\nprotocol=tcp\nhost=127.0.0.1\n" >~/.my.cnf
139+
mysql --print-defaults
140+
mysql -u root -e "CREATE USER 'rounduptest'@'127.0.0.1' IDENTIFIED BY 'rounduptest'; GRANT ALL ON rounduptest.* TO 'rounduptest'@'127.0.0.1';"
137141
# patch host to 127.0.0.1 to force TCP connection to MySQL
138142
sed -i -e 's/\(config[.]RDBMS_HOST =\) "localhost"/\1 "127.0.0.1"/' test/db_test_base.py
143+
sed -i -e 's/rdbms_name=\([^,]\+\),/rdbms_host=127.0.0.1,rdbms_name=\1,/' test/test_admin.py
139144
140145
- run:
141146
name: run tests
142147
command: |
143-
py.test-3 -v test/ --cov=roundup
144-
environment:
145-
MYSQL_HOST: 127.0.0.1
148+
py.test-3 -v test/ --cov=roundup --junitxml test-results/debian.xml
146149
147150
- run:
148151
name: run coverage
149152
command: |
150153
python3-coverage html -i
151154
155+
- store_test_results:
156+
path: test-results
157+
152158
- store_artifacts:
153159
path: htmlcov
154160
destination: roundup-debian

CHANGES.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ Python v2.5 and v2.6. Starting with the v1.6 releases of Roundup
1111
v2.7.2 or later are required to run newer releases of Roundup. From v2.0
1212
onwards Python 3.4 and later are also supported.
1313

14+
XXXX-XX-XX 2.0.1
15+
16+
Fixed:
17+
- Reverse multilink to *the same class* would trigger a traceback about
18+
a modified dictionary on iteration (Ralf Schlatterbeck)
19+
1420
2020-07-13 2.0.0
1521

1622
Fixed:

roundup/hyperdb.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -875,7 +875,9 @@ def post_init(self):
875875
done = getattr(self, 'post_init_done', None)
876876
for cn in self.getclasses():
877877
cl = self.getclass(cn)
878-
for p in cl.properties:
878+
# This will change properties if a back-multilink happens to
879+
# have the same class, so we need to iterate over .keys()
880+
for p in cl.properties.keys():
879881
prop = cl.properties[p]
880882
if not isinstance (prop, (Link, Multilink)):
881883
continue

website/issues/detectors/rsswriter.py

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
#!/usr/bin/python
2+
3+
#
4+
# RSS writer Roundup reactor
5+
# Mark Paschal <[email protected]>
6+
#
7+
8+
import os
9+
10+
import logging
11+
logger = logging.getLogger('detector')
12+
13+
import sys
14+
15+
# How many <item>s to have in the feed, at most.
16+
MAX_ITEMS = 30
17+
18+
#
19+
# Module metadata
20+
#
21+
22+
__author__ = "Mark Paschal <[email protected]>"
23+
__copyright__ = "Copyright 2003 Mark Paschal"
24+
__version__ = "1.2"
25+
26+
__changes__ = """
27+
1.1 29 Aug 2003 Produces valid pubDates. Produces pubDates and authors for
28+
change notes. Consolidates a message and change note into one
29+
item. Uses TRACKER_NAME in filename to produce one feed per
30+
tracker. Keeps to MAX_ITEMS limit more efficiently.
31+
1.2 5 Sep 2003 Fixes bug with programmatically submitted issues having
32+
messages without summaries (?!).
33+
x.x 26 Feb 2017 John Rouillard try to deal with truncation of rss
34+
file cause by error in parsing 8'bit characcters in
35+
input message. Further attempts to fix issue by
36+
modifying message bail on 0 length rss file. Delete
37+
it and retry.
38+
"""
39+
40+
__license__ = 'MIT'
41+
42+
#
43+
# Copyright 2003 Mark Paschal
44+
#
45+
# Permission is hereby granted, free of charge, to any person obtaining a copy
46+
# of this software and associated documentation files (the "Software"), to deal
47+
# in the Software without restriction, including without limitation the rights
48+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
49+
# copies of the Software, and to permit persons to whom the Software is
50+
# furnished to do so, subject to the following conditions:
51+
#
52+
# The above copyright notice and this permission notice shall be included in all
53+
# copies or substantial portions of the Software.
54+
#
55+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
56+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
57+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
58+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
59+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
60+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
61+
# SOFTWARE.
62+
#
63+
64+
65+
# The strftime format to use for <pubDate>s.
66+
RSS20_DATE_FORMAT = '%a, %d %b %Y %H:%M:%S %z'
67+
68+
69+
def newRss(title, link, description):
70+
"""Returns an XML Document containing an RSS 2.0 feed with no items."""
71+
import xml.dom.minidom
72+
rss = xml.dom.minidom.Document()
73+
74+
root = rss.appendChild(rss.createElement("rss"))
75+
root.setAttribute("version", "2.0")
76+
root.setAttribute("xmlns:atom","http://www.w3.org/2005/Atom")
77+
78+
channel = root.appendChild(rss.createElement("channel"))
79+
addEl = lambda tag,value: channel.appendChild(rss.createElement(tag)).appendChild(rss.createTextNode(value))
80+
def addElA(tag,attr):
81+
node=rss.createElement(tag)
82+
for attr, val in attr.items():
83+
node.setAttribute(attr, val)
84+
channel.appendChild(node)
85+
86+
addEl("title", title)
87+
addElA('atom:link', attr={"rel": "self",
88+
"type": "application/rss+xml", "href": link + "@@file/rss.xml"})
89+
addEl("link", link)
90+
addEl("description", description)
91+
92+
return rss # has no items
93+
94+
95+
def writeRss(db, cl, nodeid, olddata):
96+
"""
97+
Reacts to a created or changed issue. Puts new messages and the change note
98+
in items in the RSS feed, as determined by the rsswriter.py FILENAME setting.
99+
If no RSS feed exists where FILENAME specifies, a new feed is created with
100+
rsswriter.newRss.
101+
"""
102+
103+
# The filename of a tracker's RSS feed. Tracker config variables
104+
# are placed with the standard '%' operator syntax.
105+
106+
FILENAME = "%s/rss.xml"%db.config['TEMPLATES']
107+
108+
# i.e., roundup.cgi/projects/_file/rss.xml
109+
# FILENAME = "/home/markpasc/public_html/%(TRACKER_NAME)s.xml"
110+
111+
filename = FILENAME % db.config.__dict__
112+
113+
# return if issue is private
114+
if ( db.issue.get(nodeid, 'private') ):
115+
if __debug__:
116+
logger.debug("rss: Private issue. not generating rss")
117+
return
118+
119+
if __debug__:
120+
logger.debug("rss: generating rss for issue %s", nodeid)
121+
122+
# open the RSS
123+
import xml.dom.minidom
124+
from xml.parsers.expat import ExpatError
125+
126+
try:
127+
rss = xml.dom.minidom.parse(filename)
128+
except IOError as e:
129+
if 2 != e.errno: raise
130+
# File not found
131+
rss = newRss(
132+
"%s tracker" % (db.config.TRACKER_NAME,),
133+
db.config.TRACKER_WEB,
134+
"Recent changes to the %s Roundup issue tracker" % (db.config.TRACKER_NAME,)
135+
)
136+
except ExpatError as e:
137+
if os.path.getsize(filename) == 0:
138+
# delete the file, it's broke
139+
os.remove(filename)
140+
# create new rss file
141+
rss = newRss(
142+
"%s tracker" % (db.config.TRACKER_NAME,),
143+
db.config.TRACKER_WEB,
144+
"Recent changes to the %s Roundup issue tracker" % (db.config.TRACKER_NAME,)
145+
)
146+
else:
147+
raise
148+
149+
channel = rss.documentElement.getElementsByTagName('channel')[0]
150+
addEl = lambda parent,tag,value: parent.appendChild(rss.createElement(tag)).appendChild(rss.createTextNode(value))
151+
issuelink = '%sissue%s' % (db.config.TRACKER_WEB, nodeid)
152+
153+
154+
if olddata:
155+
chg = cl.generateChangeNote(nodeid, olddata)
156+
else:
157+
chg = cl.generateCreateNote(nodeid)
158+
159+
def addItem(desc, date, userid):
160+
"""
161+
Adds an RSS item to the RSS document. The title, link, and comments
162+
link are those of the current issue.
163+
164+
desc: the description text to use
165+
date: an appropriately formatted string for pubDate
166+
userid: a Roundup user ID to use as author
167+
"""
168+
169+
item = rss.createElement('item')
170+
171+
addEl(item, 'title', db.issue.get(nodeid, 'title'))
172+
addEl(item, 'link', issuelink)
173+
addEl(item, 'guid', issuelink + '#' + date.replace(' ','+'))
174+
addEl(item, 'comments', issuelink)
175+
addEl(item, 'description', desc.replace('&','&amp;').replace('<','&lt;').replace('\n', '<br>\n'))
176+
addEl(item, 'pubDate', date)
177+
addEl(item, 'author',
178+
'%s (%s)' % (
179+
db.user.get(userid, 'address'),
180+
db.user.get(userid, 'username')
181+
)
182+
)
183+
184+
channel.appendChild(item)
185+
186+
# add detectors directory to path if it's not there.
187+
# FIXME - see if this pollutes the sys.path for other
188+
# trackers.
189+
detector_path="%s/detectors"%(db.config.TRACKER_HOME)
190+
if ( sys.path.count(detector_path) == 0 ):
191+
sys.path.insert(0,detector_path)
192+
193+
from nosyreaction import determineNewMessages
194+
for msgid in determineNewMessages(cl, nodeid, olddata):
195+
logger.debug("Processing new message msg%s for issue%s", msgid, nodeid)
196+
desc = db.msg.get(msgid, 'content')
197+
198+
if desc and chg:
199+
desc += chg
200+
elif chg:
201+
desc = chg
202+
chg = None
203+
204+
addItem(desc or '', db.msg.get(msgid, 'date').pretty(RSS20_DATE_FORMAT), db.msg.get(msgid, 'author'))
205+
206+
if chg:
207+
from time import strftime
208+
addItem(chg.replace('\n----------\n', ''), strftime(RSS20_DATE_FORMAT), db.getuid())
209+
210+
211+
for c in channel.getElementsByTagName('item')[0:-MAX_ITEMS]: # leaves at most MAX_ITEMS at the end
212+
channel.removeChild(c)
213+
214+
# write the RSS
215+
out = open(filename, 'w')
216+
217+
try:
218+
out.write(rss.toxml())
219+
except Exception as e:
220+
# record the falure This should not happen.
221+
logger.error(e)
222+
out.close() # create 0 length file maybe?? But we handle above.
223+
raise # let the user know something went wrong.
224+
225+
out.close()
226+
227+
228+
def init(db):
229+
db.issue.react('create', writeRss)
230+
db.issue.react('set', writeRss)
231+
#SHA: f4c0ccb5d0d9a6ef7829696333b33bc0619b0167

website/issues/html/page.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
</script>
1313
<metal:x define-slot="more-javascript" />
1414

15+
<link rel="alternate" type="application/rss+xml"
16+
tal:attributes="href string:${request/base}@@file/rss.xml"
17+
title="Publicly available tracker feed.">
1518
</head>
1619
<body
1720
tal:define="

0 commit comments

Comments
 (0)