You can subscribe to this list here.
| 2001 |
Jan
|
Feb
|
Mar
|
Apr
|
May
|
Jun
|
Jul
(226) |
Aug
(123) |
Sep
(22) |
Oct
(143) |
Nov
(135) |
Dec
(92) |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 2002 |
Jan
(205) |
Feb
(118) |
Mar
(29) |
Apr
(57) |
May
(133) |
Jun
(71) |
Jul
(209) |
Aug
(94) |
Sep
(467) |
Oct
(139) |
Nov
(38) |
Dec
(63) |
| 2003 |
Jan
(125) |
Feb
(150) |
Mar
(159) |
Apr
(106) |
May
(50) |
Jun
(87) |
Jul
(23) |
Aug
(103) |
Sep
(78) |
Oct
(87) |
Nov
(116) |
Dec
(58) |
| 2004 |
Jan
(57) |
Feb
(117) |
Mar
(213) |
Apr
(136) |
May
(246) |
Jun
(254) |
Jul
(234) |
Aug
(26) |
Sep
(61) |
Oct
(191) |
Nov
(199) |
Dec
(80) |
| 2005 |
Jan
(196) |
Feb
(204) |
Mar
(46) |
Apr
(115) |
May
(63) |
Jun
(66) |
Jul
(52) |
Aug
(4) |
Sep
(20) |
Oct
(16) |
Nov
(3) |
Dec
(24) |
| 2006 |
Jan
(165) |
Feb
(93) |
Mar
(40) |
Apr
(44) |
May
(11) |
Jun
(37) |
Jul
(39) |
Aug
(96) |
Sep
(19) |
Oct
(36) |
Nov
(68) |
Dec
(51) |
| 2007 |
Jan
(18) |
Feb
(12) |
Mar
(22) |
Apr
(26) |
May
(9) |
Jun
(3) |
Jul
(3) |
Aug
(25) |
Sep
(83) |
Oct
(12) |
Nov
(31) |
Dec
(9) |
| 2008 |
Jan
(6) |
Feb
(26) |
Mar
(12) |
Apr
(1) |
May
|
Jun
|
Jul
(5) |
Aug
(64) |
Sep
(19) |
Oct
|
Nov
|
Dec
(1) |
| 2009 |
Jan
|
Feb
(97) |
Mar
(36) |
Apr
|
May
(1) |
Jun
(28) |
Jul
(96) |
Aug
(15) |
Sep
(8) |
Oct
(26) |
Nov
(10) |
Dec
(23) |
| 2010 |
Jan
(20) |
Feb
(30) |
Mar
(5) |
Apr
(7) |
May
(2) |
Jun
(2) |
Jul
(25) |
Aug
(9) |
Sep
(9) |
Oct
(33) |
Nov
(16) |
Dec
(1) |
| 2011 |
Jan
(1) |
Feb
(1) |
Mar
(5) |
Apr
(18) |
May
(12) |
Jun
(8) |
Jul
(20) |
Aug
(2) |
Sep
(6) |
Oct
(17) |
Nov
|
Dec
|
| 2012 |
Jan
|
Feb
(1) |
Mar
|
Apr
(16) |
May
(6) |
Jun
(4) |
Jul
(12) |
Aug
(6) |
Sep
(6) |
Oct
(7) |
Nov
(34) |
Dec
(49) |
| 2013 |
Jan
(58) |
Feb
(35) |
Mar
(12) |
Apr
(15) |
May
(10) |
Jun
(8) |
Jul
(21) |
Aug
|
Sep
(50) |
Oct
(14) |
Nov
(6) |
Dec
(10) |
| 2014 |
Jan
(3) |
Feb
(2) |
Mar
(46) |
Apr
(21) |
May
(12) |
Jun
(4) |
Jul
(22) |
Aug
(15) |
Sep
(6) |
Oct
(23) |
Nov
(10) |
Dec
(23) |
| 2015 |
Jan
(6) |
Feb
(4) |
Mar
(39) |
Apr
(4) |
May
(6) |
Jun
(4) |
Jul
(2) |
Aug
(7) |
Sep
(7) |
Oct
(4) |
Nov
|
Dec
(2) |
| 2016 |
Jan
(59) |
Feb
|
Mar
(2) |
Apr
(16) |
May
(19) |
Jun
(75) |
Jul
(93) |
Aug
(6) |
Sep
(4) |
Oct
(4) |
Nov
(2) |
Dec
(6) |
| 2017 |
Jan
(12) |
Feb
(18) |
Mar
(52) |
Apr
(31) |
May
(3) |
Jun
(2) |
Jul
|
Aug
(35) |
Sep
(49) |
Oct
(22) |
Nov
(6) |
Dec
|
| 2018 |
Jan
|
Feb
|
Mar
(4) |
Apr
(12) |
May
(9) |
Jun
(28) |
Jul
(230) |
Aug
(76) |
Sep
(48) |
Oct
(4) |
Nov
(4) |
Dec
|
| 2019 |
Jan
(55) |
Feb
(33) |
Mar
(99) |
Apr
(60) |
May
(58) |
Jun
(135) |
Jul
(39) |
Aug
(49) |
Sep
(25) |
Oct
(138) |
Nov
(39) |
Dec
(34) |
| 2020 |
Jan
(84) |
Feb
(82) |
Mar
(9) |
Apr
(40) |
May
(54) |
Jun
(54) |
Jul
(57) |
Aug
(19) |
Sep
(17) |
Oct
(26) |
Nov
(16) |
Dec
(27) |
| 2021 |
Jan
(18) |
Feb
(15) |
Mar
(72) |
Apr
(41) |
May
(66) |
Jun
(39) |
Jul
(20) |
Aug
(33) |
Sep
(41) |
Oct
(31) |
Nov
(35) |
Dec
(69) |
| 2022 |
Jan
(60) |
Feb
(15) |
Mar
(18) |
Apr
(39) |
May
(74) |
Jun
(97) |
Jul
(105) |
Aug
(61) |
Sep
(249) |
Oct
(78) |
Nov
(83) |
Dec
(49) |
| 2023 |
Jan
(23) |
Feb
(113) |
Mar
(60) |
Apr
(79) |
May
(230) |
Jun
(125) |
Jul
(126) |
Aug
(32) |
Sep
(66) |
Oct
(55) |
Nov
(32) |
Dec
(28) |
| 2024 |
Jan
(13) |
Feb
(34) |
Mar
(126) |
Apr
(112) |
May
(109) |
Jun
(55) |
Jul
(94) |
Aug
(13) |
Sep
(8) |
Oct
(43) |
Nov
(54) |
Dec
(129) |
| 2025 |
Jan
(91) |
Feb
(10) |
Mar
(6) |
Apr
(1) |
May
(24) |
Jun
(49) |
Jul
(62) |
Aug
(62) |
Sep
(36) |
Oct
(11) |
Nov
(23) |
Dec
(35) |
| 2026 |
Jan
(16) |
Feb
(16) |
Mar
(35) |
Apr
(86) |
May
(38) |
Jun
|
Jul
|
Aug
|
Sep
|
Oct
|
Nov
|
Dec
|
|
From: Mercurial C. <th...@in...> - 2026-05-19 03:53:11
|
# HG changeset patch
# User John Rouillard <ro...@ie...>
# Date 1779162686 14400
# Mon May 18 23:51:26 2026 -0400
# Node ID 6e4c9e5af3d997b526cd228a06df18fde94927b4
# Parent cafa177776bbb83a0e7dc44329dd8abba82f082f
bug: missing a close ')'
diff -r cafa177776bb -r 6e4c9e5af3d9 roundup/demo.py
--- a/roundup/demo.py Mon May 18 15:25:49 2026 -0400
+++ b/roundup/demo.py Mon May 18 23:51:26 2026 -0400
@@ -133,7 +133,7 @@
cfg = configuration.CoreConfig(home)
except configuration.ConfigurationError as e:
print(e)
- print("when processing: %s", os.path.join(home, "config.ini")
+ print("when processing: %s", os.path.join(home, "config.ini"))
sys.exit(2)
url = cfg["TRACKER_WEB"]
|
|
From: Mercurial C. <th...@in...> - 2026-05-19 03:53:10
|
New changeset in roundup: pushed by: rouilj https://sourceforge.net/p/roundup/code/ci/6e4c9e5af3d9 changeset: 8650:6e4c9e5af3d9 tag: tip user: John Rouillard <ro...@ie...> date: Mon May 18 23:51:26 2026 -0400 summary: bug: missing a close ')' -- Repository URL: https://sourceforge.net/p/roundup/code |
|
From: Mercurial C. <th...@in...> - 2026-05-18 19:45:56
|
# HG changeset patch
# User John Rouillard <ro...@ie...>
# Date 1779132349 14400
# Mon May 18 15:25:49 2026 -0400
# Node ID cafa177776bbb83a0e7dc44329dd8abba82f082f
# Parent d2da5f9c2ea8a2947459c517c1b0ca0f7284c4c9
bug: handle ConfigurationError when running demo.
Don't report traceback if a ConfigurationError occurs when running the
demo tracker. Just report the error and the config.ini absolute
filename.
diff -r d2da5f9c2ea8 -r cafa177776bb CHANGES.txt
--- a/CHANGES.txt Mon May 18 11:25:36 2026 -0400
+++ b/CHANGES.txt Mon May 18 15:25:49 2026 -0400
@@ -125,6 +125,8 @@
again after 5 seconds. (John Rouillard)
- Fix developers.txt doc bug (discovered by Ross Boylan, change by
John Rouillard)
+- Handle ConfigurationErrors in demo.py cleanly. Used to dump a full
+ traceback. Now prints error and exits. (John Rouillard)
Features:
diff -r d2da5f9c2ea8 -r cafa177776bb roundup/demo.py
--- a/roundup/demo.py Mon May 18 11:25:36 2026 -0400
+++ b/roundup/demo.py Mon May 18 15:25:49 2026 -0400
@@ -129,7 +129,13 @@
print("\nDemo Tracker Home:", home)
- cfg = configuration.CoreConfig(home)
+ try:
+ cfg = configuration.CoreConfig(home)
+ except configuration.ConfigurationError as e:
+ print(e)
+ print("when processing: %s", os.path.join(home, "config.ini")
+ sys.exit(2)
+
url = cfg["TRACKER_WEB"]
try:
hostname, port = urllib_.urlsplit(url)[1].split(':')
|
|
From: Mercurial C. <th...@in...> - 2026-05-18 19:45:55
|
# HG changeset patch # User John Rouillard <ro...@ie...> # Date 1779117936 14400 # Mon May 18 11:25:36 2026 -0400 # Node ID d2da5f9c2ea8a2947459c517c1b0ca0f7284c4c9 # Parent c51756a8a3685210fc0392d63d8a028ec42d7428 doc: fix bad directions for starting demo server from hg clone Ross Boylan reported incomplete and wrong directions for starting the demo server from an hg clone. I modified his suggested changes a bit. diff -r c51756a8a368 -r d2da5f9c2ea8 CHANGES.txt --- a/CHANGES.txt Mon May 18 01:17:22 2026 -0400 +++ b/CHANGES.txt Mon May 18 11:25:36 2026 -0400 @@ -123,6 +123,8 @@ submits the form. Clicking it says the form has been submitted please wait. This change allows the submit button to submit the form again after 5 seconds. (John Rouillard) +- Fix developers.txt doc bug (discovered by Ross Boylan, change by + John Rouillard) Features: diff -r c51756a8a368 -r d2da5f9c2ea8 doc/developers.txt --- a/doc/developers.txt Mon May 18 01:17:22 2026 -0400 +++ b/doc/developers.txt Mon May 18 11:25:36 2026 -0400 @@ -114,10 +114,10 @@ directions on getting read write access which allows pushing to the repository. -Once you have your clone, you can run ``python3 ./demo -b -sqlite`` and get a working Roundup server. This will start -the server using the ``sqlite`` backend. The code is in the -``roundup`` subdirectory. +Once you have your clone, change to the root of the tree and +run ``python3 ./demo.py -b sqlite`` to get a working Roundup +server. This will start the server using the ``sqlite`` backend. The +code is in the ``roundup`` subdirectory. To test internationalization in your environment, you have to process the locale sub-directory into a form that roundup's |
|
From: Mercurial C. <th...@in...> - 2026-05-18 19:45:53
|
# HG changeset patch
# User John Rouillard <ro...@ie...>
# Date 1779081442 14400
# Mon May 18 01:17:22 2026 -0400
# Node ID c51756a8a3685210fc0392d63d8a028ec42d7428
# Parent 4a3beb75c358c6df333d7bd9206a0b49cd1bf797
doc: typo fix.
diff -r 4a3beb75c358 -r c51756a8a368 doc/admin_guide.txt
--- a/doc/admin_guide.txt Mon May 18 00:50:54 2026 -0400
+++ b/doc/admin_guide.txt Mon May 18 01:17:22 2026 -0400
@@ -321,7 +321,7 @@
comments:
1. A ``//`` possibly indented with whitespace on a line is
- considereda a comment and is stripped from the file before
+ considered a comment and is stripped from the file before
being passed to the json parser. This is a "line comment".
2. A ``//`` with at least three white space characters before it
|
|
From: Mercurial C. <th...@in...> - 2026-05-18 19:45:52
|
# HG changeset patch
# User John Rouillard <ro...@ie...>
# Date 1779079854 14400
# Mon May 18 00:50:54 2026 -0400
# Node ID 4a3beb75c358c6df333d7bd9206a0b49cd1bf797
# Parent 9829f2e8ecbf97548537786a8220fc194d2c938d
remove unused import; have logger.error do formatting
diff -r 9829f2e8ecbf -r 4a3beb75c358 roundup/logcontext.py
--- a/roundup/logcontext.py Sun May 17 23:47:46 2026 -0400
+++ b/roundup/logcontext.py Mon May 18 00:50:54 2026 -0400
@@ -36,7 +36,6 @@
import contextvars
import functools
import logging
-import os
import uuid
from typing import Callable, Dict
@@ -249,7 +248,7 @@
# "args[1]['PATH_INFO']"
# report bad index/key/type
logger.error(
- "Eval of extract('%(extract)s')@%(loc)s caused %(e)s" %
+ "Eval of extract('%(extract)s')@%(loc)s caused %(e)s",
{"e": repr(e), "extract": extract, "loc": location}
)
|
|
From: Mercurial C. <th...@in...> - 2026-05-18 19:45:50
|
4 new changesets in roundup: pushed by: rouilj https://sourceforge.net/p/roundup/code/ci/4a3beb75c358 changeset: 8646:4a3beb75c358 user: John Rouillard <ro...@ie...> date: Mon May 18 00:50:54 2026 -0400 summary: remove unused import; have logger.error do formatting https://sourceforge.net/p/roundup/code/ci/c51756a8a368 changeset: 8647:c51756a8a368 user: John Rouillard <ro...@ie...> date: Mon May 18 01:17:22 2026 -0400 summary: doc: typo fix. https://sourceforge.net/p/roundup/code/ci/d2da5f9c2ea8 changeset: 8648:d2da5f9c2ea8 user: John Rouillard <ro...@ie...> date: Mon May 18 11:25:36 2026 -0400 summary: doc: fix bad directions for starting demo server from hg clone https://sourceforge.net/p/roundup/code/ci/cafa177776bb changeset: 8649:cafa177776bb tag: tip user: John Rouillard <ro...@ie...> date: Mon May 18 15:25:49 2026 -0400 summary: bug: handle ConfigurationError when running demo. -- Repository URL: https://sourceforge.net/p/roundup/code |
|
From: Mercurial C. <th...@in...> - 2026-05-18 03:50:34
|
# HG changeset patch
# User John Rouillard <ro...@ie...>
# Date 1779076066 14400
# Sun May 17 23:47:46 2026 -0400
# Node ID 9829f2e8ecbf97548537786a8220fc194d2c938d
# Parent 3913906eac2ce6b8142d83ba49cdd5a3052c7668
refactor: change method used to report statistics.
The old way used a hard coded list of timing statistics. It could be
translated, but required revising every time a new stat was added.
The elapsed time is always calculated, so keep that as a translatable
item.
Now dump all other stats in the timing dict. I can add new timing
values and get them dumped without addition changes.
Also check the type of the result. Only process timing if it's
text/html. I can't report it otherwise. Also templates like
_generic.translation can override the result type. Before it was
generating stats for every request even if it couldn't display
it. Also if '</body>' showed up in a js file or a comment in an image
file doing the substitution would break the data.
diff -r 3913906eac2c -r 9829f2e8ecbf roundup/cgi/client.py
--- a/roundup/cgi/client.py Sun May 17 22:23:57 2026 -0400
+++ b/roundup/cgi/client.py Sun May 17 23:47:46 2026 -0400
@@ -2510,22 +2510,33 @@
if 'Content-Type' not in self.additional_headers:
self.additional_headers['Content-Type'] = pt.content_type
- if self.env.get('CGI_SHOW_TIMING', ''):
+
+ show_timing = self.env.get('CGI_SHOW_TIMING', '').upper()
+
+ # check content_type so we don't calculate timing if
+ # we can't display it. This also prevents matching
+ # '</body>' in js, css, svg or comment strings in
+ # images that will mangle result.
+ content_type = (self.additional_headers.get('Content-Type', "")
+ or pt.content_type)
+ if (show_timing in ('COMMENT', 'INLINE') and
+ content_type == "text/html"):
+ timings = {}
if self.env['CGI_SHOW_TIMING'].upper() == 'COMMENT':
- timings = {'starttag': '<!-- ', 'endtag': ' -->'}
+ delims = {'starttag': '<!-- ', 'endtag': ' -->'}
else:
- timings = {'starttag': '<p>', 'endtag': '</p>'}
+ delims = {'starttag': '<p>', 'endtag': '</p>'}
timings['seconds'] = time.time() - self.start
s = self._(
'%(starttag)sTime elapsed: %(seconds)fs%(endtag)s\n'
- ) % timings
+ ) % {**timings, **delims}
if hasattr(self.db, 'stats'):
timings.update(self.db.stats)
- s += self._("%(starttag)sCache hits: %(cache_hits)d,"
- " misses %(cache_misses)d."
- " Loading items: %(get_items)f secs."
- " Filtering: %(filtering)f secs."
- "%(endtag)s\n") % timings
+ allstats = ", ".join(["{}: {:.6g}".format(key, value)
+ for key, value in timings.items()
+ if key != 'seconds'])
+ s += '%(starttag)s %(allstats)s %(endtag)s\n' % {
+ 'allstats': allstats, **delims}
s += '</body>'
result = result.replace('</body>', s)
return result
|
|
From: Mercurial C. <th...@in...> - 2026-05-18 03:50:33
|
# HG changeset patch
# User John Rouillard <ro...@ie...>
# Date 1779071037 14400
# Sun May 17 22:23:57 2026 -0400
# Node ID 3913906eac2ce6b8142d83ba49cdd5a3052c7668
# Parent 55937ea8655eac3f6ad002873019725cc2d29e79
bug: issue2551377 - Disabled submit button UI issues. Fixed
Add code to allow submit button to complete submission after 5 second
delay. This should be more than enough time for the page to refresh
under normal circumstances.
diff -r 55937ea8655e -r 3913906eac2c CHANGES.txt
--- a/CHANGES.txt Sun May 17 20:30:54 2026 -0400
+++ b/CHANGES.txt Sun May 17 22:23:57 2026 -0400
@@ -117,6 +117,12 @@
classic template more responsive. (John Rouillard)
- jinja2 template dependencies updated: bootstrap 4.4.1 -> 4.6.2.
(John Rouillard)
+- issue2551377 - Disabled submit button UI issues. When the submit
+ button is clicked (triggering submit_once) but client side
+ validation blocks the submission, the submit button no longer
+ submits the form. Clicking it says the form has been submitted
+ please wait. This change allows the submit button to submit the form
+ again after 5 seconds. (John Rouillard)
Features:
diff -r 55937ea8655e -r 3913906eac2c roundup/cgi/templating.py
--- a/roundup/cgi/templating.py Sun May 17 20:30:54 2026 -0400
+++ b/roundup/cgi/templating.py Sun May 17 22:23:57 2026 -0400
@@ -3447,6 +3447,9 @@
alert("Your request is being processed.\\nPlease be patient.");
return false;
}
+
+ window.setTimeout("submitted = false;", 5000)
+
submitted = true;
return true;
}
|
|
From: Mercurial C. <th...@in...> - 2026-05-18 03:50:31
|
# HG changeset patch # User John Rouillard <ro...@ie...> # Date 1779064254 14400 # Sun May 17 20:30:54 2026 -0400 # Node ID 55937ea8655eac3f6ad002873019725cc2d29e79 # Parent c355667a16bdd8ff62a34c90fadf02832180608e chore: jinja2 template dependencies updated: bootstrap 4.4.1 -> 4.6.2 diff -r c355667a16bd -r 55937ea8655e CHANGES.txt --- a/CHANGES.txt Sun May 17 02:09:22 2026 -0400 +++ b/CHANGES.txt Sun May 17 20:30:54 2026 -0400 @@ -115,6 +115,8 @@ be scrolled horizontally with the keyboard or touch. Some other CSS changes make it easier to use. This is a first step to making the classic template more responsive. (John Rouillard) +- jinja2 template dependencies updated: bootstrap 4.4.1 -> 4.6.2. + (John Rouillard) Features: diff -r c355667a16bd -r 55937ea8655e doc/upgrading.txt --- a/doc/upgrading.txt Sun May 17 02:09:22 2026 -0400 +++ b/doc/upgrading.txt Sun May 17 20:30:54 2026 -0400 @@ -469,6 +469,24 @@ change the 600px viewport breakpoint by following the directions at the top of ``style.css``. +Jinja Template Bootstrap Dependency Updated (optional) +------------------------------------------------------ + +A dependency for the jinja template has been updated. This is an +upgrade to the latest 4.x stable release. + +bootstrap: + Upgraded from 4.4.1 to 4.6.2 + +Copy the files: ``bootstrap.min.css`` and ``bootstrap.min.js`` from +the jinja2 ``static`` :term:`template` directory (meaning 3) to your +tracker's ``static`` directory. + +This change did not show any issues in testing, but you might want to +hold onto your original files in case of an issue. If you experience +and issue you should report it on the `roundup-users mailing list +<https://roundup-tracker.org/contact.html>`_. + .. index:: Upgrading; 2.4.0 to 2.5.0 Migrating from 2.4.0 to 2.5.0 diff -r c355667a16bd -r 55937ea8655e share/roundup/templates/jinja2/static/bootstrap.min.css --- a/share/roundup/templates/jinja2/static/bootstrap.min.css Sun May 17 02:09:22 2026 -0400 +++ b/share/roundup/templates/jinja2/static/bootstrap.min.css Sun May 17 20:30:54 2026 -0400 @@ -1,7 +1,7 @@ /*! - * Bootstrap v4.4.1 (https://getbootstrap.com/) - * Copyright 2011-2019 The Bootstrap Authors - * Copyright 2011-2019 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - */:root{--blue:#007bff;--indigo:#6610f2;--purple:#6f42c1;--pink:#e83e8c;--red:#dc3545;--orange:#fd7e14;--yellow:#ffc107;--green:#28a745;--teal:#20c997;--cyan:#17a2b8;--white:#fff;--gray:#6c757d;--gray-dark:#343a40;--primary:#007bff;--secondary:#6c757d;--success:#28a745;--info:#17a2b8;--warning:#ffc107;--danger:#dc3545;--light:#f8f9fa;--dark:#343a40;--breakpoint-xs:0;--breakpoint-sm:576px;--breakpoint-md:768px;--breakpoint-lg:992px;--breakpoint-xl:1200px;--font-family-sans-serif:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-family-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus:not(:focus-visible){outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent}a:hover{color:#0056b3;text-decoration:underline}a:not([href]){color:inherit;text-decoration:none}a:not([href]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}select{word-wrap:normal}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=date],input[type=datetime-local],input[type=month],input[type=time]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-bottom:.5rem;font-weight:500;line-height:1.2}.h1,h1{font-size:2.5rem}.h2,h2{font-size:2rem}.h3,h3{font-size:1.75rem}.h4,h4{font-size:1.5rem}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:6rem;font-weight:300;line-height:1.2}.display-2{font-size:5.5rem;font-weight:300;line-height:1.2}.display-3{font-size:4.5rem;font-weight:300;line-height:1.2}.display-4{font-size:3.5rem;font-weight:300;line-height:1.2}hr{margin-top:1rem;margin-bottom:1rem;border:0;border-top:1px solid rgba(0,0,0,.1)}.small,small{font-size:80%;font-weight:400}.mark,mark{padding:.2em;background-color:#fcf8e3}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:90%;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote-footer{display:block;font-size:80%;color:#6c757d}.blockquote-footer::before{content:"\2014\00A0"}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#fff;border:1px solid #dee2e6;border-radius:.25rem;max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:90%;color:#6c757d}code{font-size:87.5%;color:#e83e8c;word-wrap:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:87.5%;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:100%;font-weight:700}pre{display:block;font-size:87.5%;color:#212529}pre code{font-size:inherit;color:inherit;word-break:normal}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:576px){.container{max-width:540px}}@media (min-width:768px){.container{max-width:720px}}@media (min-width:992px){.container{max-width:960px}}@media (min-width:1200px){.container{max-width:1140px}}.container-fluid,.container-lg,.container-md,.container-sm,.container-xl{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:576px){.container,.container-sm{max-width:540px}}@media (min-width:768px){.container,.container-md,.container-sm{max-width:720px}}@media (min-width:992px){.container,.container-lg,.container-md,.container-sm{max-width:960px}}@media (min-width:1200px){.container,.container-lg,.container-md,.container-sm,.container-xl{max-width:1140px}}.row{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-15px;margin-left:-15px}.no-gutters{margin-right:0;margin-left:0}.no-gutters>.col,.no-gutters>[class*=col-]{padding-right:0;padding-left:0}.col,.col-1,.col-10,.col-11,.col-12,.col-2,.col-3,.col-4,.col-5,.col-6,.col-7,.col-8,.col-9,.col-auto,.col-lg,.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-auto,.col-md,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-auto,.col-sm,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-auto,.col-xl,.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-auto{position:relative;width:100%;padding-right:15px;padding-left:15px}.col{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.row-cols-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-first{-ms-flex-order:-1;order:-1}.order-last{-ms-flex-order:13;order:13}.order-0{-ms-flex-order:0;order:0}.order-1{-ms-flex-order:1;order:1}.order-2{-ms-flex-order:2;order:2}.order-3{-ms-flex-order:3;order:3}.order-4{-ms-flex-order:4;order:4}.order-5{-ms-flex-order:5;order:5}.order-6{-ms-flex-order:6;order:6}.order-7{-ms-flex-order:7;order:7}.order-8{-ms-flex-order:8;order:8}.order-9{-ms-flex-order:9;order:9}.order-10{-ms-flex-order:10;order:10}.order-11{-ms-flex-order:11;order:11}.order-12{-ms-flex-order:12;order:12}.offset-1{margin-left:8.333333%}.offset-2{margin-left:16.666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.333333%}.offset-5{margin-left:41.666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.333333%}.offset-8{margin-left:66.666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.333333%}.offset-11{margin-left:91.666667%}@media (min-width:576px){.col-sm{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.row-cols-sm-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-sm-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-sm-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-sm-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-sm-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-sm-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-sm-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-sm-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-sm-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-sm-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-sm-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-sm-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-sm-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-sm-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-sm-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-sm-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-sm-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-sm-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-sm-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-sm-first{-ms-flex-order:-1;order:-1}.order-sm-last{-ms-flex-order:13;order:13}.order-sm-0{-ms-flex-order:0;order:0}.order-sm-1{-ms-flex-order:1;order:1}.order-sm-2{-ms-flex-order:2;order:2}.order-sm-3{-ms-flex-order:3;order:3}.order-sm-4{-ms-flex-order:4;order:4}.order-sm-5{-ms-flex-order:5;order:5}.order-sm-6{-ms-flex-order:6;order:6}.order-sm-7{-ms-flex-order:7;order:7}.order-sm-8{-ms-flex-order:8;order:8}.order-sm-9{-ms-flex-order:9;order:9}.order-sm-10{-ms-flex-order:10;order:10}.order-sm-11{-ms-flex-order:11;order:11}.order-sm-12{-ms-flex-order:12;order:12}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.333333%}.offset-sm-2{margin-left:16.666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.333333%}.offset-sm-5{margin-left:41.666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.333333%}.offset-sm-8{margin-left:66.666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.333333%}.offset-sm-11{margin-left:91.666667%}}@media (min-width:768px){.col-md{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.row-cols-md-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-md-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-md-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-md-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-md-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-md-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-md-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-md-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-md-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-md-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-md-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-md-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-md-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-md-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-md-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-md-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-md-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-md-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-md-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-md-first{-ms-flex-order:-1;order:-1}.order-md-last{-ms-flex-order:13;order:13}.order-md-0{-ms-flex-order:0;order:0}.order-md-1{-ms-flex-order:1;order:1}.order-md-2{-ms-flex-order:2;order:2}.order-md-3{-ms-flex-order:3;order:3}.order-md-4{-ms-flex-order:4;order:4}.order-md-5{-ms-flex-order:5;order:5}.order-md-6{-ms-flex-order:6;order:6}.order-md-7{-ms-flex-order:7;order:7}.order-md-8{-ms-flex-order:8;order:8}.order-md-9{-ms-flex-order:9;order:9}.order-md-10{-ms-flex-order:10;order:10}.order-md-11{-ms-flex-order:11;order:11}.order-md-12{-ms-flex-order:12;order:12}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.333333%}.offset-md-2{margin-left:16.666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.333333%}.offset-md-5{margin-left:41.666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.333333%}.offset-md-8{margin-left:66.666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.333333%}.offset-md-11{margin-left:91.666667%}}@media (min-width:992px){.col-lg{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.row-cols-lg-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-lg-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-lg-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-lg-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-lg-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-lg-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-lg-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-lg-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-lg-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-lg-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-lg-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-lg-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-lg-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-lg-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-lg-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-lg-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-lg-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-lg-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-lg-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-lg-first{-ms-flex-order:-1;order:-1}.order-lg-last{-ms-flex-order:13;order:13}.order-lg-0{-ms-flex-order:0;order:0}.order-lg-1{-ms-flex-order:1;order:1}.order-lg-2{-ms-flex-order:2;order:2}.order-lg-3{-ms-flex-order:3;order:3}.order-lg-4{-ms-flex-order:4;order:4}.order-lg-5{-ms-flex-order:5;order:5}.order-lg-6{-ms-flex-order:6;order:6}.order-lg-7{-ms-flex-order:7;order:7}.order-lg-8{-ms-flex-order:8;order:8}.order-lg-9{-ms-flex-order:9;order:9}.order-lg-10{-ms-flex-order:10;order:10}.order-lg-11{-ms-flex-order:11;order:11}.order-lg-12{-ms-flex-order:12;order:12}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.333333%}.offset-lg-2{margin-left:16.666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.333333%}.offset-lg-5{margin-left:41.666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.333333%}.offset-lg-8{margin-left:66.666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.333333%}.offset-lg-11{margin-left:91.666667%}}@media (min-width:1200px){.col-xl{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.row-cols-xl-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-xl-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-xl-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-xl-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-xl-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-xl-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-xl-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-xl-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-xl-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-xl-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-xl-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-xl-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-xl-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-xl-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-xl-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-xl-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-xl-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-xl-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-xl-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-xl-first{-ms-flex-order:-1;order:-1}.order-xl-last{-ms-flex-order:13;order:13}.order-xl-0{-ms-flex-order:0;order:0}.order-xl-1{-ms-flex-order:1;order:1}.order-xl-2{-ms-flex-order:2;order:2}.order-xl-3{-ms-flex-order:3;order:3}.order-xl-4{-ms-flex-order:4;order:4}.order-xl-5{-ms-flex-order:5;order:5}.order-xl-6{-ms-flex-order:6;order:6}.order-xl-7{-ms-flex-order:7;order:7}.order-xl-8{-ms-flex-order:8;order:8}.order-xl-9{-ms-flex-order:9;order:9}.order-xl-10{-ms-flex-order:10;order:10}.order-xl-11{-ms-flex-order:11;order:11}.order-xl-12{-ms-flex-order:12;order:12}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.333333%}.offset-xl-2{margin-left:16.666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.333333%}.offset-xl-5{margin-left:41.666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.333333%}.offset-xl-8{margin-left:66.666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.333333%}.offset-xl-11{margin-left:91.666667%}}.table{width:100%;margin-bottom:1rem;color:#212529}.table td,.table th{padding:.75rem;vertical-align:top;border-top:1px solid #dee2e6}.table thead th{vertical-align:bottom;border-bottom:2px solid #dee2e6}.table tbody+tbody{border-top:2px solid #dee2e6}.table-sm td,.table-sm th{padding:.3rem}.table-bordered{border:1px solid #dee2e6}.table-bordered td,.table-bordered th{border:1px solid #dee2e6}.table-bordered thead td,.table-bordered thead th{border-bottom-width:2px}.table-borderless tbody+tbody,.table-borderless td,.table-borderless th,.table-borderless thead th{border:0}.table-striped tbody tr:nth-of-type(odd){background-color:rgba(0,0,0,.05)}.table-hover tbody tr:hover{color:#212529;background-color:rgba(0,0,0,.075)}.table-primary,.table-primary>td,.table-primary>th{background-color:#b8daff}.table-primary tbody+tbody,.table-primary td,.table-primary th,.table-primary thead th{border-color:#7abaff}.table-hover .table-primary:hover{background-color:#9fcdff}.table-hover .table-primary:hover>td,.table-hover .table-primary:hover>th{background-color:#9fcdff}.table-secondary,.table-secondary>td,.table-secondary>th{background-color:#d6d8db}.table-secondary tbody+tbody,.table-secondary td,.table-secondary th,.table-secondary thead th{border-color:#b3b7bb}.table-hover .table-secondary:hover{background-color:#c8cbcf}.table-hover .table-secondary:hover>td,.table-hover .table-secondary:hover>th{background-color:#c8cbcf}.table-success,.table-success>td,.table-success>th{background-color:#c3e6cb}.table-success tbody+tbody,.table-success td,.table-success th,.table-success thead th{border-color:#8fd19e}.table-hover .table-success:hover{background-color:#b1dfbb}.table-hover .table-success:hover>td,.table-hover .table-success:hover>th{background-color:#b1dfbb}.table-info,.table-info>td,.table-info>th{background-color:#bee5eb}.table-info tbody+tbody,.table-info td,.table-info th,.table-info thead th{border-color:#86cfda}.table-hover .table-info:hover{background-color:#abdde5}.table-hover .table-info:hover>td,.table-hover .table-info:hover>th{background-color:#abdde5}.table-warning,.table-warning>td,.table-warning>th{background-color:#ffeeba}.table-warning tbody+tbody,.table-warning td,.table-warning th,.table-warning thead th{border-color:#ffdf7e}.table-hover .table-warning:hover{background-color:#ffe8a1}.table-hover .table-warning:hover>td,.table-hover .table-warning:hover>th{background-color:#ffe8a1}.table-danger,.table-danger>td,.table-danger>th{background-color:#f5c6cb}.table-danger tbody+tbody,.table-danger td,.table-danger th,.table-danger thead th{border-color:#ed969e}.table-hover .table-danger:hover{background-color:#f1b0b7}.table-hover .table-danger:hover>td,.table-hover .table-danger:hover>th{background-color:#f1b0b7}.table-light,.table-light>td,.table-light>th{background-color:#fdfdfe}.table-light tbody+tbody,.table-light td,.table-light th,.table-light thead th{border-color:#fbfcfc}.table-hover .table-light:hover{background-color:#ececf6}.table-hover .table-light:hover>td,.table-hover .table-light:hover>th{background-color:#ececf6}.table-dark,.table-dark>td,.table-dark>th{background-color:#c6c8ca}.table-dark tbody+tbody,.table-dark td,.table-dark th,.table-dark thead th{border-color:#95999c}.table-hover .table-dark:hover{background-color:#b9bbbe}.table-hover .table-dark:hover>td,.table-hover .table-dark:hover>th{background-color:#b9bbbe}.table-active,.table-active>td,.table-active>th{background-color:rgba(0,0,0,.075)}.table-hover .table-active:hover{background-color:rgba(0,0,0,.075)}.table-hover .table-active:hover>td,.table-hover .table-active:hover>th{background-color:rgba(0,0,0,.075)}.table .thead-dark th{color:#fff;background-color:#343a40;border-color:#454d55}.table .thead-light th{color:#495057;background-color:#e9ecef;border-color:#dee2e6}.table-dark{color:#fff;background-color:#343a40}.table-dark td,.table-dark th,.table-dark thead th{border-color:#454d55}.table-dark.table-bordered{border:0}.table-dark.table-striped tbody tr:nth-of-type(odd){background-color:rgba(255,255,255,.05)}.table-dark.table-hover tbody tr:hover{color:#fff;background-color:rgba(255,255,255,.075)}@media (max-width:575.98px){.table-responsive-sm{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-sm>.table-bordered{border:0}}@media (max-width:767.98px){.table-responsive-md{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-md>.table-bordered{border:0}}@media (max-width:991.98px){.table-responsive-lg{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-lg>.table-bordered{border:0}}@media (max-width:1199.98px){.table-responsive-xl{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-xl>.table-bordered{border:0}}.table-responsive{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive>.table-bordered{border:0}.form-control{display:block;width:100%;height:calc(1.5em + .75rem + 2px);padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control::-ms-expand{background-color:transparent;border:0}.form-control:-moz-focusring{color:transparent;text-shadow:0 0 0 #495057}.form-control:focus{color:#495057;background-color:#fff;border-color:#80bdff;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.form-control::-webkit-input-placeholder{color:#6c757d;opacity:1}.form-control::-moz-placeholder{color:#6c757d;opacity:1}.form-control:-ms-input-placeholder{color:#6c757d;opacity:1}.form-control::-ms-input-placeholder{color:#6c757d;opacity:1}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#e9ecef;opacity:1}select.form-control:focus::-ms-value{color:#495057;background-color:#fff}.form-control-file,.form-control-range{display:block;width:100%}.col-form-label{padding-top:calc(.375rem + 1px);padding-bottom:calc(.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + 1px);padding-bottom:calc(.5rem + 1px);font-size:1.25rem;line-height:1.5}.col-form-label-sm{padding-top:calc(.25rem + 1px);padding-bottom:calc(.25rem + 1px);font-size:.875rem;line-height:1.5}.form-control-plaintext{display:block;width:100%;padding:.375rem 0;margin-bottom:0;font-size:1rem;line-height:1.5;color:#212529;background-color:transparent;border:solid transparent;border-width:1px 0}.form-control-plaintext.form-control-lg,.form-control-plaintext.form-control-sm{padding-right:0;padding-left:0}.form-control-sm{height:calc(1.5em + .5rem + 2px);padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.form-control-lg{height:calc(1.5em + 1rem + 2px);padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}select.form-control[multiple],select.form-control[size]{height:auto}textarea.form-control{height:auto}.form-group{margin-bottom:1rem}.form-text{display:block;margin-top:.25rem}.form-row{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-5px;margin-left:-5px}.form-row>.col,.form-row>[class*=col-]{padding-right:5px;padding-left:5px}.form-check{position:relative;display:block;padding-left:1.25rem}.form-check-input{position:absolute;margin-top:.3rem;margin-left:-1.25rem}.form-check-input:disabled~.form-check-label,.form-check-input[disabled]~.form-check-label{color:#6c757d}.form-check-label{margin-bottom:0}.form-check-inline{display:-ms-inline-flexbox;display:inline-flex;-ms-flex-align:center;align-items:center;padding-left:0;margin-right:.75rem}.form-check-inline .form-check-input{position:static;margin-top:0;margin-right:.3125rem;margin-left:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#28a745}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;line-height:1.5;color:#fff;background-color:rgba(40,167,69,.9);border-radius:.25rem}.is-valid~.valid-feedback,.is-valid~.valid-tooltip,.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip{display:block}.form-control.is-valid,.was-validated .form-control:valid{border-color:#28a745;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-valid:focus,.was-validated .form-control:valid:focus{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.custom-select.is-valid,.was-validated .custom-select:valid{border-color:#28a745;padding-right:calc(.75em + 2.3125rem);background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right .75rem center/8px 10px,url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e") #fff no-repeat center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem)}.custom-select.is-valid:focus,.was-validated .custom-select:valid:focus{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:#28a745}.form-check-input.is-valid~.valid-feedback,.form-check-input.is-valid~.valid-tooltip,.was-validated .form-check-input:valid~.valid-feedback,.was-validated .form-check-input:valid~.valid-tooltip{display:block}.custom-control-input.is-valid~.custom-control-label,.was-validated .custom-control-input:valid~.custom-control-label{color:#28a745}.custom-control-input.is-valid~.custom-control-label::before,.was-validated .custom-control-input:valid~.custom-control-label::before{border-color:#28a745}.custom-control-input.is-valid:checked~.custom-control-label::before,.was-validated .custom-control-input:valid:checked~.custom-control-label::before{border-color:#34ce57;background-color:#34ce57}.custom-control-input.is-valid:focus~.custom-control-label::before,.was-validated .custom-control-input:valid:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.custom-control-input.is-valid:focus:not(:checked)~.custom-control-label::before,.was-validated .custom-control-input:valid:focus:not(:checked)~.custom-control-label::before{border-color:#28a745}.custom-file-input.is-valid~.custom-file-label,.was-validated .custom-file-input:valid~.custom-file-label{border-color:#28a745}.custom-file-input.is-valid:focus~.custom-file-label,.was-validated .custom-file-input:valid:focus~.custom-file-label{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#dc3545}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;line-height:1.5;color:#fff;background-color:rgba(220,53,69,.9);border-radius:.25rem}.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip,.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip{display:block}.form-control.is-invalid,.was-validated .form-control:invalid{border-color:#dc3545;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-invalid:focus,.was-validated .form-control:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.custom-select.is-invalid,.was-validated .custom-select:invalid{border-color:#dc3545;padding-right:calc(.75em + 2.3125rem);background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right .75rem center/8px 10px,url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e") #fff no-repeat center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem)}.custom-select.is-invalid:focus,.was-validated .custom-select:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:#dc3545}.form-check-input.is-invalid~.invalid-feedback,.form-check-input.is-invalid~.invalid-tooltip,.was-validated .form-check-input:invalid~.invalid-feedback,.was-validated .form-check-input:invalid~.invalid-tooltip{display:block}.custom-control-input.is-invalid~.custom-control-label,.was-validated .custom-control-input:invalid~.custom-control-label{color:#dc3545}.custom-control-input.is-invalid~.custom-control-label::before,.was-validated .custom-control-input:invalid~.custom-control-label::before{border-color:#dc3545}.custom-control-input.is-invalid:checked~.custom-control-label::before,.was-validated .custom-control-input:invalid:checked~.custom-control-label::before{border-color:#e4606d;background-color:#e4606d}.custom-control-input.is-invalid:focus~.custom-control-label::before,.was-validated .custom-control-input:invalid:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.custom-control-input.is-invalid:focus:not(:checked)~.custom-control-label::before,.was-validated .custom-control-input:invalid:focus:not(:checked)~.custom-control-label::before{border-color:#dc3545}.custom-file-input.is-invalid~.custom-file-label,.was-validated .custom-file-input:invalid~.custom-file-label{border-color:#dc3545}.custom-file-input.is-invalid:focus~.custom-file-label,.was-validated .custom-file-input:invalid:focus~.custom-file-label{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.form-inline{display:-ms-flexbox;display:flex;-ms-flex-flow:row wrap;flex-flow:row wrap;-ms-flex-align:center;align-items:center}.form-inline .form-check{width:100%}@media (min-width:576px){.form-inline label{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;margin-bottom:0}.form-inline .form-group{display:-ms-flexbox;display:flex;-ms-flex:0 0 auto;flex:0 0 auto;-ms-flex-flow:row wrap;flex-flow:row wrap;-ms-flex-align:center;align-items:center;margin-bottom:0}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-plaintext{display:inline-block}.form-inline .custom-select,.form-inline .input-group{width:auto}.form-inline .form-check{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;width:auto;padding-left:0}.form-inline .form-check-input{position:relative;-ms-flex-negative:0;flex-shrink:0;margin-top:0;margin-right:.25rem;margin-left:0}.form-inline .custom-control{-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center}.form-inline .custom-control-label{margin-bottom:0}}.btn{display:inline-block;font-weight:400;color:#212529;text-align:center;vertical-align:middle;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-color:transparent;border:1px solid transparent;padding:.375rem .75rem;font-size:1rem;line-height:1.5;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.btn{transition:none}}.btn:hover{color:#212529;text-decoration:none}.btn.focus,.btn:focus{outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.btn.disabled,.btn:disabled{opacity:.65}a.btn.disabled,fieldset:disabled a.btn{pointer-events:none}.btn-primary{color:#fff;background-color:#007bff;border-color:#007bff}.btn-primary:hover{color:#fff;background-color:#0069d9;border-color:#0062cc}.btn-primary.focus,.btn-primary:focus{color:#fff;background-color:#0069d9;border-color:#0062cc;box-shadow:0 0 0 .2rem rgba(38,143,255,.5)}.btn-primary.disabled,.btn-primary:disabled{color:#fff;background-color:#007bff;border-color:#007bff}.btn-primary:not(:disabled):not(.disabled).active,.btn-primary:not(:disabled):not(.disabled):active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#0062cc;border-color:#005cbf}.btn-primary:not(:disabled):not(.disabled).active:focus,.btn-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(38,143,255,.5)}.btn-secondary{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:hover{color:#fff;background-color:#5a6268;border-color:#545b62}.btn-secondary.focus,.btn-secondary:focus{color:#fff;background-color:#5a6268;border-color:#545b62;box-shadow:0 0 0 .2rem rgba(130,138,145,.5)}.btn-secondary.disabled,.btn-secondary:disabled{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:not(:disabled):not(.disabled).active,.btn-secondary:not(:disabled):not(.disabled):active,.show>.btn-secondary.dropdown-toggle{color:#fff;background-color:#545b62;border-color:#4e555b}.btn-secondary:not(:disabled):not(.disabled).active:focus,.btn-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(130,138,145,.5)}.btn-success{color:#fff;background-color:#28a745;border-color:#28a745}.btn-success:hover{color:#fff;background-color:#218838;border-color:#1e7e34}.btn-success.focus,.btn-success:focus{color:#fff;background-color:#218838;border-color:#1e7e34;box-shadow:0 0 0 .2rem rgba(72,180,97,.5)}.btn-success.disabled,.btn-success:disabled{color:#fff;background-color:#28a745;border-color:#28a745}.btn-success:not(:disabled):not(.disabled).active,.btn-success:not(:disabled):not(.disabled):active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#1e7e34;border-color:#1c7430}.btn-success:not(:disabled):not(.disabled).active:focus,.btn-success:not(:disabled):not(.disabled):active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(72,180,97,.5)}.btn-info{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-info:hover{color:#fff;background-color:#138496;border-color:#117a8b}.btn-info.focus,.btn-info:focus{color:#fff;background-color:#138496;border-color:#117a8b;box-shadow:0 0 0 .2rem rgba(58,176,195,.5)}.btn-info.disabled,.btn-info:disabled{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-info:not(:disabled):not(.disabled).active,.btn-info:not(:disabled):not(.disabled):active,.show>.btn-info.dropdown-toggle{color:#fff;background-color:#117a8b;border-color:#10707f}.btn-info:not(:disabled):not(.disabled).active:focus,.btn-info:not(:disabled):not(.disabled):active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(58,176,195,.5)}.btn-warning{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning:hover{color:#212529;background-color:#e0a800;border-color:#d39e00}.btn-warning.focus,.btn-warning:focus{color:#212529;background-color:#e0a800;border-color:#d39e00;box-shadow:0 0 0 .2rem rgba(222,170,12,.5)}.btn-warning.disabled,.btn-warning:disabled{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning:not(:disabled):not(.disabled).active,.btn-warning:not(:disabled):not(.disabled):active,.show>.btn-warning.dropdown-toggle{color:#212529;background-color:#d39e00;border-color:#c69500}.btn-warning:not(:disabled):not(.disabled).active:focus,.btn-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(222,170,12,.5)}.btn-danger{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:hover{color:#fff;background-color:#c82333;border-color:#bd2130}.btn-danger.focus,.btn-danger:focus{color:#fff;background-color:#c82333;border-color:#bd2130;box-shadow:0 0 0 .2rem rgba(225,83,97,.5)}.btn-danger.disabled,.btn-danger:disabled{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:not(:disabled):not(.disabled).active,.btn-danger:not(:disabled):not(.disabled):active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#bd2130;border-color:#b21f2d}.btn-danger:not(:disabled):not(.disabled).active:focus,.btn-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(225,83,97,.5)}.btn-light{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:hover{color:#212529;background-color:#e2e6ea;border-color:#dae0e5}.btn-light.focus,.btn-light:focus{color:#212529;background-color:#e2e6ea;border-color:#dae0e5;box-shadow:0 0 0 .2rem rgba(216,217,219,.5)}.btn-light.disabled,.btn-light:disabled{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:not(:disabled):not(.disabled).active,.btn-light:not(:disabled):not(.disabled):active,.show>.btn-light.dropdown-toggle{color:#212529;background-color:#dae0e5;border-color:#d3d9df}.btn-light:not(:disabled):not(.disabled).active:focus,.btn-light:not(:disabled):not(.disabled):active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(216,217,219,.5)}.btn-dark{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark:hover{color:#fff;background-color:#23272b;border-color:#1d2124}.btn-dark.focus,.btn-dark:focus{color:#fff;background-color:#23272b;border-color:#1d2124;box-shadow:0 0 0 .2rem rgba(82,88,93,.5)}.btn-dark.disabled,.btn-dark:disabled{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark:not(:disabled):not(.disabled).active,.btn-dark:not(:disabled):not(.disabled):active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#1d2124;border-color:#171a1d}.btn-dark:not(:disabled):not(.disabled).active:focus,.btn-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(82,88,93,.5)}.btn-outline-primary{color:#007bff;border-color:#007bff}.btn-outline-primary:hover{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-primary.focus,.btn-outline-primary:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-outline-primary.disabled,.btn-outline-primary:disabled{color:#007bff;background-color:transparent}.btn-outline-primary:not(:disabled):not(.disabled).active,.btn-outline-primary:not(:disabled):not(.disabled):active,.show>.btn-outline-primary.dropdown-toggle{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-primary:not(:disabled):not(.disabled).active:focus,.btn-outline-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-outline-secondary{color:#6c757d;border-color:#6c757d}.btn-outline-secondary:hover{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-outline-secondary.focus,.btn-outline-secondary:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{color:#6c757d;background-color:transparent}.btn-outline-secondary:not(:disabled):not(.disabled).active,.btn-outline-secondary:not(:disabled):not(.disabled):active,.show>.btn-outline-secondary.dropdown-toggle{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-outline-secondary:not(:disabled):not(.disabled).active:focus,.btn-outline-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-outline-success{color:#28a745;border-color:#28a745}.btn-outline-success:hover{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-success.focus,.btn-outline-success:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-outline-success.disabled,.btn-outline-success:disabled{color:#28a745;background-color:transparent}.btn-outline-success:not(:disabled):not(.disabled).active,.btn-outline-success:not(:disabled):not(.disabled):active,.show>.btn-outline-success.dropdown-toggle{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-success:not(:disabled):not(.disabled).active:focus,.btn-outline-success:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-outline-info{color:#17a2b8;border-color:#17a2b8}.btn-outline-info:hover{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-info.focus,.btn-outline-info:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-outline-info.disabled,.btn-outline-info:disabled{color:#17a2b8;background-color:transparent}.btn-outline-info:not(:disabled):not(.disabled).active,.btn-outline-info:not(:disabled):not(.disabled):active,.show>.btn-outline-info.dropdown-toggle{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-info:not(:disabled):not(.disabled).active:focus,.btn-outline-info:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-outline-warning{color:#ffc107;border-color:#ffc107}.btn-outline-warning:hover{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning.focus,.btn-outline-warning:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-outline-warning.disabled,.btn-outline-warning:disabled{color:#ffc107;background-color:transparent}.btn-outline-warning:not(:disabled):not(.disabled).active,.btn-outline-warning:not(:disabled):not(.disabled):active,.show>.btn-outline-warning.dropdown-toggle{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning:not(:disabled):not(.disabled).active:focus,.btn-outline-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-outline-danger{color:#dc3545;border-color:#dc3545}.btn-outline-danger:hover{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-danger.focus,.btn-outline-danger:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-outline-danger.disabled,.btn-outline-danger:disabled{color:#dc3545;background-color:transparent}.btn-outline-danger:not(:disabled):not(.disabled).active,.btn-outline-danger:not(:disabled):not(.disabled):active,.show>.btn-outline-danger.dropdown-toggle{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-danger:not(:disabled):not(.disabled).active:focus,.btn-outline-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-outline-light{color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:hover{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light.focus,.btn-outline-light:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-light.disabled,.btn-outline-light:disabled{color:#f8f9fa;background-color:transparent}.btn-outline-light:not(:disabled):not(.disabled).active,.btn-outline-light:not(:disabled):not(.disabled):active,.show>.btn-outline-light.dropdown-toggle{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:not(:disabled):not(.disabled).active:focus,.btn-outline-light:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-dark{color:#343a40;border-color:#343a40}.btn-outline-dark:hover{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark.focus,.btn-outline-dark:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-outline-dark.disabled,.btn-outline-dark:disabled{color:#343a40;background-color:transparent}.btn-outline-dark:not(:disabled):not(.disabled).active,.btn-outline-dark:not(:disabled):not(.disabled):active,.show>.btn-outline-dark.dropdown-toggle{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark:not(:disabled):not(.disabled).active:focus,.btn-outline-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-link{font-weight:400;color:#007bff;text-decoration:none}.btn-link:hover{color:#0056b3;text-decoration:underline}.btn-link.focus,.btn-link:focus{text-decoration:underline;box-shadow:none}.btn-link.disabled,.btn-link:disabled{color:#6c757d;pointer-events:none}.btn-group-lg>.btn,.btn-lg{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}.btn-group-sm>.btn,.btn-sm{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:.5rem}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{transition:opacity .15s linear}@media (prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{position:relative;height:0;overflow:hidden;transition:height .35s ease}@media (prefers-reduced-motion:reduce){.collapsing{transition:none}}.dropdown,.dropleft,.dropright,.dropup{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:10rem;padding:.5rem 0;margin:.125rem 0 0;font-size:1rem;color:#212529;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.15);border-radius:.25rem}.dropdown-menu-left{right:auto;left:0}.dropdown-menu-right{right:0;left:auto}@media (min-width:576px){.dropdown-menu-sm-left{right:auto;left:0}.dropdown-menu-sm-right{right:0;left:auto}}@media (min-width:768px){.dropdown-menu-md-left{right:auto;left:0}.dropdown-menu-md-right{right:0;left:auto}}@media (min-width:992px){.dropdown-menu-lg-left{right:auto;left:0}.dropdown-menu-lg-right{right:0;left:auto}}@media (min-width:1200px){.dropdown-menu-xl-left{right:auto;left:0}.dropdown-menu-xl-right{right:0;left:auto}}.dropup .dropdown-menu{top:auto;bottom:100%;margin-top:0;margin-bottom:.125rem}.dropup .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-menu{top:0;right:auto;left:100%;margin-top:0;margin-left:.125rem}.dropright .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:0;border-bottom:.3em solid transparent;border-left:.3em solid}.dropright .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-toggle::after{vertical-align:0}.dropleft .dropdown-menu{top:0;right:100%;left:auto;margin-top:0;margin-right:.125rem}.dropleft .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropleft .dropdown-toggle::after{display:none}.dropleft .dropdown-toggle::before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropleft .dropdown-toggle:empty::after{margin-left:0}.dropleft .dropdown-toggle::before{vertical-align:0}.dropdown-menu[x-placement^=bottom],.dropdown-menu[x-placement^=left],.dropdown-menu[x-placement^=right],.dropdown-menu[x-placement^=top]{right:auto;bottom:auto}.dropdown-divider{height:0;margin:.5rem 0;overflow:hidden;border-top:1px solid #e9ecef}.dropdown-item{display:block;width:100%;padding:.25rem 1.5rem;clear:both;font-weight:400;color:#212529;text-align:inherit;white-space:nowrap;background-color:transparent;border:0}.dropdown-item:focus,.dropdown-item:hover{color:#16181b;text-decoration:none;background-color:#f8f9fa}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#007bff}.dropdown-item.disabled,.dropdown-item:disabled{color:#6c757d;pointer-events:none;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:.5rem 1.5rem;margin-bottom:0;font-size:.875rem;color:#6c757d;white-space:nowrap}.dropdown-item-text{display:block;padding:.25rem 1.5rem;color:#212529}.btn-group,.btn-group-vertical{position:relative;display:-ms-inline-flexbox;display:inline-flex;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;-ms-flex:1 1 auto;flex:1 1 auto}.btn-group-vertical>.btn:hover,.btn-group>.btn:hover{z-index:1}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus{z-index:1}.btn-toolbar{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-pack:start;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn-group:not(:first-child),.btn-group>.btn:not(:first-child){margin-left:-1px}.btn-group>.btn-group:not(:last-child)>.btn,.btn-group>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:not(:first-child)>.btn,.btn-group>.btn:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropright .dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after{margin-left:0}.dropleft .dropdown-toggle-split::before{margin-right:0}.btn-group-sm>.btn+.dropdown-toggle-split,.btn-sm+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-group-lg>.btn+.dropdown-toggle-split,.btn-lg+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{-ms-flex-direction:column;flex-direction:column;-ms-flex-align:start;align-items:flex-start;-ms-flex-pack:center;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn-group:not(:first-child),.btn-group-vertical>.btn:not(:first-child){margin-top:-1px}.btn-group-vertical>.btn-group:not(:last-child)>.btn,.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child)>.btn,.btn-group-vertical>.btn:not(:first-child){border-top-left-radius:0;border-top-right-radius:0}.btn-group-toggle>.btn,.btn-group-toggle>.btn-group>.btn{margin-bottom:0}.btn-group-toggle>.btn input[type=checkbox],.btn-group-toggle>.btn input[type=radio],.btn-group-toggle>.btn-group>.btn input[type=checkbox],.btn-group-toggle>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:stretch;align-items:stretch;width:100%}.input-group>.custom-file,.input-group>.custom-select,.input-group>.form-control,.input-group>.form-control-plaintext{position:relative;-ms-flex:1 1 0%;flex:1 1 0%;min-width:0;margin-bottom:0}.input-group>.custom-file+.custom-file,.input-group>.custom-file+.custom-select,.input-group>.custom-file+.form-control,.input-group>.custom-select+.custom-file,.input-group>.custom-select+.custom-select,.input-group>.custom-select+.form-control,.input-group>.form-control+.custom-file,.input-group>.form-control+.custom-select,.input-group>.form-control+.form-control,.input-group>.form-control-plaintext+.custom-file,.input-group>.form-control-plaintext+.custom-select,.input-group>.form-control-plaintext+.form-control{margin-left:-1px}.input-group>.custom-file .custom-file-input:focus~.custom-file-label,.input-group>.custom-select:focus,.input-group>.form-control:focus{z-index:3}.input-group>.custom-file .custom-file-input:focus{z-index:4}.input-group>.custom-select:not(:last-child),.input-group>.form-control:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.custom-select:not(:first-child),.input-group>.form-control:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.input-group>.custom-file{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center}.input-group>.custom-file:not(:last-child) .custom-file-label,.input-group>.custom-file:not(:last-child) .custom-file-label::after{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.custom-file:not(:first-child) .custom-file-label{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-append,.input-group-prepend{display:-ms-flexbox;display:flex}.input-group-append .btn,.input-group-prepend .btn{position:relative;z-index:2}.input-group-append .btn:focus,.input-group-prepend .btn:focus{z-index:3}.input-group-append .btn+.btn,.input-group-append .btn+.input-group-text,.input-group-append .input-group-text+.btn,.input-group-append .input-group-text+.input-group-text,.input-group-prepend .btn+.btn,.input-group-prepend .btn+.input-group-text,.input-group-prepend .input-group-text+.btn,.input-group-prepend .input-group-text+.input-group-text{margin-left:-1... [truncated message content] |
|
From: Mercurial C. <th...@in...> - 2026-05-18 03:50:29
|
3 new changesets in roundup: pushed by: rouilj https://sourceforge.net/p/roundup/code/ci/55937ea8655e changeset: 8643:55937ea8655e user: John Rouillard <ro...@ie...> date: Sun May 17 20:30:54 2026 -0400 summary: chore: jinja2 template dependencies updated: bootstrap 4.4.1 -> 4.6.2 https://sourceforge.net/p/roundup/code/ci/3913906eac2c changeset: 8644:3913906eac2c user: John Rouillard <ro...@ie...> date: Sun May 17 22:23:57 2026 -0400 summary: bug: issue2551377 - Disabled submit button UI issues. Fixed https://sourceforge.net/p/roundup/code/ci/9829f2e8ecbf changeset: 8645:9829f2e8ecbf tag: tip user: John Rouillard <ro...@ie...> date: Sun May 17 23:47:46 2026 -0400 summary: refactor: change method used to report statistics. -- Repository URL: https://sourceforge.net/p/roundup/code |
|
From: Mercurial C. <th...@in...> - 2026-05-17 06:27:57
|
# HG changeset patch
# User John Rouillard <ro...@ie...>
# Date 1778998162 14400
# Sun May 17 02:09:22 2026 -0400
# Node ID c355667a16bdd8ff62a34c90fadf02832180608e
# Parent ede573cfbd7d0d4e7a5755aa3a3c679d3167ebd5
bug: modernize classic template page.html
Remove table layout from page.html. Replace with modern HTML5: header,
nav, main, and search.
Reset all box-sizing to border-box to make sizing more predictable.
Use flexbox in the header tag to separate the search component on the
right from the (optional) logo/title on the left. Also add some
padding to the inline body so things stay away from the edges of the
viewport. Span whole viewport.
Added a div around nav (sidebar) and main and use flexbox to lay them
out. nav has max-width of 20% of viewport.
At 600px breakpoint, stack the nav and main items. Also take the 6
blocks in the nav and arrange in a 2x3 or 3x2 grid above main.
The underline when hovering over links is back. It looks like it got
put in the wrong place and was overridden. Also used relative colors
to darken the underline compared to the text.
Hide the CSV export link when printing.
Due to change in style.css update the liveserver file range request
tests which targets style.css.
diff -r ede573cfbd7d -r c355667a16bd CHANGES.txt
--- a/CHANGES.txt Sat May 16 23:52:37 2026 -0400
+++ b/CHANGES.txt Sun May 17 02:09:22 2026 -0400
@@ -107,6 +107,14 @@
you were trying to edit the existing query. This fix modifies the
queryname before generating the error form. So when submitted it
doesn't overwrite "asearch". (John Rouillard)
+- update the classic tracker's page.html to remove table based
+ layout. It now uses html5 landmarks like header, main, search,
+ nav. It also uses flexbox and grid layouts and includes a viewport
+ meta tag for use on mobile devices. It can be navigated with the
+ keyboard. Parts of the page that are too wide for the viewport can
+ be scrolled horizontally with the keyboard or touch. Some other CSS
+ changes make it easier to use. This is a first step to making the
+ classic template more responsive. (John Rouillard)
Features:
diff -r ede573cfbd7d -r c355667a16bd doc/upgrading.txt
--- a/doc/upgrading.txt Sat May 16 23:52:37 2026 -0400
+++ b/doc/upgrading.txt Sun May 17 02:09:22 2026 -0400
@@ -421,6 +421,54 @@
{% endif %}
{% endblock %}
+
+Modernizing Classic Tracker HTML (optional)
+-------------------------------------------
+
+This update improves the classic tracker by changing the page layout
+to use modern HTML and CSS instead of the old table layout from
+2001. All the updates are done by changing the ``page.html`` and the
+``style.css`` files.
+
+Among the fixes are:
+
+* HTML5 landmarks like header, main, search, and nav replace the
+ table that was used for layout.
+* a meta viewport tag was added for mobile devices.
+* the sidebar and main columns now use flexbox. They will stack and
+ turn into one column when the screen size is less than 600px.
+* The sidebar will change from 6 blocks in a column to 6 blocks in a
+ responsive grid when the screen size is under 600px.
+* Text in the nav/sidebar will have wrapped lines indented making it
+ easier to identify individual queries with long names.
+* The other templates in your tracker should not need any changes. If
+ they are too wide the updated ``page.html`` enables keyboard or
+ touch horizontal scrolling to see all of a table or other content.
+* A line now appears under a link when you hover over it.
+* The header uses flexbox to arrange the left side (logo/page
+ description) and right side (search) components.
+
+The most noticable change is that the page title now lines up with the
+edge of the screen instead of the edge of the main column. Other than
+that your tracker should look pretty much the same as before.
+
+Two files were changed to provide this basic responsiveness:
+``page.html`` and ``style.css``.
+
+Most local changes to ``page.html`` are done to add javascript. You
+should be able to copy the new page.html from the classic tracker
+template and put your ``script`` tags back in to benefit from this
+upgrade.
+
+You should also copy ``style.css`` into your tracker home's html
+directory and merge any local changes you made. At the top of the new
+``style.css`` are directions for two adjustments. Long query names
+that wrap in the sidebar will have their wrapped lines indented by
+0.5em. You can control this by uncommenting and adjusting the value of
+the css custom property ``--long-query-indent``. Additionally you can
+change the 600px viewport breakpoint by following the directions at
+the top of ``style.css``.
+
.. index:: Upgrading; 2.4.0 to 2.5.0
Migrating from 2.4.0 to 2.5.0
diff -r ede573cfbd7d -r c355667a16bd share/roundup/templates/classic/html/page.html
--- a/share/roundup/templates/classic/html/page.html Sat May 16 23:52:37 2026 -0400
+++ b/share/roundup/templates/classic/html/page.html Sun May 17 02:09:22 2026 -0400
@@ -3,6 +3,7 @@
><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
+<meta name="viewport" content="width=device-width, initial-scale=1">
<title metal:define-slot="head_title">title goes here</title>
<link rel="stylesheet" type="text/css" href="@@file/style.css">
<meta http-equiv="Content-Type"
@@ -14,175 +15,170 @@
<metal:x define-slot="more-javascript" />
</head>
-<body class="body">
-
-<table class="body"
- tal:define="
-kw_edit python:request.user.hasPermission('Edit', 'keyword');
-kw_create python:request.user.hasPermission('Create', 'keyword');
-kw_edit_link python:kw_edit and db.keyword.list();
-columns string:id,activity,title,creator,status;
-columns_showall string:id,activity,title,creator,assignedto,status;
-status_notresolved string:-1,1,2,3,4,5,6,7;
-"
->
+<body class="body" tal:define="
+ kw_edit python:request.user.hasPermission('Edit', 'keyword');
+ kw_create python:request.user.hasPermission('Create', 'keyword');
+ kw_edit_link python:kw_edit and db.keyword.list();
+ columns string:id,activity,title,creator,status;
+ columns_showall string:id,activity,title,creator,assignedto,status;
+ status_notresolved string:-1,1,2,3,4,5,6,7;
+ ">
+ <header>
+ <div class="header-left">
+ <div id="header-logo">
+ </div>
+ <div id="body-title">
+ <h2><span metal:define-slot="body_title">body title</span></h2>
+ </div>
+ </div>
+ <search id="searchbox">
+ <form method="GET" action="issue">
+ <input type="hidden" name="@columns"
+ tal:attributes="value columns_showall"
+ value="id,activity,title,creator,assignedto,status"/>
+ <input type="hidden" name="@sort" value="activity"/>
+ <input type="hidden" name="@group" value="priority"/>
+ <input id="search-text" name="@search_text" size="10"
+ tal:attributes="value request/search_text | default" />
+ <input type="submit" id="submit" name="submit" value="Search"
+ i18n:attributes="value" />
+ </form>
+ </search>
+ </header>
-<tr>
- <td class="page-header-left"> </td>
- <td class="page-header-top">
- <div id="searchbox">
- <form method="GET" action="issue">
- <input type="hidden" name="@columns"
- tal:attributes="value columns_showall"
- value="id,activity,title,creator,assignedto,status"/>
- <input type="hidden" name="@sort" value="activity"/>
- <input type="hidden" name="@group" value="priority"/>
- <input id="search-text" name="@search_text" size="10"
- tal:attributes="value request/search_text | default" />
- <input type="submit" id="submit" name="submit" value="Search"
- i18n:attributes="value" />
- </form>
- </div>
- <div id="body-title">
- <h2><span metal:define-slot="body_title">body title</span></h2>
- </div>
- </td>
-</tr>
+ <div class="body">
+ <nav aria-label="Main" i18n:attributes="aria-label">
+ <p class="classblock"
+ tal:condition="python:request.user.hasPermission('View', 'query')">
+ <span i18n:translate=""
+ ><b>Your Queries</b> (<a href="query?@template=edit">edit</a>)</span><br>
+ <tal:block tal:repeat="qs request/user/queries">
+ <a href="#" tal:attributes="href string:${qs/klass}?${qs/url}&@dispname=${qs/name/url_quote}"
+ tal:content="qs/name">link</a><br>
+ </tal:block>
+ </p>
-<tr>
- <td rowspan="2" valign="top" class="sidebar">
- <p class="classblock"
- tal:condition="python:request.user.hasPermission('View', 'query')">
- <span i18n:translate=""
- ><b>Your Queries</b> (<a href="query?@template=edit">edit</a>)</span><br>
- <tal:block tal:repeat="qs request/user/queries">
- <a href="#" tal:attributes="href string:${qs/klass}?${qs/url}&@dispname=${qs/name/url_quote}"
- tal:content="qs/name">link</a><br>
- </tal:block>
- </p>
+ <form method="GET" tal:attributes="action request/base">
+ <p class="classblock"
+ tal:condition="python:request.user.hasPermission('View', 'issue')">
+ <b i18n:translate="">Issues</b><br>
+ <span tal:condition="python:request.user.hasPermission('Create', 'issue')">
+ <a href="issue?@template=item" i18n:translate="">Create New</a><br>
+ </span>
+ <a href="#"
+ tal:attributes="href python:request.indexargs_url('issue', {
+ '@sort': '-activity',
+ '@group': 'priority',
+ '@filter': 'status,assignedto',
+ '@columns': columns,
+ '@search_text': '',
+ 'status': status_notresolved,
+ 'assignedto': '-1',
+ '@dispname': i18n.gettext('Show Unassigned'),
+ })"
+ i18n:translate="">Show Unassigned</a><br>
+ <a href="#"
+ tal:attributes="href python:request.indexargs_url('issue', {
+ '@sort': '-activity',
+ '@group': 'priority',
+ '@filter': 'status',
+ '@columns': columns_showall,
+ '@search_text': '',
+ 'status': status_notresolved,
+ '@dispname': i18n.gettext('Show All'),
+ })"
+ i18n:translate="">Show All</a><br>
+ <a href="issue?@template=search" i18n:translate="">Search</a><br>
+ <input type="submit" class="form-small" value="Show issue:"
+ i18n:attributes="value"><input class="form-small" size="4"
+ type="text" name="@number">
+ <input type="hidden" name="@type" value="issue">
+ <input type="hidden" name="@action" value="show">
+ </p>
+ </form>
- <form method="POST" tal:attributes="action request/base">
- <p class="classblock"
- tal:condition="python:request.user.hasPermission('View', 'issue')">
- <b i18n:translate="">Issues</b><br>
- <span tal:condition="python:request.user.hasPermission('Create', 'issue')">
- <a href="issue?@template=item" i18n:translate="">Create New</a><br>
- </span>
- <a href="#"
- tal:attributes="href python:request.indexargs_url('issue', {
- '@sort': '-activity',
- '@group': 'priority',
- '@filter': 'status,assignedto',
- '@columns': columns,
- '@search_text': '',
- 'status': status_notresolved,
- 'assignedto': '-1',
- '@dispname': i18n.gettext('Show Unassigned'),
- })"
- i18n:translate="">Show Unassigned</a><br>
- <a href="#"
- tal:attributes="href python:request.indexargs_url('issue', {
- '@sort': '-activity',
- '@group': 'priority',
- '@filter': 'status',
- '@columns': columns_showall,
- '@search_text': '',
- 'status': status_notresolved,
- '@dispname': i18n.gettext('Show All'),
- })"
- i18n:translate="">Show All</a><br>
- <a href="issue?@template=search" i18n:translate="">Search</a><br>
- <input type="submit" class="form-small" value="Show issue:"
- i18n:attributes="value"><input class="form-small" size="4"
- type="text" name="@number">
- <input type="hidden" name="@type" value="issue">
- <input name="@csrf" type="hidden"
- tal:attributes="value python:utils.anti_csrf_nonce()">
- <input type="hidden" name="@action" value="show">
- </p>
- </form>
+ <p class="classblock"
+ tal:condition="python:kw_edit or kw_create">
+ <b i18n:translate="">Keywords</b><br>
+ <span tal:condition="python:request.user.hasPermission('Create', 'keyword')">
+ <a href="keyword?@template=item" i18n:translate="">Create New</a><br>
+ </span>
+ <span tal:condition="kw_edit_link">
+ <a href="keyword?@template=item" i18n:translate="">Edit Existing</a><br>
+ </span>
+ </p>
- <p class="classblock"
- tal:condition="python:kw_edit or kw_create">
- <b i18n:translate="">Keywords</b><br>
- <span tal:condition="python:request.user.hasPermission('Create', 'keyword')">
- <a href="keyword?@template=item" i18n:translate="">Create New</a><br>
- </span>
- <span tal:condition="kw_edit_link">
- <a href="keyword?@template=item" i18n:translate="">Edit Existing</a><br>
- </span>
- </p>
+ <p class="classblock"
+ tal:condition="python:request.user.hasPermission('View', 'user')">
+ <b i18n:translate="">Administration</b><br>
+ <span tal:condition="python:request.user.hasPermission('Edit', None)">
+ <a href="home?@template=classlist" i18n:translate="">Class List</a><br>
+ </span>
+ <span tal:condition="python:request.user.hasPermission('View', 'user')
+ or request.user.hasPermission('Edit', 'user')">
+ <a href="user" i18n:translate="">User List</a><br>
+ </span>
+ <a tal:condition="python:request.user.hasPermission('Create', 'user')"
+ href="user?@template=item" i18n:translate="">Add User</a>
+ </p>
- <p class="classblock"
- tal:condition="python:request.user.hasPermission('View', 'user')">
- <b i18n:translate="">Administration</b><br>
- <span tal:condition="python:request.user.hasPermission('Edit', None)">
- <a href="home?@template=classlist" i18n:translate="">Class List</a><br>
- </span>
- <span tal:condition="python:request.user.hasPermission('View', 'user')
- or request.user.hasPermission('Edit', 'user')">
- <a href="user" i18n:translate="">User List</a><br>
- </span>
- <a tal:condition="python:request.user.hasPermission('Create', 'user')"
- href="user?@template=item" i18n:translate="">Add User</a>
- </p>
+ <form method="POST" tal:condition="python:request.user.username=='anonymous'"
+ tal:attributes="action request/base">
+ <p class="userblock">
+ <b i18n:translate="">Login</b><br>
+ <input size="10" required name="__login_name"><br>
+ <input size="10" spellcheck="false" type="password"
+ tal:attributes="required python: 'required'
+ if not db.config.WEB_LOGIN_EMPTY_PASSWORDS else nothing"
+ name="__login_password"><br>
+ <input type="hidden" name="@action" value="Login">
+ <input type="checkbox" name="remember" id="remember">
+ <label for="remember" i18n:translate="">Remember me?</label><br>
+ <input name="@csrf" type="hidden"
+ tal:attributes="value python:utils.anti_csrf_nonce()">
+ <input type="submit" value="Login" i18n:attributes="value"><br>
+ <input type="hidden" name="__came_from"
+ tal:condition="exists:request/env/QUERY_STRING"
+ tal:attributes="value string:${request/base}${request/env/PATH_INFO}?${request/env/QUERY_STRING}">
+ <input type="hidden" name="__came_from"
+ tal:condition="not:exists:request/env/QUERY_STRING"
+ tal:attributes="value string:${request/base}${request/env/PATH_INFO}">
+ <span tal:replace="structure request/indexargs_form" />
+ <a href="user?@template=register"
+ tal:condition="python:request.user.hasPermission('Register', 'user')"
+ i18n:translate="">Register</a><br>
+ <a href="user?@template=forgotten" i18n:translate="">Lost your login?</a><br>
+ </p>
+ </form>
- <form method="POST" tal:condition="python:request.user.username=='anonymous'"
- tal:attributes="action request/base">
- <p class="userblock">
- <b i18n:translate="">Login</b><br>
- <input size="10" required name="__login_name"><br>
- <input size="10" spellcheck="false" type="password"
- tal:attributes="required python: 'required'
- if not db.config.WEB_LOGIN_EMPTY_PASSWORDS else nothing"
- name="__login_password"><br>
- <input type="hidden" name="@action" value="Login">
- <input type="checkbox" name="remember" id="remember">
- <label for="remember" i18n:translate="">Remember me?</label><br>
- <input name="@csrf" type="hidden"
- tal:attributes="value python:utils.anti_csrf_nonce()">
- <input type="submit" value="Login" i18n:attributes="value"><br>
- <input type="hidden" name="__came_from"
- tal:condition="exists:request/env/QUERY_STRING"
- tal:attributes="value string:${request/base}${request/env/PATH_INFO}?${request/env/QUERY_STRING}">
- <input type="hidden" name="__came_from"
- tal:condition="not:exists:request/env/QUERY_STRING"
- tal:attributes="value string:${request/base}${request/env/PATH_INFO}">
- <span tal:replace="structure request/indexargs_form" />
- <a href="user?@template=register"
- tal:condition="python:request.user.hasPermission('Register', 'user')"
- i18n:translate="">Register</a><br>
- <a href="user?@template=forgotten" i18n:translate="">Lost your login?</a><br>
- </p>
- </form>
+ <p class="userblock" tal:condition="python:request.user.username != 'anonymous'">
+ <b i18n:translate="">Hello, <span i18n:name="user"
+ tal:replace="python:request.user.username.plain(escape=1)">username</span></b><br>
+ <a href="#"
+ tal:attributes="href python:request.indexargs_url('issue', {
+ '@sort': '-activity',
+ '@group': 'priority',
+ '@filter': 'status,assignedto',
+ '@columns': 'id,activity,title,creator,status',
+ '@search_text': '',
+ 'status': status_notresolved,
+ 'assignedto': request.user.id,
+ '@dispname': i18n.gettext('Your Issues'),
+ })"
+ i18n:translate="">Your Issues</a><br>
+ <a href="#" tal:attributes="href string:user${request/user/id}"
+ i18n:translate="">Your Details</a><br>
+ <a href="#" tal:attributes="href python:request.indexargs_url('',
+ {'@action':'logout'})" i18n:translate="">Logout</a>
+ </p>
+ <p class="userblock">
+ <b i18n:translate="">Help</b><br>
+ <a href="https://www.roundup-tracker.org"
+ i18n:translate="">Roundup docs</a>
+ </p>
+ </nav>
- <p class="userblock" tal:condition="python:request.user.username != 'anonymous'">
- <b i18n:translate="">Hello, <span i18n:name="user"
- tal:replace="python:request.user.username.plain(escape=1)">username</span></b><br>
- <a href="#"
- tal:attributes="href python:request.indexargs_url('issue', {
- '@sort': '-activity',
- '@group': 'priority',
- '@filter': 'status,assignedto',
- '@columns': 'id,activity,title,creator,status',
- '@search_text': '',
- 'status': status_notresolved,
- 'assignedto': request.user.id,
- '@dispname': i18n.gettext('Your Issues'),
- })"
- i18n:translate="">Your Issues</a><br>
- <a href="#" tal:attributes="href string:user${request/user/id}"
- i18n:translate="">Your Details</a><br>
- <a href="#" tal:attributes="href python:request.indexargs_url('',
- {'@action':'logout'})" i18n:translate="">Logout</a>
- </p>
- <p class="userblock">
- <b i18n:translate="">Help</b><br>
- <a href="https://www.roundup-tracker.org"
- i18n:translate="">Roundup docs</a>
- </p>
- </td>
- <td>
+ <main tabindex="0">
<p tal:condition="options/error_message | nothing" class="error-message"
tal:repeat="m options/error_message"
tal:content="structure string:$m <br/ > " />
@@ -192,14 +188,9 @@
<a class="form-small" tal:attributes="href request/current_url"
i18n:translate="">clear this message</a>
</p>
- </td>
-</tr>
-<tr>
- <td class="content" metal:define-slot="content">Page content goes here</td>
-</tr>
-
-</table>
-
+ <div class="content" metal:define-slot="content">Page content goes here</div>
+</main>
+</div>
<pre tal:condition="request/form/debug | nothing" tal:content="request">
</pre>
diff -r ede573cfbd7d -r c355667a16bd share/roundup/templates/classic/html/style.css
--- a/share/roundup/templates/classic/html/style.css Sat May 16 23:52:37 2026 -0400
+++ b/share/roundup/templates/classic/html/style.css Sun May 17 02:09:22 2026 -0400
@@ -1,3 +1,16 @@
+:root {
+ /* uncomment and set this to control the indentation for wrapped
+ lines in the Your Queries section of the sidebar */
+ /*--long-query-indent: 0.5em;*/
+
+ /* search this file for single-column-breakpoint to control the
+ minimum screen size where the left/right column layout is
+ changed to a top/bottom single column layout. */
+}
+
+/* reset */
+*, *:before, *:after { box-sizing: border-box; }
+
/* main page styles */
body.body {
font-family: sans-serif, Arial, Helvetica;
@@ -5,41 +18,93 @@
color: #333;
margin: 0;
}
-a[href]:hover {
- color:blue;
- text-decoration: underline;
-}
a[href], a[href]:link {
color:blue;
text-decoration: none;
}
+a[href]:hover {
+ color: blue;
+ text-decoration: underline;
+ text-decoration-color: hsl(from blue h s calc(l - 20));
+}
pre {
white-space: pre-wrap;
}
-table.body {
- border: 0;
- padding: 0;
- border-spacing: 0;
- border-collapse: separate;
+body {
+ padding: 0 0.5em;
+}
+
+header {
+ border-bottom: 1px solid #444;
+ display: flex;
+ justify-content: space-between;
+ margin-block-start: 1em;
+ margin-block-end: 2px;
+}
+
+.header-left {
+ display: flex;
+}
+
+div.body {
+ /* contains nav and main elements. */
+ display: flex;
+ gap: 0.5em;
+ flex-wrap: wrap;
+}
+
+nav {
+ flex-basis: auto;
+ flex-shrink: 1;
+ /* force wrapping of long search names. */
+ max-width: 20%;
}
-td.page-header-left {
- padding: 5px;
- border-bottom: 1px solid #444;
+nav > p > a {
+ /* indent wrapped lines in the Your Queries section
+ to make scanning each query easier */
+ display: inline-block;
+ padding-inline-start: var(--long-query-indent, 0.5em);
+ text-indent: calc(var(--long-query-indent, 0.5em) * -1);
}
-td.sidebar {
- padding: 1px 0 0 1px;
- white-space: nowrap;
+
+main {
+ flex-basis: 0%;
+ flex-grow: 1;
+ /* make content accessible on overflow. */
+ overflow-x: auto;
}
-/* don't display the sidebar when printing */
+/* single-column-breakpoint (600px) - when a two column layout is
+ changed to a single left -> top, right -> bottom layout. */
+
+@media (width < 600px) {
+ main {
+ flex-basis:100%;
+ flex-grow: 0;
+ flex-shrink:0;
+ }
+ nav {
+ display: grid;
+ gap: 8px;
+ grid-template-columns: repeat(auto-fit,minmax(10em,1fr));
+ max-width: initial;
+ width: 100%;
+ }
+ nav > form > p {
+ /* force nav children that are forms to stretch to the full
+ grid cell height by expanding their internal p
+ tags. Otherwise short forms do not fill the same
+ space as the other items in the grid. */
+ height: 100%;
+ }
+}
+
+/* don't display interactive controls when printing */
@media print {
- td.page-header-left {
- display: none;
- }
- td.sidebar {
+ nav {
display: none;
}
.index-controls {
@@ -48,19 +113,18 @@
#searchbox {
display: none;
}
-}
-td.page-header-top {
- padding: 5px;
- border-bottom: 1px solid #444;
+ a[href*="action=export"] {
+ /* hide CSV export on index pages */
+ display: none;
+ }
}
-div#searchbox {
- float: right;
- padding-top: 1em;
+#searchbox {
+ padding-top: 1.5em;
}
-div#searchbox input#search-text {
+#searchbox input#search-text {
width: 10em;
}
@@ -72,14 +136,14 @@
font-family: monospace;
}
-td.sidebar p.classblock {
+nav p.classblock {
padding: 2px 5px 2px 5px;
margin: 1px;
border: 1px solid #444;
background-color: #eee;
}
-td.sidebar p.userblock {
+nav p.userblock {
padding: 2px 5px 2px 5px;
margin: 1px 1px 1px 1px;
border: 1px solid #444;
diff -r ede573cfbd7d -r c355667a16bd test/test_liveserver.py
--- a/test/test_liveserver.py Sat May 16 23:52:37 2026 -0400
+++ b/test/test_liveserver.py Sun May 17 02:09:22 2026 -0400
@@ -247,7 +247,7 @@
@skip_hypothesis
class FuzzGetUrls(WsgiSetup, ClientSetup):
- _max_examples = 100
+ _max_examples = 10
# Timeout for each fuzz test in ms. Use env variable in local
# pytest.ini if your dev environment can't complete in the default
@@ -332,7 +332,7 @@
@skip_hypothesis
class FuzzTestSettingData(WsgiSetup, ClientSetup):
- _max_examples = 100
+ _max_examples = 10
# Timeout for each fuzz test in ms. Use env variable in local
# pytest.ini if your dev environment can't complete in the default
@@ -744,7 +744,7 @@
hdrs = {"Range": "bytes=0-10"}
f = requests.get(self.url_base() + "/@@file/style.css", headers=hdrs)
self.assertEqual(f.status_code, 206)
- self.assertEqual(f.content, b"/* main pag")
+ self.assertEqual(f.content, b":root {\n ")
# compression disabled for length < 100, so we can use 11 here
self.assertEqual(f.headers['content-length'], '11')
self.assertEqual(f.headers['content-range'],
@@ -754,7 +754,7 @@
hdrs = {"Range": "bytes=10-20"}
f = requests.get(self.url_base() + "/@@file/style.css", headers=hdrs)
self.assertEqual(f.status_code, 206)
- self.assertEqual(f.content, b"ge styles *")
+ self.assertEqual(f.content, b" /* uncomm")
# compression disabled for length < 100, so we can use 11 here
self.assertEqual(f.headers['content-length'], '11')
self.assertEqual(f.headers['content-range'],
@@ -774,7 +774,7 @@
hdrs['If-Range'] = etag
f = requests.get(self.url_base() + "/@@file/style.css", headers=hdrs)
self.assertEqual(f.status_code, 206)
- self.assertEqual(f.content, b"/* main pag")
+ self.assertEqual(f.content, b":root {\n ")
# compression disabled for length < 100, so we can use 11 here
self.assertEqual(f.headers['content-length'], '11')
self.assertEqual(f.headers['content-range'],
|
|
From: Mercurial C. <th...@in...> - 2026-05-17 06:27:56
|
# HG changeset patch
# User John Rouillard <ro...@ie...>
# Date 1778989957 14400
# Sat May 16 23:52:37 2026 -0400
# Node ID ede573cfbd7d0d4e7a5755aa3a3c679d3167ebd5
# Parent d681fce4c3789b28a31f109b47aa462c58acfe28
bug: don't clobber existing query with identically named new query
Fix SearchAction so we don't clobber a saved search when a new search
of the same name is created.
The issue.search.html template sets @old-queryname to the value of
@queryname. In new searches @old-queryname is "" so saving the new
query doesn't update an existing query identified by @old-queryname.
However, assume a search named "asearch" exists. Create a new search
(not editing the existing "asearch").
Set new query params and name it "asearch". Roundup will warn you that
"asearch" already exists and you need to choose a new name. It can
tell this because @old-queryname is empty and @queryname is looked up
and a conflicting identically named search is found.
Now a replacement search input form is generated with the warning that
the query name is in use. However in this replacement form
@old-queryname is set to "asearch". Then the user renames the query
to "bsearch" and saves/executes.
This used to overwrite "asearch" as it set "@old-queryname" of the
form to "asearch". So it looked like you were trying to edit
the existing query.
This fix modifies @queryname (and hence @old-queryname) before
generating the error form by appending " - duplicate'. Now when it is
submitted it doesn't overwrite "asearch".
diff -r d681fce4c378 -r ede573cfbd7d CHANGES.txt
--- a/CHANGES.txt Sat May 16 21:47:18 2026 -0400
+++ b/CHANGES.txt Sat May 16 23:52:37 2026 -0400
@@ -96,6 +96,17 @@
- Make queries selected from query edit screen include the query
name/display name. This makes it work the same as invoking a query
from the main "Your Queries" menu. (John Rouillard)
+- Fix SearchAction to not clobber a saved search when a new search of
+ the same name is created. Assume a search named "asearch"
+ exists. Create a new search (not editing the existing
+ "asearch"). Set new query params and name it "asearch". Roundup will
+ warn you that "asearch" already exists and you need to choose a new
+ name. In the warning form, you rename it to "bsearch" and
+ save/execute. Before this would overwrite "asearch" as it set the
+ "@old-queryname" of the warning form to "asearch". So it looked like
+ you were trying to edit the existing query. This fix modifies the
+ queryname before generating the error form. So when submitted it
+ doesn't overwrite "asearch". (John Rouillard)
Features:
diff -r d681fce4c378 -r ede573cfbd7d roundup/cgi/actions.py
--- a/roundup/cgi/actions.py Sat May 16 21:47:18 2026 -0400
+++ b/roundup/cgi/actions.py Sat May 16 23:52:37 2026 -0400
@@ -358,6 +358,23 @@
"Please choose another name.") % (queryname)
self.client.add_error_message(message)
+ # Assume we duplicate query "a". When the
+ # response is generated, @old-queryname is set
+ # to 'a' in the template. This causes the 'a'
+ # query to be changed to the new query name
+ # and values.
+
+ # So we change @queryname by appending ' -
+ # duplicate'. This way @old-queryname will be
+ # 'a - duplicate' and will not overwrite the
+ # query named 'a'. (but it will
+ # overwrite/change a query named "a -
+ # duplicate". Hopefully nobody uses that name
+ # 8-).
+ self.client.form['@queryname'].value = (
+ # .Hint suffix added to queryname when
+ # a new queryname is already used.
+ queryname + _(" - duplicate"))
return
# edit the new way, query name not a key any more
|
|
From: Mercurial C. <th...@in...> - 2026-05-17 06:27:55
|
# HG changeset patch
# User John Rouillard <ro...@ie...>
# Date 1778982438 14400
# Sat May 16 21:47:18 2026 -0400
# Node ID d681fce4c3789b28a31f109b47aa462c58acfe28
# Parent 9a2845390354bf41b984337ccf21747d9bdb54b4
bug: queries triggered from query edit page missing query name
The query edit page is used to editing the queries listed in the main
menu's "Your Queries" section. However it also allows you to execute
all available queries.
However when these queries are executed, they query name is not
included. This patch fixes that.
Also it improves the jinja2 template to display the query name which
didn't work even when the query was done from the main menu.
The jinja code works in manual testing. I verified that & and <> etc
are escaped properly in the URL as well as in the html with the
classic and jinja2 trackers.
diff -r 9a2845390354 -r d681fce4c378 CHANGES.txt
--- a/CHANGES.txt Wed May 13 21:40:51 2026 -0400
+++ b/CHANGES.txt Sat May 16 21:47:18 2026 -0400
@@ -93,6 +93,9 @@
without further info. Added more specific ConnectionAbortedError
and changed IOError handling. Case in issue now logs the exception
for diagnosis.
+- Make queries selected from query edit screen include the query
+ name/display name. This makes it work the same as invoking a query
+ from the main "Your Queries" menu. (John Rouillard)
Features:
diff -r 9a2845390354 -r d681fce4c378 doc/upgrading.txt
--- a/doc/upgrading.txt Wed May 13 21:40:51 2026 -0400
+++ b/doc/upgrading.txt Sat May 16 21:47:18 2026 -0400
@@ -365,6 +365,62 @@
Additional details can be found in the :ref:`nonceless CSRF` section of
the reference guide.
+Make Query Edit Page Queries Display Query Name (optional)
+----------------------------------------------------------
+
+When queries are started from the main ``Your Queries`` menu section,
+the window shows the query name for TAL based trackers. Users can
+pick which queries/searches appear in the main menu's ``Your Queries``
+section by clicking the ``edit`` link to go to the edit screen. The
+``edit`` screen also lets users run the queries that aren't in the
+main section.
+
+Before this update, the query name wasn't shown when running queries
+from the ``edit`` screen. This update fixes that for all the included
+templates. It also makes the jinja2 issue index template show the
+query name.
+
+You can add this to your tracker. If you are not using a jinja2 based
+tracker, open your tracker's ``html/query.edit.html`` file. Find and
+change the line that includes::
+
+ <a tal:attributes="href string:${query/klass}?${query/url}"
+
+to look like::
+
+ <a tal:attributes="href string:${query/klass}?${query/url}&@dispname=${query/name/url_quote}"
+
+If you are using the jinja2 template, a similar change is needed to
+``html/query.edit.html`` replacing::
+
+ <a href="{{ query.klass.plain()|u }}?{{ query.url.plain()|u }}">{{ query.name.plain() }}</a>
+
+with::
+
+ <a href="{{ query.klass.plain()|u }}?{{ query.url.plain()|u }}&@dispname={{ query.name.url_quote()|u }}">{{ query.name.plain() }}</a>
+
+Then in the ``html/issue.index.html`` you will add::
+
+ {% if request.form['@dispname'] %}
+ - {{ request.form['@dispname'].value|u }}
+ {% endif %}
+
+to the ``head_title`` and ``page_header`` blocks so they look like::
+
+ {% block head_title %}
+ {% trans %}List of issues{% endtrans %}
+ {% if request.form['@dispname'] %}
+ - {{ request.form['@dispname'].value|u }}
+ {% endif %}
+ {% endblock %}
+
+ {% block page_header %}
+ {% trans %}List of issues{% endtrans %}
+ {% if request.form['@dispname'] %}
+ - {{ request.form['@dispname'].value|u }}
+ {% endif %}
+ {% endblock %}
+
.. index:: Upgrading; 2.4.0 to 2.5.0
Migrating from 2.4.0 to 2.5.0
diff -r 9a2845390354 -r d681fce4c378 share/roundup/templates/classic/html/query.edit.html
--- a/share/roundup/templates/classic/html/query.edit.html Wed May 13 21:40:51 2026 -0400
+++ b/share/roundup/templates/classic/html/query.edit.html Sat May 16 21:47:18 2026 -0400
@@ -68,8 +68,8 @@
<tr tal:define="queries python:db.query.filter(filterspec={'creator': uid})"
tal:repeat="query queries">
<tal:block>
- <td><a tal:attributes="href string:${query/klass}?${query/url}"
- tal:content="query/name">query</a></td>
+ <td><a tal:attributes="href string:${query/klass}?${query/url}&@dispname=${query/name/url_quote}"
+ tal:content="query/name">query</a></td>
<td metal:define-macro="include">
<select tal:condition="python:query.id not in mine"
diff -r 9a2845390354 -r d681fce4c378 share/roundup/templates/devel/html/query.edit.html
--- a/share/roundup/templates/devel/html/query.edit.html Wed May 13 21:40:51 2026 -0400
+++ b/share/roundup/templates/devel/html/query.edit.html Sat May 16 21:47:18 2026 -0400
@@ -67,8 +67,8 @@
<tr tal:repeat="query mine">
<tal:block condition="query/is_retired">
- <td><a tal:attributes="href string:${query/klass}?${query/url}"
- tal:content="query/name">query</a></td>
+ <td><a tal:attributes="href string:${query/klass}?${query/url}&@dispname=${query/name/url_quote}"
+ tal:content="query/name">query</a></td>
<td metal:define-macro="include">
<select tal:condition="python:query.id not in mine"
diff -r 9a2845390354 -r d681fce4c378 share/roundup/templates/jinja2/html/issue.index.html
--- a/share/roundup/templates/jinja2/html/issue.index.html Wed May 13 21:40:51 2026 -0400
+++ b/share/roundup/templates/jinja2/html/issue.index.html Sat May 16 21:47:18 2026 -0400
@@ -2,10 +2,16 @@
{% block head_title %}
{% trans %}List of issues{% endtrans %}
+ {% if request.form['@dispname'] %}
+ - {{ request.form['@dispname'].value|u }}
+ {% endif %}
{% endblock %}
{% block page_header %}
{% trans %}List of issues{% endtrans %}
+ {% if request.form['@dispname'] %}
+ - {{ request.form['@dispname'].value|u }}
+ {% endif %}
{% endblock %}
{% block content %}
@@ -108,3 +114,4 @@
{% endif %}
{% endblock %}
+<!-- SHA: 6d38a513a175e5167c501d47dbb7528617df4bca -->
diff -r 9a2845390354 -r d681fce4c378 share/roundup/templates/jinja2/html/query.edit.html
--- a/share/roundup/templates/jinja2/html/query.edit.html Wed May 13 21:40:51 2026 -0400
+++ b/share/roundup/templates/jinja2/html/query.edit.html Sat May 16 21:47:18 2026 -0400
@@ -93,7 +93,7 @@
{% for query in queries %}
<tr>
<td>
- <a href="{{ query.klass.plain()|u }}?{{ query.url.plain()|u }}">{{ query.name.plain() }}</a>
+ <a href="{{ query.klass.plain()|u }}?{{ query.url.plain()|u }}&@dispname={{ query.name.url_quote()|u }}">{{ query.name.plain() }}</a>
</td>
{{ include_query(query) }}
diff -r 9a2845390354 -r d681fce4c378 share/roundup/templates/responsive/html/query.edit.html
--- a/share/roundup/templates/responsive/html/query.edit.html Wed May 13 21:40:51 2026 -0400
+++ b/share/roundup/templates/responsive/html/query.edit.html Sat May 16 21:47:18 2026 -0400
@@ -67,8 +67,8 @@
<tr tal:repeat="query mine">
<tal:block condition="query/is_retired">
- <td><a tal:attributes="href string:${query/klass}?${query/url}"
- tal:content="query/name">query</a></td>
+ <td><a tal:attributes="href string:${query/klass}?${query/url}&@dispname=${query/name/url_quote}"
+ tal:content="query/name">query</a></td>
<td metal:define-macro="include">
<select tal:condition="python:query.id not in mine"
|
|
From: Mercurial C. <th...@in...> - 2026-05-17 06:27:53
|
# HG changeset patch # User John Rouillard <ro...@ie...> # Date 1778722851 14400 # Wed May 13 21:40:51 2026 -0400 # Node ID 9a2845390354bf41b984337ccf21747d9bdb54b4 # Parent 1bcd6a0275713f1a2460a101506636a0021962a6 doc: update link to version 3.14 from 2 diff -r 1bcd6a027571 -r 9a2845390354 doc/developers.txt --- a/doc/developers.txt Wed May 13 21:37:24 2026 -0400 +++ b/doc/developers.txt Wed May 13 21:40:51 2026 -0400 @@ -680,7 +680,7 @@ .. _emacs: https://www.gnu.org/software/emacs/ .. _flake8: https://flake8.pycqa.org/en/latest/ .. _gettext package: https://www.gnu.org/savannah-checkouts/gnu/gettext/manual/gettext.html -.. _gettext module: https://docs.python.org/2/library/gettext.html +.. _gettext module: https://docs.python.org/3.14/library/gettext.html .. _GitHub Actions: https://github.com/roundup-tracker/roundup/actions .. _GNU: https://www.gnu.org/ .. _GNU mirror sites: https://www.gnu.org/prep/ftp.html |
|
From: Mercurial C. <th...@in...> - 2026-05-17 06:27:52
|
# HG changeset patch # User John Rouillard <ro...@ie...> # Date 1778722644 14400 # Wed May 13 21:37:24 2026 -0400 # Node ID 1bcd6a0275713f1a2460a101506636a0021962a6 # Parent 3f76baa4f0cb477c42e59aa09f9826465bdd4665 doc: fix example remove bad option. diff -r 3f76baa4f0cb -r 1bcd6a027571 doc/developers.txt --- a/doc/developers.txt Wed May 13 13:08:30 2026 -0400 +++ b/doc/developers.txt Wed May 13 21:37:24 2026 -0400 @@ -537,7 +537,7 @@ After creating the ```locale`` subdirectory in my tracker home, I was able to use:: - pybabel extract -F babel.cfg -d locale --add-comments ".Hint' \ + pybabel extract -F babel.cfg --add-comments ".Hint' \ --output locale/messages.po . with the following ``babel.cfg``:: |
|
From: Mercurial C. <th...@in...> - 2026-05-17 06:27:51
|
# HG changeset patch
# User John Rouillard <ro...@ie...>
# Date 1778692110 14400
# Wed May 13 13:08:30 2026 -0400
# Node ID 3f76baa4f0cb477c42e59aa09f9826465bdd4665
# Parent 1352ae565dee514bfbf2631e56ac8c047ffd9700
doc: reorder sections, roundup-gettext doc; jinja2 message extraction
Moved a number of sections around to group by:
marking translations
extracting translations
translating translations
Moved using translations in CLI to end from the middle of the other
structure as it didn't fit there.
Replaced footnote on roundup-gettext limitations with paragraph and
added lack of support for .Hint notes.
Added reference to jinja-i18n-tools for extracting translatable
strings from jinja templates. I wasn't able to run it successfully on
our jinja template though. But I was able to get pybabel to work so I
documented that.
diff -r 1352ae565dee -r 3f76baa4f0cb doc/developers.txt
--- a/doc/developers.txt Mon May 11 13:31:02 2026 -0400
+++ b/doc/developers.txt Wed May 13 13:08:30 2026 -0400
@@ -372,34 +372,8 @@
allowing the translator to see the replaced string so they can
understand how name and value are used.
-Command Line Interfaces
-~~~~~~~~~~~~~~~~~~~~~~~
-
-Scripts and routines run from the command line use "static" language
-defined by environment variables recognized by ``gettext`` module
-from Python library (``LANGUAGE``, ``LC_ALL``, ``LC_MESSAGES``, and
-``LANG``). Primarily, these are ``roundup-admin`` script and
-``admin.py`` module, but also help texts and startup error messages
-in other scripts and their supporting modules.
-
-For these interfaces, Python ``gettext`` engine must be initialized
-to use Roundup message catalogs. This is normally done by including
-the following line in the module imports::
-
- from i18n import _, ngettext
-
-Simple translations are automatically marked by calls to builtin
-message translation function ``_()``::
-
- print(_("This message is translated"))
-
-Translations for messages whose grammatical depends on a number
-must be done by ``ngettext()`` function::
-
- print(ngettext("Nuked %i file", "Nuked %i files", number_of_files_nuked))
-
Deferred Translations
-~~~~~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^^^^^^^^
Sometimes translatable strings appear in the source code in untranslated
form [#note_admin.py]_ and must be translated elsewhere.
@@ -426,7 +400,7 @@
as help messages.
Web User Interface
-~~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^^^^^
For Web User Interface, translation services are provided by Client
object. Action classes have methods ``_()`` and ``gettext()``,
@@ -516,7 +490,7 @@
see: localization; i18n
Extracting Translatable Messages
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The most common tool for message extraction is ``xgettext`` utility
from `GNU gettext package`_. Unfortunately, this utility has no means
@@ -535,12 +509,15 @@
just run ``gmake`` (or ``make``, if you are on a `GNU`_ system like
`linux`_ or `cygwin`_) in the ``locale`` directory.
+On-site Tracker Translation
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
For on-site i18n, Roundup provides command-line utility::
roundup-gettext <tracker_home>
extracting translatable messages from tracker's html templates and
-detectors / extensions (assuming `polib`_ is installed) [2]_.
+detectors / extensions (assuming `polib`_ is installed).
This utility creates message template file ``messages.pot`` in
``locale`` subdirectory of the tracker home directory. Translated
messages may be put in *locale*.po files (where *locale* is the two
@@ -548,6 +525,34 @@
These message catalogs are searched prior to system-wide translations
kept in the ``share`` directory.
+Note that ``roundup-gettext`` only works with trackers using TAL based
+templates. Also it does not support the markup used for deferred
+translations. Deferred translation strings have to be manually added
+to messages.pot. It also does not support hint comments.
+
+If you are using a jinja2 based template, it looks like the
+`jinja-i18n-tools`_ package might be helpful. It can be installed
+using ``pip``.
+
+After creating the ```locale`` subdirectory in my tracker home, I was
+able to use::
+
+ pybabel extract -F babel.cfg -d locale --add-comments ".Hint' \
+ --output locale/messages.po .
+
+with the following ``babel.cfg``::
+
+ [python: **.py]
+ [jinja2: html/**.html]
+ extensions=jinja2.ext.i18n
+
+Note that extracting deferred translations using a concatenated empty
+string does not seem to work.
+
+If you are successful at using local translations with your jinja2
+template based tracker, please contact the Roundup authors (see
+Contact on the main web page) so we can add more detailed directions.
+
If you are creating your own ``.po`` file from scratch rather than
using ``roundup-gettext``, you must have the minimal preamble
specifying the format of the file. This::
@@ -562,12 +567,9 @@
of the ASCII range will cause Roundup to crash with a
UnicodeDecodeError.
-.. [2] Note that it will not extract `deferred translations`_
- from detectors or extensions. Those have to be manually
- added to messages.pot.
Translating Messages
-^^^^^^^^^^^^^^^^^^^^
+~~~~~~~~~~~~~~~~~~~~
Gettext Message File (`PO`_ file) is a plain text file, that can be created
by simple copying ``roundup.pot`` to new .po file, like this::
@@ -633,6 +635,31 @@
.. [1] Roundup is written in Python and we believe in using tools in
the Python ecosystem whenever possible.
+Command Line Interfaces
+~~~~~~~~~~~~~~~~~~~~~~~
+
+Scripts and routines run from the command line use "static" language
+defined by environment variables recognized by ``gettext`` module
+from Python library (``LANGUAGE``, ``LC_ALL``, ``LC_MESSAGES``, and
+``LANG``). Primarily, these are ``roundup-admin`` script and
+``admin.py`` module, but also help texts and startup error messages
+in other scripts and their supporting modules.
+
+For these interfaces, Python ``gettext`` engine must be initialized
+to use Roundup message catalogs. This is normally done by including
+the following line in the module imports::
+
+ from i18n import _, ngettext
+
+Simple translations are automatically marked by calls to builtin
+message translation function ``_()``::
+
+ print(_("This message is translated"))
+
+Translations for messages whose grammatical depends on a number
+must be done by ``ngettext()`` function::
+
+ print(ngettext("Nuked %i file", "Nuked %i files", number_of_files_nuked))
.. _`Customising Roundup`: customizing.html
@@ -660,6 +687,7 @@
.. _hosted at sourceforge:
https://sourceforge.net/p/roundup/code/ci/default/tree/
.. _issue tracker: https://issues.roundup-tracker.org/
+.. _jinja-i18n-tools: https://pypi.org/project/jinja-i18n-tools/-i
.. _Lokalize: https://apps.kde.org/lokalize/
.. _KDE: https://kde.org/
.. _linux: https://www.linux.org/
|
|
From: Mercurial C. <th...@in...> - 2026-05-17 06:27:49
|
6 new changesets in roundup: pushed by: rouilj https://sourceforge.net/p/roundup/code/ci/3f76baa4f0cb changeset: 8637:3f76baa4f0cb user: John Rouillard <ro...@ie...> date: Wed May 13 13:08:30 2026 -0400 summary: doc: reorder sections, roundup-gettext doc; jinja2 message extraction https://sourceforge.net/p/roundup/code/ci/1bcd6a027571 changeset: 8638:1bcd6a027571 user: John Rouillard <ro...@ie...> date: Wed May 13 21:37:24 2026 -0400 summary: doc: fix example remove bad option. https://sourceforge.net/p/roundup/code/ci/9a2845390354 changeset: 8639:9a2845390354 user: John Rouillard <ro...@ie...> date: Wed May 13 21:40:51 2026 -0400 summary: doc: update link to version 3.14 from 2 https://sourceforge.net/p/roundup/code/ci/d681fce4c378 changeset: 8640:d681fce4c378 user: John Rouillard <ro...@ie...> date: Sat May 16 21:47:18 2026 -0400 summary: bug: queries triggered from query edit page missing query name https://sourceforge.net/p/roundup/code/ci/ede573cfbd7d changeset: 8641:ede573cfbd7d user: John Rouillard <ro...@ie...> date: Sat May 16 23:52:37 2026 -0400 summary: bug: don't clobber existing query with identically named new query https://sourceforge.net/p/roundup/code/ci/c355667a16bd changeset: 8642:c355667a16bd tag: tip user: John Rouillard <ro...@ie...> date: Sun May 17 02:09:22 2026 -0400 summary: bug: modernize classic template page.html -- Repository URL: https://sourceforge.net/p/roundup/code |
|
From: Mercurial C. <th...@in...> - 2026-05-12 21:11:59
|
# HG changeset patch
# User John Rouillard <ro...@ie...>
# Date 1778520662 14400
# Mon May 11 13:31:02 2026 -0400
# Node ID 1352ae565dee514bfbf2631e56ac8c047ffd9700
# Parent 75bd16f814a465b45a2e031e456666f652891a41
test: remove test log files.
The testIniFileLogger creates some log files. Make the log file names
unique so we don't delete a real log file.
Note I use an f-string for some substituition because the logging
config is meant to be processed using % style substitutions. So I
can't use % style tokens.
Also fix some whitespace stuff.
diff -r 75bd16f814a4 -r 1352ae565dee test/test_config.py
--- a/test/test_config.py Mon May 11 12:16:41 2026 -0400
+++ b/test/test_config.py Mon May 11 13:31:02 2026 -0400
@@ -499,6 +499,8 @@
backend = 'anydbm'
+ nonce = "JJxyzzy" # use a real nonce at some point
+
def setUp(self):
self.dirname = '_test_instance'
# set up and open a tracker
@@ -1578,7 +1580,7 @@
def testIniFileLoggerConfig(self):
# good base test case
- config1 = dedent("""
+ config1 = dedent(f"""
[loggers]
keys=root,roundup,roundup.http,roundup.hyperdb,actions,schema,extension,detector
@@ -1646,12 +1648,12 @@
[handler_rotate]
class=logging.handlers.RotatingFileHandler
- args=('roundup.log','a', 512000, 2)
+ args=('roundup_test-{self.nonce}.log','a', 512000, 2)
formatter=basic
[handler_rotate_weblog]
class=logging.handlers.RotatingFileHandler
- args=('httpd.log','a', 512000, 2)
+ args=('httpd_test-{self.nonce}.log','a', 512000, 2)
formatter=plain
[formatters]
@@ -1741,7 +1743,7 @@
self.assertEqual(output, expected)
self.reset_logging()
-
+
# handler = basic to handler = basi
test_config = config1.replace("handlers=basic\n", "handlers=basi\n", 1)
with open(log_config_filename, "w") as log_config_file:
@@ -1793,7 +1795,7 @@
with self.assertRaises(configparser.DuplicateOptionError) as cm:
config = self.db.config.init_logging()
-
+
# verify that logging was reset
# default log config doesn't define handlers for roundup.http
self.assertEqual(len(logging.getLogger('roundup.http').handlers), 0)
@@ -1805,7 +1807,12 @@
{"filename": log_config_filename})
)
self.reset_logging()
-
+
+ for logfile in ['roundup_test-%(nonce)s.log' % {"nonce": self.nonce},
+ 'httpd_test-%(nonce)s.log' % {"nonce": self.nonce} ]:
+ if os.path.exists(logfile):
+ os.remove(logfile)
+
def test_missing_logging_config_file(self):
saved_config = self.db.config['LOGGING_CONFIG']
|
|
From: Mercurial C. <th...@in...> - 2026-05-12 21:11:58
|
# HG changeset patch
# User John Rouillard <ro...@ie...>
# Date 1778516201 14400
# Mon May 11 12:16:41 2026 -0400
# Node ID 75bd16f814a465b45a2e031e456666f652891a41
# Parent ef5c2df157a8395cad8c332f1901d169d99e6ea4
bug: fix ResourceWarning unclosed file
diff -r ef5c2df157a8 -r 75bd16f814a4 roundup/install_util.py
--- a/roundup/install_util.py Mon May 11 10:51:40 2026 -0400
+++ b/roundup/install_util.py Mon May 11 12:16:41 2026 -0400
@@ -154,7 +154,8 @@
if file is None:
file = sys.argv[0]
- testdata = open(file, 'rb').read()
+ with open(file, 'rb') as f:
+ testdata = f.read()
for ext in digested_file_types:
testfile = "__digest_test" + ext
|
|
From: Mercurial C. <th...@in...> - 2026-05-12 21:11:57
|
# HG changeset patch
# User John Rouillard <ro...@ie...>
# Date 1778511100 14400
# Mon May 11 10:51:40 2026 -0400
# Node ID ef5c2df157a8395cad8c332f1901d169d99e6ea4
# Parent ac7e280a9783b1d34cdbd01d5000d8f775437cbe
bug: fix ResourceWarning unclosed scandir.
Even though roundup-admin import is not a long running process, fix
the ResourceWarning.
diff -r ac7e280a9783 -r ef5c2df157a8 roundup/admin.py
--- a/roundup/admin.py Mon May 11 10:47:00 2026 -0400
+++ b/roundup/admin.py Mon May 11 10:51:40 2026 -0400
@@ -1280,28 +1280,29 @@
delimiter = ':'
# import all the files
- for dir_entry in os.scandir(import_dir):
- filename = dir_entry.name
- classname, ext = os.path.splitext(filename)
- # we only care about CSV files
- if ext != '.csv' or classname.endswith('-journals'):
- continue
-
- cl = self.get_class(classname)
-
- maxid = self.import_class(dir_entry.path, colon_separated, cl,
- import_dir, import_files)
-
- # import the journals
- with open(os.path.join(import_dir, classname + '-journals.csv'), 'r') as f:
- reader = csv.reader(f, colon_separated, lineterminator='\n')
- cl.import_journals(reader)
-
- # (print to sys.stdout here to allow tests to squash it .. ugh)
- print('setting', classname, maxid + 1, file=sys.stdout)
-
- # set the id counter
- self.db.setid(classname, str(maxid + 1))
+ with os.scandir(import_dir) as dirs:
+ for dir_entry in dirs:
+ filename = dir_entry.name
+ classname, ext = os.path.splitext(filename)
+ # we only care about CSV files
+ if ext != '.csv' or classname.endswith('-journals'):
+ continue
+
+ cl = self.get_class(classname)
+
+ maxid = self.import_class(dir_entry.path, colon_separated, cl,
+ import_dir, import_files)
+
+ # import the journals
+ with open(os.path.join(import_dir, classname + '-journals.csv'), 'r') as f:
+ reader = csv.reader(f, colon_separated, lineterminator='\n')
+ cl.import_journals(reader)
+
+ # (print to sys.stdout here to allow tests to squash it .. ugh)
+ print('setting', classname, maxid + 1, file=sys.stdout)
+
+ # set the id counter
+ self.db.setid(classname, str(maxid + 1))
self.db_uncommitted = True
return 0
|
|
From: Mercurial C. <th...@in...> - 2026-05-12 21:11:55
|
# HG changeset patch
# User John Rouillard <ro...@ie...>
# Date 1778510820 14400
# Mon May 11 10:47:00 2026 -0400
# Node ID ac7e280a9783b1d34cdbd01d5000d8f775437cbe
# Parent 3b826d2a7c74813596c7597cf1c19280e0b45a68
bug: fix ResourceWarning unclosed socket.
diff -r 3b826d2a7c74 -r ac7e280a9783 test/wsgi_liveserver.py
--- a/test/wsgi_liveserver.py Mon May 11 09:57:50 2026 -0400
+++ b/test/wsgi_liveserver.py Mon May 11 10:47:00 2026 -0400
@@ -102,6 +102,7 @@
except socket.error as e:
if not hasattr(e, 'args') or e.args[0] != errno.ECONNREFUSED:
raise
+ s.close()
return port
else:
s.close()
|
|
From: Mercurial C. <th...@in...> - 2026-05-12 21:11:54
|
# HG changeset patch
# User John Rouillard <ro...@ie...>
# Date 1778507870 14400
# Mon May 11 09:57:50 2026 -0400
# Node ID 3b826d2a7c74813596c7597cf1c19280e0b45a68
# Parent e99c562b8bb496cdcb6760a1ffb969cb27c6ab0b
bug: issue2551368 - pip install gpg fails in CI - ubuntu 24.04
Version 2.0.0 of the pgp module was published by Bernhard Reiter.
Special instructions on installing a newer gpg is not needed.
Updated documentation and tests.
diff -r e99c562b8bb4 -r 3b826d2a7c74 .github/workflows/ci-test.yml
--- a/.github/workflows/ci-test.yml Sun May 10 16:39:38 2026 -0400
+++ b/.github/workflows/ci-test.yml Mon May 11 09:57:50 2026 -0400
@@ -243,15 +243,6 @@
# docutils for ReStructuredText
pip install beautifulsoup4 brotli docutils gpg jinja2 \
mistune==0.8.4 pyjwt pytz whoosh
- # gpg on PyPi is currently broken with newer OS platform
- # ubuntu 24.04
- # used for newer Python versions. Temporarily use the
- # testing index, which contains a newer version of the
- # bindings on 24.04 or released version for other OS
- # versions. See issue2551368. 'pip install gpg' should work
- # at some point when things are released to the production repo.
- #pip install --index-url https://test.pypi.org/simple/ \
- # --extra-index-url https://pypi.org/simple gpg;
- name: Install aux packages that need versions differences
# if zstd fails install, keep going with test, don't abort
diff -r e99c562b8bb4 -r 3b826d2a7c74 CHANGES.txt
--- a/CHANGES.txt Sun May 10 16:39:38 2026 -0400
+++ b/CHANGES.txt Mon May 11 09:57:50 2026 -0400
@@ -137,6 +137,9 @@
https://words.filippo.io/csrf/. (John Rouillard)
- standardize use of roundup.anypy.urllib_. Replace urlparse() with
urlsplit() (recommended) for some speedup. (John Rouillard)
+- issue2551368 - version 2.0.0 of the pgp module was published by
+ Bernhard Reiter. Updated documentation and tests. (Bernhard Reiter,
+ John Rouillard)
2025-07-13 2.5.0
diff -r e99c562b8bb4 -r 3b826d2a7c74 doc/admin_guide.txt
--- a/doc/admin_guide.txt Sun May 10 16:39:38 2026 -0400
+++ b/doc/admin_guide.txt Mon May 11 09:57:50 2026 -0400
@@ -1881,9 +1881,7 @@
.. note::
This section was written with the help of the Devin/DeepWiki AI.
-You have to install the gpg module using pip. See :ref:`directions for
-installing gpg <gpginstall>`
-in the upgrading document for more information.
+You have to install the gpg module using pip (``pip install gpg``).
In your tracker's config.ini configure the following settings in the
``[pgp]`` section::
diff -r e99c562b8bb4 -r 3b826d2a7c74 doc/installation.txt
--- a/doc/installation.txt Sun May 10 16:39:38 2026 -0400
+++ b/doc/installation.txt Mon May 11 09:57:50 2026 -0400
@@ -248,10 +248,7 @@
If gpg_ is installed you can configure the mail gateway to perform
verification or decryption of incoming OpenPGP MIME messages. When
configured, you can require email to be cryptographically signed
- before roundup will allow it to make modifications to issues. (Note
- with python 3.13 or newer on some platforms, you may need to install
- version 2.0 of gpg from test.pypi.org. See the `gpg install
- directions in the upgrading document`_.
+ before Roundup will allow it to make modifications to issues.
nanoid
If nanoid_ is installed, it is used to generate short unique
diff -r e99c562b8bb4 -r 3b826d2a7c74 doc/upgrading.txt
--- a/doc/upgrading.txt Sun May 10 16:39:38 2026 -0400
+++ b/doc/upgrading.txt Mon May 11 09:57:50 2026 -0400
@@ -58,6 +58,7 @@
* **optional** - new features or changes to existing features you might
want to use
* **info** - important possibly visible changes in how things operate
+* **obsolete** - ignore these entries. They are no longer required.
If you use virtual environments for your installation, you
can run trackers with different versions of Roundup. So you
@@ -978,9 +979,15 @@
.. _gpginstall:
-Directions for installing gpg (optional)
+Directions for installing gpg (obsolete)
----------------------------------------
+.. note::
+ issue2551368 was closed in May 2026. Running ``pip install
+ gpg`` will install gpg 2.0.0 on all versions of Python supported by
+ Roundup. If you installed the test version use ``pip install -U
+ gpg`` to upgrade.
+
In this release a new version of the gpg module was needed for Ubuntu
24.04 and python 3.13. Paul Schwabauer produced a new version of the
gpg module. However it is only on the test instance of pypi. If you
|
|
From: Mercurial C. <th...@in...> - 2026-05-12 21:11:52
|
5 new changesets in roundup: pushed by: rouilj https://sourceforge.net/p/roundup/code/ci/3b826d2a7c74 changeset: 8632:3b826d2a7c74 user: John Rouillard <ro...@ie...> date: Mon May 11 09:57:50 2026 -0400 summary: bug: issue2551368 - pip install gpg fails in CI - ubuntu 24.04 https://sourceforge.net/p/roundup/code/ci/ac7e280a9783 changeset: 8633:ac7e280a9783 user: John Rouillard <ro...@ie...> date: Mon May 11 10:47:00 2026 -0400 summary: bug: fix ResourceWarning unclosed socket. https://sourceforge.net/p/roundup/code/ci/ef5c2df157a8 changeset: 8634:ef5c2df157a8 user: John Rouillard <ro...@ie...> date: Mon May 11 10:51:40 2026 -0400 summary: bug: fix ResourceWarning unclosed scandir. https://sourceforge.net/p/roundup/code/ci/75bd16f814a4 changeset: 8635:75bd16f814a4 user: John Rouillard <ro...@ie...> date: Mon May 11 12:16:41 2026 -0400 summary: bug: fix ResourceWarning unclosed file https://sourceforge.net/p/roundup/code/ci/1352ae565dee changeset: 8636:1352ae565dee tag: tip user: John Rouillard <ro...@ie...> date: Mon May 11 13:31:02 2026 -0400 summary: test: remove test log files. -- Repository URL: https://sourceforge.net/p/roundup/code |
|
From: Mercurial C. <th...@in...> - 2026-05-11 01:41:32
|
# HG changeset patch
# User John Rouillard <ro...@ie...>
# Date 1778445578 14400
# Sun May 10 16:39:38 2026 -0400
# Node ID e99c562b8bb496cdcb6760a1ffb969cb27c6ab0b
# Parent 9311bb32ddf023ce879f53be7051363931b21718
issue2551368 - test gpg-2.0.0
Bern updated the issue to report gpg 2.0.0 should be available for
install from pypi.
Test that.
diff -r 9311bb32ddf0 -r e99c562b8bb4 .github/workflows/ci-test.yml
--- a/.github/workflows/ci-test.yml Thu May 07 22:11:20 2026 -0400
+++ b/.github/workflows/ci-test.yml Sun May 10 16:39:38 2026 -0400
@@ -241,7 +241,7 @@
# pygments for markdown2 to highlight code blocks
pip install markdown2 pygments
# docutils for ReStructuredText
- pip install beautifulsoup4 brotli docutils jinja2 \
+ pip install beautifulsoup4 brotli docutils gpg jinja2 \
mistune==0.8.4 pyjwt pytz whoosh
# gpg on PyPi is currently broken with newer OS platform
# ubuntu 24.04
@@ -250,8 +250,8 @@
# bindings on 24.04 or released version for other OS
# versions. See issue2551368. 'pip install gpg' should work
# at some point when things are released to the production repo.
- pip install --index-url https://test.pypi.org/simple/ \
- --extra-index-url https://pypi.org/simple gpg;
+ #pip install --index-url https://test.pypi.org/simple/ \
+ # --extra-index-url https://pypi.org/simple gpg;
- name: Install aux packages that need versions differences
# if zstd fails install, keep going with test, don't abort
|