Mercurial > public > madeira
changeset 1:0dcfcdf50c62
Initial import of Madeira project from the private repository.
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/media/css/theme.css Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,284 @@ +body { + font-family: arial, helvetica, sans-serif; + color: #EF7D21; + background-color: #213442; +} + +.centered { text-align: center; } + +A:link { + color: #6B969C; + text-decoration: none; +} + +A:visited { + color: #6B969C; + text-decoration: none; +} + +A:hover { + text-decoration: underline; +} + +h1 { + font: italic 32px georgia, 'times new roman', sans-serif; + margin: 0px; + padding: 0 0 10px 0; +} + +.headline1 { + font: italic 32px georgia, 'times new roman', sans-serif; + margin: 0px; + padding: 0 0 10px 0; +} + +h2 { + font: italic 20px verdana, tahoma, arial, sans-serif; + padding-top: 5px; + color: #6B969C; + border-top: solid 1px #6B969C; +} + +div.madeira-photo-list { + margin: 2em; + text-align: center; +} + +div.madeira-photo-list img { + border: thin dotted #6B969C; + padding: 2px; + margin: 5px; +} + +a.img-link:link { text-decoration: none; background-color: transparent; } +a.img-link:visited { text-decoration: none; background-color: transparent; } +a.img-link:hover { text-decoration: none; background-color: transparent; } + +.header { + margin-top: 10px; + margin-left: 5px; +} + +.content { + margin: 20px 1em 1em 150px; + max-width: 840px; + z-index: 3; + padding: 5px; +} + +.photobkgnd { + margin: 3em; + z-index: 3; + background-color: #888888; + padding: 5px; +} + +.newsflash { + margin: 20px 1em; + border: 3px solid #6B969C; + padding: 5px; + background-color: #2e414f; + color: #EF7D21; +} + +/* +** f'in IE doesn't seem to honor my padding and the float doesn't work. +** solution: make an invisible border...lame. +** It's fine in Firefox. +*/ +.floatLeftBox { + /* padding: 0 1em 1em 0; I wish I could just use this */ + float: left; + border-right: 0.5em solid #213442; + border-bottom: 0.5em solid #213442; +} + +.clearMe { + clear: left; +} + +.cdCoverFloatList { + margin-left: 260px; + text-align: left; +} + +/* For internal links (within the same page) */ +.intLink { + margin-top: 2em; + font-size: small; +} + +label.normal { + width: 9em; + float: left; + text-align: right; + margin: 0 1em 10px 0; + clear: both; + font-weight: bold; +} + +.form-box { + color: black; + background: #ccc; + border-top: black solid 2px; + border-left: black solid 2px; + border-bottom: #eee solid 2px; + border-right: #eee solid 2px; + margin-bottom: 5px; +} + +.form-radio { + margin-left: 10em; +} + +.form-comment { + font-style: italic; + font-size: x-small; + margin-left: 10em; + margin-bottom: 10px; +} + +.submit-button { + color: black; + background: #6B969C; + border: 2px outset; + padding: 3px; +} + +table.input-form th { + vertical-align: top; +} + +ul.errorlist { + margin: 0 0 0 1em; + padding: 2px; +} +.errorlist li { + color: red; +} + +fieldset { + border: 1px solid teal; + margin-top: 2em; +} + +legend { + background: #6B969C; + color: black; + border: 1px solid black; + padding: 2px 12px 2px 12px; +} + +#footer { + clear: both; + list-style-type: none; + margin-top: 30px; + padding-top: 1em; + border-top: solid 1px #6B969C; + font-size: small; + font-style: italic; + text-align: right; +} + +#navleft { + position: absolute; + top: 343px; + left: 10px; + width: 130px; + height: auto; +} + +#navleft ul { + margin: 0 0 0 0; + padding: 0 0 0 0; + font: 12px verdana, tahoma, arial, sans-serif; + font: bold 15px georgia, 'times new roman', sans-serif; + list-style-type: none; + display: inline; +} + +#navleft li { + margin: 0px 0 0; + display: inline; +} + +#navleft a { + display: block; + width: 118px; + padding: 2px 2px 2px 10px; + border: 1px solid #000000; + /* background: #EF7D21; */ + /* color: #213442; */ + background: #6B969C; + color: #EF7D21; + text-decoration: none; +} + +#navleft a:link, #navleft a:active, #navleft a:visited { + color: #213442; +} + +#navleft a:hover { + border: 1px solid #000000; + background: #213442; + background: #EF7D21; + color: #EF7D21; + background: #213442 url(../images/brick.gif); + color: #ffffff; + color: #6B969C; + color: #213442; +} + +div.article-source { + font-style: italic; + padding-bottom: 2px; + margin-bottom: 5px; +} + +div.center-block { + margin: 10px 30px 10px 30px; + border: 1px solid #6B969C; + padding: 3px; +} + +div.center-block h2 { + font: italic 20px verdana, tahoma, arial, sans-serif; + padding-top: 1px; + margin-top: 1px; + color: #6B969C; + border-top: none; + text-align: center; +} + +div.thumb-box { + margin: 20px 1em; + border: 3px solid #6B969C; + padding: 5px; + background-color: #2e414f; + color: #EF7D21; +} + +div.thumb-box img { + border: thin dotted #6B969C; + padding: 2px; + margin: 5px; +} + +div.thumb-box h2 { + font: italic 20px verdana, tahoma, arial, sans-serif; + padding-top: 1px; + margin-top: 1px; + color: #6B969C; + border-top: none; + text-align: center; +} + +table.image-table { + float: left; + margin: 0 5px 20px 0; +} + +table.image-table caption { + font-size: x-small; + caption-side: top; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/media/css/thickbox.css Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,163 @@ +/* ----------------------------------------------------------------------------------------------------------------*/ +/* ---------->>> global settings needed for thickbox <<<-----------------------------------------------------------*/ +/* ----------------------------------------------------------------------------------------------------------------*/ +/* *{padding: 0; margin: 0;} */ + +/* ----------------------------------------------------------------------------------------------------------------*/ +/* ---------->>> thickbox specific link and font settings <<<------------------------------------------------------*/ +/* ----------------------------------------------------------------------------------------------------------------*/ +#TB_window { + font: 12px Arial, Helvetica, sans-serif; + color: #333333; +} + +#TB_secondLine { + font: 10px Arial, Helvetica, sans-serif; + color:#666666; +} + +#TB_window a:link {color: #666666;} +#TB_window a:visited {color: #666666;} +#TB_window a:hover {color: #000;} +#TB_window a:active {color: #666666;} +#TB_window a:focus{color: #666666;} + +/* ----------------------------------------------------------------------------------------------------------------*/ +/* ---------->>> thickbox settings <<<-----------------------------------------------------------------------------*/ +/* ----------------------------------------------------------------------------------------------------------------*/ +#TB_overlay { + position: fixed; + z-index:100; + top: 0px; + left: 0px; + height:100%; + width:100%; +} + +.TB_overlayMacFFBGHack {background: url(macFFBgHack.png) repeat;} +.TB_overlayBG { + background-color:#000; + filter:alpha(opacity=75); + -moz-opacity: 0.75; + opacity: 0.75; +} + +* html #TB_overlay { /* ie6 hack */ + position: absolute; + height: expression(document.body.scrollHeight > document.body.offsetHeight ? document.body.scrollHeight : document.body.offsetHeight + 'px'); +} + +#TB_window { + position: fixed; + background: #ffffff; + z-index: 102; + color:#000000; + display:none; + border: 4px solid #525252; + text-align:left; + top:50%; + left:50%; +} + +* html #TB_window { /* ie6 hack */ +position: absolute; +margin-top: expression(0 - parseInt(this.offsetHeight / 2) + (TBWindowMargin = document.documentElement && document.documentElement.scrollTop || document.body.scrollTop) + 'px'); +} + +#TB_window img#TB_Image { + display:block; + margin: 15px 0 0 15px; + border-right: 1px solid #ccc; + border-bottom: 1px solid #ccc; + border-top: 1px solid #666; + border-left: 1px solid #666; +} + +#TB_caption{ + height:25px; + padding:7px 30px 10px 25px; + float:left; +} + +#TB_closeWindow{ + height:25px; + padding:11px 25px 10px 0; + float:right; +} + +#TB_closeAjaxWindow{ + padding:7px 10px 5px 0; + margin-bottom:1px; + text-align:right; + float:right; +} + +#TB_ajaxWindowTitle{ + float:left; + padding:7px 0 5px 10px; + margin-bottom:1px; +} + +#TB_title{ + background-color:#e8e8e8; + height:27px; +} + +#TB_ajaxContent{ + clear:both; + padding:2px 15px 15px 15px; + overflow:auto; + text-align:left; + line-height:1.4em; +} + +#TB_ajaxContent.TB_modal{ + padding:15px; +} + +#TB_ajaxContent p{ + padding:5px 0px 5px 0px; +} + +#TB_load{ + position: fixed; + display:none; + height:13px; + width:208px; + z-index:103; + top: 50%; + left: 50%; + margin: -6px 0 0 -104px; /* -height/2 0 0 -width/2 */ +} + +* html #TB_load { /* ie6 hack */ +position: absolute; +margin-top: expression(0 - parseInt(this.offsetHeight / 2) + (TBWindowMargin = document.documentElement && document.documentElement.scrollTop || document.body.scrollTop) + 'px'); +} + +#TB_HideSelect{ + z-index:99; + position:fixed; + top: 0; + left: 0; + background-color:#fff; + border:none; + filter:alpha(opacity=0); + -moz-opacity: 0; + opacity: 0; + height:100%; + width:100%; +} + +* html #TB_HideSelect { /* ie6 hack */ + position: absolute; + height: expression(document.body.scrollHeight > document.body.offsetHeight ? document.body.scrollHeight : document.body.offsetHeight + 'px'); +} + +#TB_iframeContent{ + clear:both; + border:none; + margin-bottom:-1px; + margin-top:1px; + _margin-bottom:1px; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/media/django/css/base.css Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,746 @@ +/* + DJANGO Admin styles +*/ + +body { + margin: 0; + padding: 0; + font-size: 12px; + font-family: "Lucida Grande","DejaVu Sans","Bitstream Vera Sans",Verdana,Arial,sans-serif; + color: #333; + background: #fff; +} + +/* LINKS */ + +a:link, a:visited { + color: #5b80b2; + text-decoration: none; +} + +a:hover { + color: #036; +} + +a img { + border: none; +} + +a.section:link, a.section:visited { + color: white; + text-decoration: none; +} + +/* GLOBAL DEFAULTS */ + +p, ol, ul, dl { + margin: .2em 0 .8em 0; +} + +p { + padding: 0; + line-height: 140%; +} + +h1,h2,h3,h4,h5 { + font-weight: bold; +} + +h1 { + font-size: 18px; + color: #666; + padding: 0 6px 0 0; + margin: 0 0 .2em 0; +} + +h2 { + font-size: 16px; + margin: 1em 0 .5em 0; +} + +h2.subhead { + font-weight: normal; + margin-top: 0; +} + +h3 { + font-size: 14px; + margin: .8em 0 .3em 0; + color: #666; + font-weight: bold; +} + +h4 { + font-size: 12px; + margin: 1em 0 .8em 0; + padding-bottom: 3px; +} + +h5 { + font-size: 10px; + margin: 1.5em 0 .5em 0; + color: #666; + text-transform: uppercase; + letter-spacing: 1px; +} + +ul li { + list-style-type: square; + padding: 1px 0; +} + +ul.plainlist { + margin-left: 0 !important; +} + +ul.plainlist li { + list-style-type: none; +} + +li ul { + margin-bottom: 0; +} + +li, dt, dd { + font-size: 11px; + line-height: 14px; +} + +dt { + font-weight: bold; + margin-top: 4px; +} + +dd { + margin-left: 0; +} + +form { + margin: 0; + padding: 0; +} + +fieldset { + margin: 0; + padding: 0; +} + +blockquote { + font-size: 11px; + color: #777; + margin-left: 2px; + padding-left: 10px; + border-left: 5px solid #ddd; +} + +code, pre { + font-family: "Bitstream Vera Sans Mono", Monaco, "Courier New", Courier, monospace; + background: inherit; + color: #666; + font-size: 11px; +} + +pre.literal-block { + margin: 10px; + background: #eee; + padding: 6px 8px; +} + +code strong { + color: #930; +} + +hr { + clear: both; + color: #eee; + background-color: #eee; + height: 1px; + border: none; + margin: 0; + padding: 0; + font-size: 1px; + line-height: 1px; +} + +/* TEXT STYLES & MODIFIERS */ + +.small { + font-size: 11px; +} + +.tiny { + font-size: 10px; +} + +p.tiny { + margin-top: -2px; +} + +.mini { + font-size: 9px; +} + +p.mini { + margin-top: -3px; +} + +.help, p.help { + font-size: 10px !important; + color: #999; +} + +p img, h1 img, h2 img, h3 img, h4 img, td img { + vertical-align: middle; +} + +.quiet, a.quiet:link, a.quiet:visited { + color: #999 !important; + font-weight: normal !important; +} + +.quiet strong { + font-weight: bold !important; +} + +.float-right { + float: right; +} + +.float-left { + float: left; +} + +.clear { + clear: both; +} + +.align-left { + text-align: left; +} + +.align-right { + text-align: right; +} + +.example { + margin: 10px 0; + padding: 5px 10px; + background: #efefef; +} + +.nowrap { + white-space: nowrap; +} + +/* TABLES */ + +table { + border-collapse: collapse; + border-color: #ccc; +} + +td, th { + font-size: 11px; + line-height: 13px; + border-bottom: 1px solid #eee; + vertical-align: top; + padding: 5px; + font-family: "Lucida Grande", Verdana, Arial, sans-serif; +} + +th { + text-align: left; + font-size: 12px; + font-weight: bold; +} + +thead th, +tfoot td { + color: #666; + padding: 2px 5px; + font-size: 11px; + background: #e1e1e1 url(../img/admin/nav-bg.gif) top left repeat-x; + border-left: 1px solid #ddd; + border-bottom: 1px solid #ddd; +} + +tfoot td { + border-bottom: none; + border-top: 1px solid #ddd; +} + +thead th:first-child, +tfoot td:first-child { + border-left: none !important; +} + +thead th.optional { + font-weight: normal !important; +} + +fieldset table { + border-right: 1px solid #eee; +} + +tr.row-label td { + font-size: 9px; + padding-top: 2px; + padding-bottom: 0; + border-bottom: none; + color: #666; + margin-top: -1px; +} + +tr.alt { + background: #f6f6f6; +} + +.row1 { + background: #EDF3FE; +} + +.row2 { + background: white; +} + +/* SORTABLE TABLES */ + +thead th a:link, thead th a:visited { + color: #666; + display: block; +} + +table thead th.sorted { + background-position: bottom left !important; +} + +table thead th.sorted a { + padding-right: 13px; +} + +table thead th.ascending a { + background: url(../img/admin/arrow-down.gif) right .4em no-repeat; +} + +table thead th.descending a { + background: url(../img/admin/arrow-up.gif) right .4em no-repeat; +} + +/* ORDERABLE TABLES */ + +table.orderable tbody tr td:hover { + cursor: move; +} + +table.orderable tbody tr td:first-child { + padding-left: 14px; + background-image: url(../img/admin/nav-bg-grabber.gif); + background-repeat: repeat-y; +} + +table.orderable-initalized .order-cell, body>tr>td.order-cell { + display: none; +} + +/* FORM DEFAULTS */ + +input, textarea, select { + margin: 2px 0; + padding: 2px 3px; + vertical-align: middle; + font-family: "Lucida Grande", Verdana, Arial, sans-serif; + font-weight: normal; + font-size: 11px; +} + +textarea { + vertical-align: top !important; +} + +input[type=text], input[type=password], textarea, select, .vTextField { + border: 1px solid #ccc; +} + +/* FORM BUTTONS */ + +.button, input[type=submit], input[type=button], .submit-row input { + background: white url(../img/admin/nav-bg.gif) bottom repeat-x; + padding: 3px; + color: black; + border: 1px solid #bbb; + border-color: #ddd #aaa #aaa #ddd; +} + +.button:active, input[type=submit]:active, input[type=button]:active { + background-image: url(../img/admin/nav-bg-reverse.gif); + background-position: top; +} + +.button.default, input[type=submit].default, .submit-row input.default { + border: 2px solid #5b80b2; + background: #7CA0C7 url(../img/admin/default-bg.gif) bottom repeat-x; + font-weight: bold; + color: white; + float: right; +} + +.button.default:active, input[type=submit].default:active { + background-image: url(../img/admin/default-bg-reverse.gif); + background-position: top; +} + +/* MODULES */ + +.module { + border: 1px solid #ccc; + margin-bottom: 5px; + background: white; +} + +.module p, .module ul, .module h3, .module h4, .module dl, .module pre { + padding-left: 10px; + padding-right: 10px; +} + +.module blockquote { + margin-left: 12px; +} + +.module ul, .module ol { + margin-left: 1.5em; +} + +.module h3 { + margin-top: .6em; +} + +.module h2, .module caption, .inline-group h2 { + margin: 0; + padding: 2px 5px 3px 5px; + font-size: 11px; + text-align: left; + font-weight: bold; + background: #7CA0C7 url(../img/admin/default-bg.gif) top left repeat-x; + color: white; +} + +.module table { + border-collapse: collapse; +} + +/* MESSAGES & ERRORS */ + +ul.messagelist { + padding: 0 0 5px 0; + margin: 0; +} + +ul.messagelist li { + font-size: 12px; + display: block; + padding: 4px 5px 4px 25px; + margin: 0 0 3px 0; + border-bottom: 1px solid #ddd; + color: #666; + background: #ffc url(../img/admin/icon_success.gif) 5px .3em no-repeat; +} + +.errornote { + font-size: 12px !important; + display: block; + padding: 4px 5px 4px 25px; + margin: 0 0 3px 0; + border: 1px solid red; + color: red; + background: #ffc url(../img/admin/icon_error.gif) 5px .3em no-repeat; +} + +ul.errorlist { + margin: 0 !important; + padding: 0 !important; +} + +.errorlist li { + font-size: 12px !important; + display: block; + padding: 4px 5px 4px 25px; + margin: 0 0 3px 0; + border: 1px solid red; + color: white; + background: red url(../img/admin/icon_alert.gif) 5px .3em no-repeat; +} + +td ul.errorlist { + margin: 0 !important; + padding: 0 !important; +} + +td ul.errorlist li { + margin: 0 !important; +} + +.errors { + background: #ffc; +} + +.errors input, .errors select { + border: 1px solid red; +} + +div.system-message { + background: #ffc; + margin: 10px; + padding: 6px 8px; + font-size: .8em; +} + +div.system-message p.system-message-title { + padding: 4px 5px 4px 25px; + margin: 0; + color: red; + background: #ffc url(../img/admin/icon_error.gif) 5px .3em no-repeat; +} + +.description { + font-size: 12px; + padding: 5px 0 0 12px; +} + +/* BREADCRUMBS */ + +div.breadcrumbs { + background: white url(../img/admin/nav-bg-reverse.gif) 0 -10px repeat-x; + padding: 2px 8px 3px 8px; + font-size: 11px; + color: #999; + border-top: 1px solid white; + border-bottom: 1px solid #ccc; + text-align: left; +} + +/* ACTION ICONS */ + +.addlink { + padding-left: 12px; + background: url(../img/admin/icon_addlink.gif) 0 .2em no-repeat; +} + +.changelink { + padding-left: 12px; + background: url(../img/admin/icon_changelink.gif) 0 .2em no-repeat; +} + +.deletelink { + padding-left: 12px; + background: url(../img/admin/icon_deletelink.gif) 0 .25em no-repeat; +} + +a.deletelink:link, a.deletelink:visited { + color: #CC3434; +} + +a.deletelink:hover { + color: #993333; +} + +/* OBJECT TOOLS */ + +.object-tools { + font-size: 10px; + font-weight: bold; + font-family: Arial,Helvetica,sans-serif; + padding-left: 0; + float: right; + position: relative; + margin-top: -2.4em; + margin-bottom: -2em; +} + +.form-row .object-tools { + margin-top: 5px; + margin-bottom: 5px; + float: none; + height: 2em; + padding-left: 3.5em; +} + +.object-tools li { + display: block; + float: left; + background: url(../img/admin/tool-left.gif) 0 0 no-repeat; + padding: 0 0 0 8px; + margin-left: 2px; + height: 16px; +} + +.object-tools li:hover { + background: url(../img/admin/tool-left_over.gif) 0 0 no-repeat; +} + +.object-tools a:link, .object-tools a:visited { + display: block; + float: left; + color: white; + padding: .1em 14px .1em 8px; + height: 14px; + background: #999 url(../img/admin/tool-right.gif) 100% 0 no-repeat; +} + +.object-tools a:hover, .object-tools li:hover a { + background: #5b80b2 url(../img/admin/tool-right_over.gif) 100% 0 no-repeat; +} + +.object-tools a.viewsitelink, .object-tools a.golink { + background: #999 url(../img/admin/tooltag-arrowright.gif) top right no-repeat; + padding-right: 28px; +} + +.object-tools a.viewsitelink:hover, .object-tools a.golink:hover { + background: #5b80b2 url(../img/admin/tooltag-arrowright_over.gif) top right no-repeat; +} + +.object-tools a.addlink { + background: #999 url(../img/admin/tooltag-add.gif) top right no-repeat; + padding-right: 28px; +} + +.object-tools a.addlink:hover { + background: #5b80b2 url(../img/admin/tooltag-add_over.gif) top right no-repeat; +} + +/* OBJECT HISTORY */ + +table#change-history { + width: 100%; +} + +table#change-history tbody th { + width: 16em; +} + +/* PAGE STRUCTURE */ + +#container { + position: relative; + width: 100%; + min-width: 760px; + padding: 0; +} + +#content { + margin: 10px 15px; +} + +#header { + width: 100%; +} + +#content-main { + float: left; + width: 100%; +} + +#content-related { + float: right; + width: 18em; + position: relative; + margin-right: -19em; +} + +#footer { + clear: both; + padding: 10px; +} + +/* COLUMN TYPES */ + +.colMS { + margin-right: 20em !important; +} + +.colSM { + margin-left: 20em !important; +} + +.colSM #content-related { + float: left; + margin-right: 0; + margin-left: -19em; +} + +.colSM #content-main { + float: right; +} + +.popup .colM { + width: 95%; +} + +.subcol { + float: left; + width: 46%; + margin-right: 15px; +} + +.dashboard #content { + width: 500px; +} + +/* HEADER */ + +#header { + background: #417690; + color: #ffc; + overflow: hidden; +} + +#header a:link, #header a:visited { + color: white; +} + +#header a:hover { + text-decoration: underline; +} + +#branding h1 { + padding: 0 10px; + font-size: 18px; + margin: 8px 0; + font-weight: normal; + color: #f4f379; +} + +#branding h2 { + padding: 0 10px; + font-size: 14px; + margin: -8px 0 8px 0; + font-weight: normal; + color: #ffc; +} + +#user-tools { + position: absolute; + top: 0; + right: 0; + padding: 1.2em 10px; + font-size: 11px; + text-align: right; +} + +/* SIDEBAR */ + +#content-related h3 { + font-size: 12px; + color: #666; + margin-bottom: 3px; +} + +#content-related h4 { + font-size: 11px; +} + +#content-related .module h2 { + background: #eee url(../img/admin/nav-bg.gif) bottom left repeat-x; + color: #666; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/media/django/css/changelists.css Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,211 @@ +/* CHANGELISTS */ + +#changelist { + position: relative; + width: 100%; +} + +#changelist table { + width: 100%; +} + +.change-list .filtered table { + border-right: 1px solid #ddd; +} + +.change-list .filtered { + min-height: 400px; +} + +.change-list .filtered { + background: white url(../img/admin/changelist-bg.gif) top right repeat-y !important; +} + +.change-list .filtered table, .change-list .filtered .paginator, .filtered #toolbar, .filtered div.xfull { + margin-right: 160px !important; + width: auto !important; +} + +.change-list .filtered table tbody th { + padding-right: 1em; +} + +#changelist .toplinks { + border-bottom: 1px solid #ccc !important; +} + +#changelist .paginator { + color: #666; + border-top: 1px solid #eee; + border-bottom: 1px solid #eee; + background: white url(../img/admin/nav-bg.gif) 0 180% repeat-x; + overflow: hidden; +} + +.change-list .filtered .paginator { + border-right: 1px solid #ddd; +} + +/* CHANGELIST TABLES */ + +#changelist table thead th { + white-space: nowrap; +} + +#changelist table tbody td { + border-left: 1px solid #ddd; +} + +#changelist table tfoot { + color: #666; +} + +/* TOOLBAR */ + +#changelist #toolbar { + padding: 3px; + border-bottom: 1px solid #ddd; + background: #e1e1e1 url(../img/admin/nav-bg.gif) top left repeat-x; + color: #666; +} + +#changelist #toolbar form input { + font-size: 11px; + padding: 1px 2px; +} + +#changelist #toolbar form #searchbar { + padding: 2px; +} + +#changelist #changelist-search img { + vertical-align: middle; +} + +/* FILTER COLUMN */ + +#changelist-filter { + position: absolute; + top: 0; + right: 0; + z-index: 1000; + width: 160px; + border-left: 1px solid #ddd; + background: #efefef; + margin: 0; +} + +#changelist-filter h2 { + font-size: 11px; + padding: 2px 5px; + border-bottom: 1px solid #ddd; +} + +#changelist-filter h3 { + font-size: 12px; + margin-bottom: 0; +} + +#changelist-filter ul { + padding-left: 0; + margin-left: 10px; +} + +#changelist-filter li { + list-style-type: none; + margin-left: 0; + padding-left: 0; +} + +#changelist-filter a { + color: #999; +} + +#changelist-filter a:hover { + color: #036; +} + +#changelist-filter li.selected { + border-left: 5px solid #ccc; + padding-left: 5px; + margin-left: -10px; +} + +#changelist-filter li.selected a { + color: #5b80b2 !important; +} + +/* DATE DRILLDOWN */ + +.change-list ul.toplinks { + display: block; + background: white url(../img/admin/nav-bg-reverse.gif) 0 -10px repeat-x; + border-top: 1px solid white; + float: left; + padding: 0 !important; + margin: 0 !important; + width: 100%; +} + +.change-list ul.toplinks li { + float: left; + width: 9em; + padding: 3px 6px; + font-weight: bold; + list-style-type: none; +} + +.change-list ul.toplinks .date-back a { + color: #999; +} + +.change-list ul.toplinks .date-back a:hover { + color: #036; +} + +/* PAGINATOR */ + +.paginator { + font-size: 11px; + padding-top: 10px; + padding-bottom: 10px; + line-height: 22px; + margin: 0; + border-top: 1px solid #ddd; +} + +.paginator a:link, .paginator a:visited { + padding: 2px 6px; + border: solid 1px #ccc; + background: white; + text-decoration: none; +} + +.paginator a.showall { + padding: 0 !important; + border: none !important; +} + +.paginator a.showall:hover { + color: #036 !important; + background: transparent !important; +} + +.paginator .end { + border-width: 2px !important; + margin-right: 6px; +} + +.paginator .this-page { + padding: 2px 6px; + font-weight: bold; + font-size: 13px; + vertical-align: top; +} + +.paginator a:hover { + color: white; + background: #5b80b2; + border-color: #036; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/media/django/css/dashboard.css Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,24 @@ +/* DASHBOARD */ + +.dashboard .module table th { + width: 100%; +} + +.dashboard .module table td { + white-space: nowrap; +} + +.dashboard .module table td a { + display: block; + padding-right: .6em; +} + +/* RECENT ACTIONS MODULE */ + +.module ul.actionlist { + margin-left: 0; +} + +ul.actionlist li { + list-style-type: none; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/media/django/css/forms.css Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,327 @@ +@import url('widgets.css'); + +/* FORM ROWS */ + +.form-row { + overflow: hidden; + padding: 8px 12px; + font-size: 11px; + border-bottom: 1px solid #eee; +} + +.form-row img, .form-row input { + vertical-align: middle; +} + +form .form-row p { + padding-left: 0; + font-size: 11px; +} + +/* FORM LABELS */ + +form h4 { + margin: 0 !important; + padding: 0 !important; + border: none !important; +} + +label { + font-weight: normal !important; + color: #666; + font-size: 12px; +} + +.required label, label.required { + font-weight: bold !important; + color: #333 !important; +} + +/* RADIO BUTTONS */ + +form ul.radiolist li { + list-style-type: none; +} + +form ul.radiolist label { + float: none; + display: inline; +} + +form ul.inline { + margin-left: 0; + padding: 0; +} + +form ul.inline li { + float: left; + padding-right: 7px; +} + +/* ALIGNED FIELDSETS */ + +.aligned label { + display: block; + padding: 3px 10px 0 0; + float: left; + width: 8em; +} + +.colMS .aligned .vLargeTextField, .colMS .aligned .vXMLLargeTextField { + width: 350px; +} + +form .aligned p, form .aligned ul { + margin-left: 7em; + padding-left: 30px; +} + +form .aligned table p { + margin-left: 0; + padding-left: 0; +} + +form .aligned p.help { + padding-left: 38px; +} + +.aligned .vCheckboxLabel { + float: none !important; + display: inline; + padding-left: 4px; +} + +.colM .aligned .vLargeTextField, .colM .aligned .vXMLLargeTextField { + width: 610px; +} + +.checkbox-row p.help { + margin-left: 0; + padding-left: 0 !important; +} + +fieldset .field-box { + float: left; + margin-right: 20px; +} + +/* WIDE FIELDSETS */ + +.wide label { + width: 15em !important; +} + +form .wide p { + margin-left: 15em; +} + +form .wide p.help { + padding-left: 38px; +} + +.colM fieldset.wide .vLargeTextField, .colM fieldset.wide .vXMLLargeTextField { + width: 450px; +} + +/* COLLAPSED FIELDSETS */ + +fieldset.collapsed * { + display: none; +} + +fieldset.collapsed h2, fieldset.collapsed { + display: block !important; +} + +fieldset.collapsed h2 { + background-image: url(../img/admin/nav-bg.gif); + background-position: bottom left; + color: #999; +} + +fieldset.collapsed .collapse-toggle { + padding: 3px 5px !important; + background: transparent; + display: inline !important; +} + +/* MONOSPACE TEXTAREAS */ + +fieldset.monospace textarea { + font-family: "Bitstream Vera Sans Mono",Monaco,"Courier New",Courier,monospace; +} + +/* SUBMIT ROW */ + +.submit-row { + padding: 5px 7px; + text-align: right; + background: white url(../img/admin/nav-bg.gif) 0 100% repeat-x; + border: 1px solid #ccc; + margin: 5px 0; + overflow: hidden; +} + +.submit-row input { + margin: 0 0 0 5px; +} + +.submit-row p { + margin: 0.3em; +} + +.submit-row p.deletelink-box { + float: left; +} + +.submit-row .deletelink { + background: url(../img/admin/icon_deletelink.gif) 0 50% no-repeat; + padding-left: 14px; +} + +/* CUSTOM FORM FIELDS */ + +.vSelectMultipleField { + vertical-align: top !important; +} + +.vCheckboxField { + border: none; +} + +.vDateField, .vTimeField { + margin-right: 2px; +} + +.vURLField { + width: 30em; +} + +.vLargeTextField, .vXMLLargeTextField { + width: 48em; +} + +.flatpages-flatpage #id_content { + height: 40.2em; +} + +.module table .vPositiveSmallIntegerField { + width: 2.2em; +} + +.vTextField { + width: 20em; +} + +.vIntegerField { + width: 5em; +} + +.vForeignKeyRawIdAdminField { + width: 5em; +} + +/* INLINES */ + +.inline-group { + padding: 0; + border: 1px solid #ccc; + margin: 10px 0; +} + +.inline-group .aligned label { + width: 8em; +} + +.inline-related { + position: relative; +} + +.inline-related h3 { + margin: 0; + color: #666; + padding: 3px 5px; + font-size: 11px; + background: #e1e1e1 url(../img/admin/nav-bg.gif) top left repeat-x; + border-bottom: 1px solid #ddd; +} + +.inline-related h3 span.delete { + padding-left: 20px; + position: absolute; + top: 2px; + right: 10px; +} + +.inline-related h3 span.delete label { + margin-left: 2px; + font-size: 11px; +} + +.inline-related fieldset { + margin: 0; + background: #fff; + border: none; +} + +.inline-related fieldset.module h3 { + margin: 0; + padding: 2px 5px 3px 5px; + font-size: 11px; + text-align: left; + font-weight: bold; + background: #bcd; + color: #fff; +} + +.inline-related.tabular fieldset.module table { + width: 100%; +} + +.last-related fieldset { + border: none; +} + +.inline-group .tabular tr.has_original td { + padding-top: 2em; +} + +.inline-group .tabular tr td.original { + padding: 2px 0 0 0; + width: 0; + _position: relative; +} + +.inline-group .tabular th.original { + width: 0px; + padding: 0; +} + +.inline-group .tabular td.original p { + position: absolute; + left: 0; + height: 1.1em; + padding: 2px 7px; + overflow: hidden; + font-size: 9px; + font-weight: bold; + color: #666; + _width: 700px; +} + +.inline-group ul.tools { + padding: 0; + margin: 0; + list-style: none; +} + +.inline-group ul.tools li { + display: inline; + padding: 0 5px; +} + +.inline-group ul.tools a.add { + background: url(../img/admin/icon_addlink.gif) 0 50% no-repeat; + padding-left: 14px; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/media/django/css/global.css Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,142 @@ +body { margin:0; padding:0; font-size:12px; font-family:"Lucida Grande","DejaVu Sans","Bitstream Vera Sans",Verdana,Arial,sans-serif; color:#333; background:#fff; } + +/* LINKS */ +a:link, a:visited { color: #5b80b2; text-decoration:none; } +a:hover { color: #036; } +a img { border:none; } +a.section:link, a.section:visited { color: white; text-decoration:none; } + +/* GLOBAL DEFAULTS */ +p, ol, ul, dl { margin:.2em 0 .8em 0; } +p { padding:0; line-height:140%; } + +h1,h2,h3,h4,h5 { font-weight:bold; } +h1 { font-size:18px; color:#666; padding:0 6px 0 0; margin:0 0 .2em 0; } +h2 { font-size:16px; margin:1em 0 .5em 0; } +h2.subhead { font-weight:normal;margin-top:0; } +h3 { font-size:14px; margin:.8em 0 .3em 0; color:#666; font-weight:bold; } +h4 { font-size:12px; margin:1em 0 .8em 0; padding-bottom:3px; } +h5 { font-size:10px; margin:1.5em 0 .5em 0; color:#666; text-transform:uppercase; letter-spacing:1px; } + +ul li { list-style-type:square; padding:1px 0; } +ul.plainlist { margin-left:0 !important; } +ul.plainlist li { list-style-type:none; } +li ul { margin-bottom:0; } +li, dt, dd { font-size:11px; line-height:14px; } +dt { font-weight:bold; margin-top:4px; } +dd { margin-left:0; } + +form { margin:0; padding:0; } +fieldset { margin:0; padding:0; } + +blockquote { font-size:11px; color:#777; margin-left:2px; padding-left:10px; border-left:5px solid #ddd; } +code, pre { font-family:"Bitstream Vera Sans Mono", Monaco, "Courier New", Courier, monospace; background:inherit; color:#666; font-size:11px; } +pre.literal-block { margin:10px; background:#eee; padding:6px 8px; } +code strong { color:#930; } +hr { clear:both; color:#eee; background-color:#eee; height:1px; border:none; margin:0; padding:0; font-size:1px; line-height:1px; } + +/* TEXT STYLES & MODIFIERS */ +.small { font-size:11px; } +.tiny { font-size:10px; } +p.tiny { margin-top:-2px; } +.mini { font-size:9px; } +p.mini { margin-top:-3px; } +.help, p.help { font-size:10px !important; color:#999; } +p img, h1 img, h2 img, h3 img, h4 img, td img { vertical-align:middle; } +.quiet, a.quiet:link, a.quiet:visited { color:#999 !important;font-weight:normal !important; } +.quiet strong { font-weight:bold !important; } +.float-right { float:right; } +.float-left { float:left; } +.clear { clear:both; } +.align-left { text-align:left; } +.align-right { text-align:right; } +.example { margin:10px 0; padding:5px 10px; background:#efefef; } +.nowrap { white-space:nowrap; } + +/* TABLES */ +table { border-collapse:collapse; border-color:#ccc; } +td, th { font-size:11px; line-height:13px; border-bottom:1px solid #eee; vertical-align:top; padding:5px; font-family:"Lucida Grande", Verdana, Arial, sans-serif; } +th { text-align:left; font-size:12px; font-weight:bold; } +thead th, +tfoot td { color:#666; padding:2px 5px; font-size:11px; background:#e1e1e1 url(../img/admin/nav-bg.gif) top left repeat-x; border-left:1px solid #ddd; border-bottom:1px solid #ddd; } +tfoot td { border-bottom:none; border-top:1px solid #ddd; } +thead th:first-child, +tfoot td:first-child { border-left:none !important; } +thead th.optional { font-weight:normal !important; } +fieldset table { border-right:1px solid #eee; } +tr.row-label td { font-size:9px; padding-top:2px; padding-bottom:0; border-bottom:none; color:#666; margin-top:-1px; } +tr.alt { background:#f6f6f6; } +.row1 { background:#EDF3FE; } +.row2 { background:white; } + +/* SORTABLE TABLES */ +thead th a:link, thead th a:visited { color:#666; display:block; } +table thead th.sorted { background-position:bottom left !important; } +table thead th.sorted a { padding-right:13px; } +table thead th.ascending a { background:url(../img/admin/arrow-down.gif) right .4em no-repeat; } +table thead th.descending a { background:url(../img/admin/arrow-up.gif) right .4em no-repeat; } + +/* ORDERABLE TABLES */ +table.orderable tbody tr td:hover { cursor:move; } +table.orderable tbody tr td:first-child { padding-left:14px; background-image:url(../img/admin/nav-bg-grabber.gif); background-repeat:repeat-y; } +table.orderable-initalized .order-cell, body>tr>td.order-cell { display:none; } + +/* FORM DEFAULTS */ +input, textarea, select { margin:2px 0; padding:2px 3px; vertical-align:middle; font-family:"Lucida Grande", Verdana, Arial, sans-serif; font-weight:normal; font-size:11px; } +textarea { vertical-align:top !important; } +input[type=text], input[type=password], textarea, select, .vTextField { border:1px solid #ccc; } + +/* FORM BUTTONS */ +input[type=submit], input[type=button], .submit-row input { background:white url(../img/admin/nav-bg.gif) bottom repeat-x; padding:3px; color:black; border:1px solid #bbb; border-color:#ddd #aaa #aaa #ddd; } +input[type=submit]:active, input[type=button]:active { background-image:url(../img/admin/nav-bg-reverse.gif); background-position:top; } +input[type=submit].default, .submit-row input.default { border:2px solid #5b80b2; background:#7CA0C7 url(../img/admin/default-bg.gif) bottom repeat-x; font-weight:bold; color:white; float:right; } +input[type=submit].default:active { background-image:url(../img/admin/default-bg-reverse.gif); background-position:top; } + +/* MODULES */ +.module { border:1px solid #ccc; margin-bottom:5px; background:white; } +.module p, .module ul, .module h3, .module h4, .module dl, .module pre { padding-left:10px; padding-right:10px; } +.module blockquote { margin-left:12px; } +.module ul, .module ol { margin-left:1.5em; } +.module h3 { margin-top:.6em; } +.module h2, .module caption, .inline-group h2 { margin:0; padding:2px 5px 3px 5px; font-size:11px; text-align:left; font-weight:bold; background:#7CA0C7 url(../img/admin/default-bg.gif) top left repeat-x; color:white; } +.module table { border-collapse: collapse; } + +/* MESSAGES & ERRORS */ +ul.messagelist { padding:0 0 5px 0; margin:0; } +ul.messagelist li { font-size:12px; display:block; padding:4px 5px 4px 25px; margin:0 0 3px 0; border-bottom:1px solid #ddd; color:#666; background:#ffc url(../img/admin/icon_success.gif) 5px .3em no-repeat; } +.errornote { font-size:12px !important; display:block; padding:4px 5px 4px 25px; margin:0 0 3px 0; border:1px solid red; color:red;background:#ffc url(../img/admin/icon_error.gif) 5px .3em no-repeat; } +ul.errorlist { margin:0 !important; padding:0 !important; } +.errorlist li { font-size:12px !important; display:block; padding:4px 5px 4px 25px; margin:0 0 3px 0; border:1px solid red; color:white; background:red url(../img/admin/icon_alert.gif) 5px .3em no-repeat; } +td ul.errorlist { margin:0 !important; padding:0 !important; } +td ul.errorlist li { margin:0 !important; } +.errors { background:#ffc; } +.errors input, .errors select { border:1px solid red; } +div.system-message { background: #ffc; margin: 10px; padding: 6px 8px; font-size: .8em; } +div.system-message p.system-message-title { padding:4px 5px 4px 25px; margin:0; color:red; background:#ffc url(../img/admin/icon_error.gif) 5px .3em no-repeat; } +.description { font-size:12px; padding:5px 0 0 12px; } + +/* BREADCRUMBS */ +div.breadcrumbs { background:white url(../img/admin/nav-bg-reverse.gif) 0 -10px repeat-x; padding:2px 8px 3px 8px; font-size:11px; color:#999; border-top:1px solid white; border-bottom:1px solid #ccc; text-align:left; } + +/* ACTION ICONS */ +.addlink { padding-left:12px; background:url(../img/admin/icon_addlink.gif) 0 .2em no-repeat; } +.changelink { padding-left:12px; background:url(../img/admin/icon_changelink.gif) 0 .2em no-repeat; } +.deletelink { padding-left:12px; background:url(../img/admin/icon_deletelink.gif) 0 .25em no-repeat; } +a.deletelink:link, a.deletelink:visited { color:#CC3434; } +a.deletelink:hover { color:#993333; } + +/* OBJECT TOOLS */ +.object-tools { font-size:10px; font-weight:bold; font-family:Arial,Helvetica,sans-serif; padding-left:0; float:right; position:relative; margin-top:-2.4em; margin-bottom:-2em; } +.form-row .object-tools { margin-top:5px; margin-bottom:5px; float:none; height:2em; padding-left:3.5em; } +.object-tools li { display:block; float:left; background:url(../img/admin/tool-left.gif) 0 0 no-repeat; padding:0 0 0 8px; margin-left:2px; height:16px; } +.object-tools li:hover { background:url(../img/admin/tool-left_over.gif) 0 0 no-repeat; } +.object-tools a:link, .object-tools a:visited { display:block; float:left; color:white; padding:.1em 14px .1em 8px; height:14px; background:#999 url(../img/admin/tool-right.gif) 100% 0 no-repeat; } +.object-tools a:hover, .object-tools li:hover a { background:#5b80b2 url(../img/admin/tool-right_over.gif) 100% 0 no-repeat; } +.object-tools a.viewsitelink, .object-tools a.golink { background:#999 url(../img/admin/tooltag-arrowright.gif) top right no-repeat; padding-right:28px; } +.object-tools a.viewsitelink:hover, .object-tools a.golink:hover { background:#5b80b2 url(../img/admin/tooltag-arrowright_over.gif) top right no-repeat; } +.object-tools a.addlink { background:#999 url(../img/admin/tooltag-add.gif) top right no-repeat; padding-right:28px; } +.object-tools a.addlink:hover { background:#5b80b2 url(../img/admin/tooltag-add_over.gif) top right no-repeat; } + +/* OBJECT HISTORY */ +table#change-history { width:100%; } +table#change-history tbody th { width:16em; }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/media/django/css/ie.css Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,51 @@ +/* IE 6 & 7 */ + +/* Proper fixed width for dashboard in IE6 */ + +.dashboard #content { + *width: 768px; +} + +.dashboard #content-main { + *width: 535px; +} + +/* IE 6 ONLY */ + +/* Keep header from flowing off the page */ + +#container { + _position: static; +} + +/* Put the right sidebars back on the page */ + +.colMS #content-related { + _margin-right: 0; + _margin-left: 10px; + _position: static; +} + +/* Put the left sidebars back on the page */ + +.colSM #content-related { + _margin-right: 10px; + _margin-left: -115px; + _position: static; +} + +.form-row { + _height: 1%; +} + +/* Fix right margin for changelist filters in IE6 */ + +#changelist-filter ul { + _margin-right: -10px; +} + +/* IE ignores min-height, but treats height as if it were min-height */ + +.change-list .filtered { + _height: 400px; +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/media/django/css/layout.css Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,29 @@ +/* PAGE STRUCTURE */ +#container { position:relative; width:100%; min-width:760px; padding:0; } +#content { margin:10px 15px; } +#header { width:100%; } +#content-main { float:left; width:100%; } +#content-related { float:right; width:18em; position:relative; margin-right:-19em; } +#footer { clear:both; padding:10px; } + +/* COLUMN TYPES */ +.colMS { margin-right:20em !important; } +.colSM { margin-left:20em !important; } +.colSM #content-related { float:left; margin-right:0; margin-left:-19em; } +.colSM #content-main { float:right; } +.popup .colM { width:95%; } +.subcol { float:left; width:46%; margin-right:15px; } +.dashboard #content { width:500px; } + +/* HEADER */ +#header { background:#417690; color:#ffc; overflow:hidden; } +#header a:link, #header a:visited { color:white; } +#header a:hover { text-decoration:underline; } +#branding h1 { padding:0 10px; font-size:18px; margin:8px 0; font-weight:normal; color:#f4f379; } +#branding h2 { padding:0 10px; font-size:14px; margin:-8px 0 8px 0; font-weight:normal; color:#ffc; } +#user-tools { position:absolute; top:0; right:0; padding:1.2em 10px; font-size:11px; text-align:right; } + +/* SIDEBAR */ +#content-related h3 { font-size:12px; color:#666; margin-bottom:3px; } +#content-related h4 { font-size:11px; } +#content-related .module h2 { background:#eee url(../img/admin/nav-bg.gif) bottom left repeat-x; color:#666; } \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/media/django/css/login.css Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,54 @@ +/* LOGIN FORM */ + +body.login { + background: #eee; +} + +.login #container { + background: white; + border: 1px solid #ccc; + width: 28em; + min-width: 300px; + margin-left: auto; + margin-right: auto; + margin-top: 100px; +} + +.login #content-main { + width: 100%; +} + +.login form { + margin-top: 1em; +} + +.login .form-row { + padding: 4px 0; + float: left; + width: 100%; +} + +.login .form-row label { + float: left; + width: 9em; + padding-right: 0.5em; + line-height: 2em; + text-align: right; + font-size: 1em; + color: #333; +} + +.login .form-row #id_username, .login .form-row #id_password { + width: 14em; +} + +.login span.help { + font-size: 10px; + display: block; +} + +.login .submit-row { + clear: both; + padding: 1em 0 0 9.4em; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/media/django/css/null.css Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,1 @@ +/* Nothing to see here. Dummy file to feed to the high pass filter which hides CSS from IE5/win. Details: http://tantek.com/CSS/Examples/highpass.html */ \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/media/django/css/patch-iewin.css Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,8 @@ +* html #container { position:static; } /* keep header from flowing off the page */ +* html .colMS #content-related { margin-right:0; margin-left:10px; position:static; } /* put the right sidebars back on the page */ +* html .colSM #content-related { margin-right:10px; margin-left:-115px; position:static; } /* put the left sidebars back on the page */ +* html .form-row { height:1%; } +* html .dashboard #content { width:768px; } /* proper fixed width for dashboard in IE6 */ +* html .dashboard #content-main { width:535px; } /* proper fixed width for dashboard in IE6 */ +* html #changelist-filter ul { margin-right:-10px; } /* fix right margin for changelist filters in IE6 */ +* html .change-list .filtered { height:400px; } /* IE ignores min-height, but treats height as if it were min-height */ \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/media/django/css/rtl.css Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,195 @@ +body { + direction: rtl; +} + +/* LOGIN */ + +.login .form-row { + float: right; +} + +.login .form-row label { + float: right; + padding-left: 0.5em; + padding-right: 0; + text-align: left; +} + +.login .submit-row { + clear: both; + padding: 1em 9.4em 0 0; +} + +/* GLOBAL */ + +th { + text-align: right; +} + +.module h2, .module caption { + text-align: right; +} + +.addlink, .changelink { + padding-left: 0px; + padding-right: 12px; + background-position: 100% 0.2em; +} + +.deletelink { + padding-left: 0px; + padding-right: 12px; + background-position: 100% 0.25em; +} + +.object-tools { + float: left; +} + +/* LAYOUT */ + +#user-tools { + right: auto; + left: 0; + text-align: left; +} + +div.breadcrumbs { + text-align: right; +} + +#content-main { + float: right; +} + +#content-related { + float: left; + margin-left: -19em; + margin-right: auto; +} + +.colMS { + margin-left: 20em !important; + margin-right: 10px !important; +} + +/* dashboard styles */ + +.dashboard .module table td a { + padding-left: .6em; + padding-right: 12px; +} + +/* changelists styles */ + +.change-list .filtered { + background: white url(../img/admin/changelist-bg_rtl.gif) top left repeat-y !important; +} + +.change-list .filtered table { + border-left: 1px solid #ddd; + border-right: 0px none; +} + +#changelist-filter { + right: auto; + left: 0; + border-left: 0px none; + border-right: 1px solid #ddd; +} + +.change-list .filtered table, .change-list .filtered .paginator, .filtered #toolbar, .filtered div.xfull { + margin-right: 0px !important; + margin-left: 160px !important; +} + +#changelist-filter li.selected { + border-left: 0px none; + padding-left: 0px; + margin-left: 0; + border-right: 5px solid #ccc; + padding-right: 5px; + margin-right: -10px; +} + +/* FORMS */ + +.aligned label { + padding: 0 0 3px 1em; + float: right; +} + +.submit-row { + text-align: left +} + +.submit-row p.deletelink-box { + float: right; +} + +.submit-row .deletelink { + background: url(../img/admin/icon_deletelink.gif) 0 50% no-repeat; + padding-right: 14px; +} + +.vDateField, .vTimeField { + margin-left: 2px; +} + +form ul.inline li { + float: right; + padding-right: 0; + padding-left: 7px; +} + +input[type=submit].default, .submit-row input.default { + float: left; +} + +fieldset .field-box { + float: right; + margin-left: 20px; +} + +.errorlist li { + background-position: 100% .3em; + padding: 4px 25px 4px 5px; +} + +.errornote { + background-position: 100% .3em; + padding: 4px 25px 4px 5px; +} + +/* WIDGETS */ + +.calendarnav-previous { + top: 0; + left: auto; + right: 0; +} + +.calendarnav-next { + top: 0; + right: auto; + left: 0; +} + +.calendar caption, .calendarbox h2 { + text-align: center; +} + +.selector { + float: right; +} + +.selector .selector-filter { + text-align: right; +} + +/* MISC */ + +.inline-related h2 { + text-align: right +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/media/django/css/widgets.css Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,506 @@ +/* SELECTOR (FILTER INTERFACE) */ + +.selector { + width: 580px; + float: left; +} + +.selector select { + width: 270px; + height: 17.2em; +} + +.selector-available, .selector-chosen { + float: left; + width: 270px; + text-align: center; + margin-bottom: 5px; +} + +.selector-available h2, .selector-chosen h2 { + border: 1px solid #ccc; +} + +.selector .selector-available h2 { + background: white url(../img/admin/nav-bg.gif) bottom left repeat-x; + color: #666; +} + +.selector .selector-filter { + background: white; + border: 1px solid #ccc; + border-width: 0 1px; + padding: 3px; + color: #999; + font-size: 10px; + margin: 0; + text-align: left; +} + +.selector .selector-chosen .selector-filter { + padding: 4px 5px; +} + +.selector .selector-available input { + width: 230px; +} + +.selector ul.selector-chooser { + float: left; + width: 22px; + height: 50px; + background: url(../img/admin/chooser-bg.gif) top center no-repeat; + margin: 8em 3px 0 3px; + padding: 0; +} + +.selector-chooser li { + margin: 0; + padding: 3px; + list-style-type: none; +} + +.selector select { + margin-bottom: 5px; + margin-top: 0; +} + +.selector-add, .selector-remove { + width: 16px; + height: 16px; + display: block; + text-indent: -3000px; +} + +.selector-add { + background: url(../img/admin/selector-add.gif) top center no-repeat; + margin-bottom: 2px; +} + +.selector-remove { + background: url(../img/admin/selector-remove.gif) top center no-repeat; +} + +a.selector-chooseall, a.selector-clearall { + display: block; + width: 6em; + text-align: left; + margin-left: auto; + margin-right: auto; + font-weight: bold; + color: #666; + padding: 3px 0 3px 18px; +} + +a.selector-chooseall:hover, a.selector-clearall:hover { + color: #036; +} + +a.selector-chooseall { + width: 7em; + background: url(../img/admin/selector-addall.gif) left center no-repeat; +} + +a.selector-clearall { + background: url(../img/admin/selector-removeall.gif) left center no-repeat; +} + + +/* STACKED SELECTORS */ + +.stacked { + float: left; + width: 500px; +} + +.stacked select { + width: 480px; + height: 10.1em; +} + +.stacked .selector-available, .stacked .selector-chosen { + width: 480px; +} + +.stacked .selector-available { + margin-bottom: 0; +} + +.stacked .selector-available input { + width: 442px; +} + +.stacked ul.selector-chooser { + height: 22px; + width: 50px; + margin: 0 0 3px 40%; + background: url(../img/admin/chooser_stacked-bg.gif) top center no-repeat; +} + +.stacked .selector-chooser li { + float: left; + padding: 3px 3px 3px 5px; +} + +.stacked .selector-chooseall, .stacked .selector-clearall { + display: none; +} + +.stacked .selector-add { + background-image: url(../img/admin/selector_stacked-add.gif); +} + +.stacked .selector-remove { + background-image: url(../img/admin/selector_stacked-remove.gif); +} + + +/* DATE AND TIME */ + +p.datetime { + line-height: 20px; + margin: 0; + padding: 0; + color: #666; + font-size: 11px; + font-weight: bold; +} + +.datetime span { + font-size: 11px; + color: #ccc; + font-weight: normal; + white-space: nowrap; +} + +table p.datetime { + font-size: 10px; + margin-left: 0; + padding-left: 0; +} + +/* FILE UPLOADS */ + +p.file-upload { + line-height: 20px; + margin: 0; + padding: 0; + color: #666; + font-size: 11px; + font-weight: bold; +} + +.file-upload a { + font-weight: normal; +} + +.file-upload .deletelink { + margin-left: 5px; +} + +/* CALENDARS & CLOCKS */ + +.calendarbox, .clockbox { + margin: 5px auto; + font-size: 11px; + width: 16em; + text-align: center; + background: white; + position: relative; +} + +.clockbox { + width: auto; +} + +.calendar { + margin: 0; + padding: 0; +} + +.calendar table { + margin: 0; + padding: 0; + border-collapse: collapse; + background: white; + width: 99%; +} + +.calendar caption, .calendarbox h2 { + margin: 0; + font-size: 11px; + text-align: center; + border-top: none; +} + +.calendar th { + font-size: 10px; + color: #666; + padding: 2px 3px; + text-align: center; + background: #e1e1e1 url(../img/admin/nav-bg.gif) 0 50% repeat-x; + border-bottom: 1px solid #ddd; +} + +.calendar td { + font-size: 11px; + text-align: center; + padding: 0; + border-top: 1px solid #eee; + border-bottom: none; +} + +.calendar td.selected a { + background: #C9DBED; +} + +.calendar td.nonday { + background: #efefef; +} + +.calendar td.today a { + background: #ffc; +} + +.calendar td a, .timelist a { + display: block; + font-weight: bold; + padding: 4px; + text-decoration: none; + color: #444; +} + +.calendar td a:hover, .timelist a:hover { + background: #5b80b2; + color: white; +} + +.calendar td a:active, .timelist a:active { + background: #036; + color: white; +} + +.calendarnav { + font-size: 10px; + text-align: center; + color: #ccc; + margin: 0; + padding: 1px 3px; +} + +.calendarnav a:link, #calendarnav a:visited, #calendarnav a:hover { + color: #999; +} + +.calendar-shortcuts { + background: white; + font-size: 10px; + line-height: 11px; + border-top: 1px solid #eee; + padding: 3px 0 4px; + color: #ccc; +} + +.calendarbox .calendarnav-previous, .calendarbox .calendarnav-next { + display: block; + position: absolute; + font-weight: bold; + font-size: 12px; + background: #C9DBED url(../img/admin/default-bg.gif) bottom left repeat-x; + padding: 1px 4px 2px 4px; + color: white; +} + +.calendarnav-previous:hover, .calendarnav-next:hover { + background: #036; +} + +.calendarnav-previous { + top: 0; + left: 0; +} + +.calendarnav-next { + top: 0; + right: 0; +} + +.calendar-cancel { + margin: 0 !important; + padding: 0; + font-size: 10px; + background: #e1e1e1 url(../img/admin/nav-bg.gif) 0 50% repeat-x; + border-top: 1px solid #ddd; +} + +.calendar-cancel a { + padding: 2px; + color: #999; +} + +ul.timelist, .timelist li { + list-style-type: none; + margin: 0; + padding: 0; +} + +.timelist a { + padding: 2px; +} + +/* INLINE ORDERER */ + +ul.orderer { + position: relative; + padding: 0 !important; + margin: 0 !important; + list-style-type: none; +} + +ul.orderer li { + list-style-type: none; + display: block; + padding: 0; + margin: 0; + border: 1px solid #bbb; + border-width: 0 1px 1px 0; + white-space: nowrap; + overflow: hidden; + background: #e2e2e2 url(../img/admin/nav-bg-grabber.gif) repeat-y; +} + +ul.orderer li:hover { + cursor: move; + background-color: #ddd; +} + +ul.orderer li a.selector { + margin-left: 12px; + overflow: hidden; + width: 83%; + font-size: 10px !important; + padding: 0.6em 0; +} + +ul.orderer li a:link, ul.orderer li a:visited { + color: #333; +} + +ul.orderer li .inline-deletelink { + position: absolute; + right: 4px; + margin-top: 0.6em; +} + +ul.orderer li.selected { + background-color: #f8f8f8; + border-right-color: #f8f8f8; +} + +ul.orderer li.deleted { + background: #bbb url(../img/admin/deleted-overlay.gif); +} + +ul.orderer li.deleted a:link, ul.orderer li.deleted a:visited { + color: #888; +} + +ul.orderer li.deleted .inline-deletelink { + background-image: url(../img/admin/inline-restore.png); +} + +ul.orderer li.deleted:hover, ul.orderer li.deleted a.selector:hover { + cursor: default; +} + +/* EDIT INLINE */ + +.inline-deletelink { + display: block; + text-indent: -9999px; + background: transparent url(../img/admin/inline-delete.png) no-repeat; + width: 15px; + height: 15px; + margin: 0.4em 0; + border: 0px none; +} + +.inline-deletelink:hover { + background-position: -15px 0; + cursor: pointer; +} + +.editinline button.addlink { + border: 0px none; + color: #5b80b2; + font-size: 100%; + cursor: pointer; +} + +.editinline button.addlink:hover { + color: #036; + cursor: pointer; +} + +.editinline table .help { + text-align: right; + float: right; + padding-left: 2em; +} + +.editinline tfoot .addlink { + white-space: nowrap; +} + +.editinline table thead th:last-child { + border-left: none; +} + +.editinline tr.deleted { + background: #ddd url(../img/admin/deleted-overlay.gif); +} + +.editinline tr.deleted .inline-deletelink { + background-image: url(../img/admin/inline-restore.png); +} + +.editinline tr.deleted td:hover { + cursor: default; +} + +.editinline tr.deleted td:first-child { + background-image: none !important; +} + +/* EDIT INLINE - STACKED */ + +.editinline-stacked { + min-width: 758px; +} + +.editinline-stacked .inline-object { + margin-left: 210px; + background: white; +} + +.editinline-stacked .inline-source { + float: left; + width: 200px; + background: #f8f8f8; +} + +.editinline-stacked .inline-splitter { + float: left; + width: 9px; + background: #f8f8f8 url(../img/admin/inline-splitter-bg.gif) 50% 50% no-repeat; + border-right: 1px solid #ccc; +} + +.editinline-stacked .controls { + clear: both; + background: #e1e1e1 url(../img/admin/nav-bg.gif) top left repeat-x; + padding: 3px 4px; + font-size: 11px; + border-top: 1px solid #ddd; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/media/django/js/SelectBox.js Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,111 @@ +var SelectBox = { + cache: new Object(), + init: function(id) { + var box = document.getElementById(id); + var node; + SelectBox.cache[id] = new Array(); + var cache = SelectBox.cache[id]; + for (var i = 0; (node = box.options[i]); i++) { + cache.push({value: node.value, text: node.text, displayed: 1}); + } + }, + redisplay: function(id) { + // Repopulate HTML select box from cache + var box = document.getElementById(id); + box.options.length = 0; // clear all options + for (var i = 0, j = SelectBox.cache[id].length; i < j; i++) { + var node = SelectBox.cache[id][i]; + if (node.displayed) { + box.options[box.options.length] = new Option(node.text, node.value, false, false); + } + } + }, + filter: function(id, text) { + // Redisplay the HTML select box, displaying only the choices containing ALL + // the words in text. (It's an AND search.) + var tokens = text.toLowerCase().split(/\s+/); + var node, token; + for (var i = 0; (node = SelectBox.cache[id][i]); i++) { + node.displayed = 1; + for (var j = 0; (token = tokens[j]); j++) { + if (node.text.toLowerCase().indexOf(token) == -1) { + node.displayed = 0; + } + } + } + SelectBox.redisplay(id); + }, + delete_from_cache: function(id, value) { + var node, delete_index = null; + for (var i = 0; (node = SelectBox.cache[id][i]); i++) { + if (node.value == value) { + delete_index = i; + break; + } + } + var j = SelectBox.cache[id].length - 1; + for (var i = delete_index; i < j; i++) { + SelectBox.cache[id][i] = SelectBox.cache[id][i+1]; + } + SelectBox.cache[id].length--; + }, + add_to_cache: function(id, option) { + SelectBox.cache[id].push({value: option.value, text: option.text, displayed: 1}); + }, + cache_contains: function(id, value) { + // Check if an item is contained in the cache + var node; + for (var i = 0; (node = SelectBox.cache[id][i]); i++) { + if (node.value == value) { + return true; + } + } + return false; + }, + move: function(from, to) { + var from_box = document.getElementById(from); + var to_box = document.getElementById(to); + var option; + for (var i = 0; (option = from_box.options[i]); i++) { + if (option.selected && SelectBox.cache_contains(from, option.value)) { + SelectBox.add_to_cache(to, {value: option.value, text: option.text, displayed: 1}); + SelectBox.delete_from_cache(from, option.value); + } + } + SelectBox.redisplay(from); + SelectBox.redisplay(to); + }, + move_all: function(from, to) { + var from_box = document.getElementById(from); + var to_box = document.getElementById(to); + var option; + for (var i = 0; (option = from_box.options[i]); i++) { + if (SelectBox.cache_contains(from, option.value)) { + SelectBox.add_to_cache(to, {value: option.value, text: option.text, displayed: 1}); + SelectBox.delete_from_cache(from, option.value); + } + } + SelectBox.redisplay(from); + SelectBox.redisplay(to); + }, + sort: function(id) { + SelectBox.cache[id].sort( function(a, b) { + a = a.text.toLowerCase(); + b = b.text.toLowerCase(); + try { + if (a > b) return 1; + if (a < b) return -1; + } + catch (e) { + // silently fail on IE 'unknown' exception + } + return 0; + } ); + }, + select_all: function(id) { + var box = document.getElementById(id); + for (var i = 0; i < box.options.length; i++) { + box.options[i].selected = 'selected'; + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/media/django/js/SelectFilter.js Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,81 @@ +/* +SelectFilter - Turns a multiple-select box into a filter interface. + +Requires SelectBox.js and addevent.js. +*/ + +function findForm(node) { + // returns the node of the form containing the given node + if (node.tagName.toLowerCase() != 'form') { + return findForm(node.parentNode); + } + return node; +} + +var SelectFilter = { + init: function(field_id) { + var from_box = document.getElementById(field_id); + from_box.id += '_from'; // change its ID + // Create the INPUT input box + var input_box = document.createElement('input'); + input_box.id = field_id + '_input'; + input_box.setAttribute('type', 'text'); + from_box.parentNode.insertBefore(input_box, from_box); + from_box.parentNode.insertBefore(document.createElement('br'), input_box.nextSibling); + // Create the TO box + var to_box = document.createElement('select'); + to_box.id = field_id + '_to'; + to_box.setAttribute('multiple', 'multiple'); + to_box.setAttribute('size', from_box.size); + from_box.parentNode.insertBefore(to_box, from_box.nextSibling); + to_box.setAttribute('name', from_box.getAttribute('name')); + from_box.setAttribute('name', from_box.getAttribute('name') + '_old'); + // Give the filters a CSS hook + from_box.setAttribute('class', 'filtered'); + to_box.setAttribute('class', 'filtered'); + // Set up the JavaScript event handlers for the select box filter interface + addEvent(input_box, 'keyup', function(e) { SelectFilter.filter_key_up(e, field_id); }); + addEvent(input_box, 'keydown', function(e) { SelectFilter.filter_key_down(e, field_id); }); + addEvent(from_box, 'dblclick', function() { SelectBox.move(field_id + '_from', field_id + '_to'); }); + addEvent(from_box, 'focus', function() { input_box.focus(); }); + addEvent(to_box, 'dblclick', function() { SelectBox.move(field_id + '_to', field_id + '_from'); }); + addEvent(findForm(from_box), 'submit', function() { SelectBox.select_all(field_id + '_to'); }); + SelectBox.init(field_id + '_from'); + SelectBox.init(field_id + '_to'); + // Move selected from_box options to to_box + SelectBox.move(field_id + '_from', field_id + '_to'); + }, + filter_key_up: function(event, field_id) { + from = document.getElementById(field_id + '_from'); + // don't submit form if user pressed Enter + if ((event.which && event.which == 13) || (event.keyCode && event.keyCode == 13)) { + from.selectedIndex = 0; + SelectBox.move(field_id + '_from', field_id + '_to'); + from.selectedIndex = 0; + return false; + } + var temp = from.selectedIndex; + SelectBox.filter(field_id + '_from', document.getElementById(field_id + '_input').value); + from.selectedIndex = temp; + return true; + }, + filter_key_down: function(event, field_id) { + from = document.getElementById(field_id + '_from'); + // right arrow -- move across + if ((event.which && event.which == 39) || (event.keyCode && event.keyCode == 39)) { + var old_index = from.selectedIndex; + SelectBox.move(field_id + '_from', field_id + '_to'); + from.selectedIndex = (old_index == from.length) ? from.length - 1 : old_index; + return false; + } + // down arrow -- wrap around + if ((event.which && event.which == 40) || (event.keyCode && event.keyCode == 40)) { + from.selectedIndex = (from.length == from.selectedIndex + 1) ? 0 : from.selectedIndex + 1; + } + // up arrow -- wrap around + if ((event.which && event.which == 38) || (event.keyCode && event.keyCode == 38)) { + from.selectedIndex = (from.selectedIndex == 0) ? from.length - 1 : from.selectedIndex - 1; + } + return true; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/media/django/js/SelectFilter2.js Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,113 @@ +/* +SelectFilter2 - Turns a multiple-select box into a filter interface. + +Different than SelectFilter because this is coupled to the admin framework. + +Requires core.js, SelectBox.js and addevent.js. +*/ + +function findForm(node) { + // returns the node of the form containing the given node + if (node.tagName.toLowerCase() != 'form') { + return findForm(node.parentNode); + } + return node; +} + +var SelectFilter = { + init: function(field_id, field_name, is_stacked, admin_media_prefix) { + var from_box = document.getElementById(field_id); + from_box.id += '_from'; // change its ID + from_box.className = 'filtered'; + + // Remove <p class="info">, because it just gets in the way. + var ps = from_box.parentNode.getElementsByTagName('p'); + for (var i=0; i<ps.length; i++) { + from_box.parentNode.removeChild(ps[i]); + } + + // <div class="selector"> or <div class="selector stacked"> + var selector_div = quickElement('div', from_box.parentNode); + selector_div.className = is_stacked ? 'selector stacked' : 'selector'; + + // <div class="selector-available"> + var selector_available = quickElement('div', selector_div, ''); + selector_available.className = 'selector-available'; + quickElement('h2', selector_available, interpolate(gettext('Available %s'), [field_name])); + var filter_p = quickElement('p', selector_available, ''); + filter_p.className = 'selector-filter'; + quickElement('img', filter_p, '', 'src', admin_media_prefix + 'img/admin/selector-search.gif'); + filter_p.appendChild(document.createTextNode(' ')); + var filter_input = quickElement('input', filter_p, '', 'type', 'text'); + filter_input.id = field_id + '_input'; + selector_available.appendChild(from_box); + var choose_all = quickElement('a', selector_available, gettext('Choose all'), 'href', 'javascript: (function(){ SelectBox.move_all("' + field_id + '_from", "' + field_id + '_to"); })()'); + choose_all.className = 'selector-chooseall'; + + // <ul class="selector-chooser"> + var selector_chooser = quickElement('ul', selector_div, ''); + selector_chooser.className = 'selector-chooser'; + var add_link = quickElement('a', quickElement('li', selector_chooser, ''), gettext('Add'), 'href', 'javascript: (function(){ SelectBox.move("' + field_id + '_from","' + field_id + '_to");})()'); + add_link.className = 'selector-add'; + var remove_link = quickElement('a', quickElement('li', selector_chooser, ''), gettext('Remove'), 'href', 'javascript: (function(){ SelectBox.move("' + field_id + '_to","' + field_id + '_from");})()'); + remove_link.className = 'selector-remove'; + + // <div class="selector-chosen"> + var selector_chosen = quickElement('div', selector_div, ''); + selector_chosen.className = 'selector-chosen'; + quickElement('h2', selector_chosen, interpolate(gettext('Chosen %s'), [field_name])); + var selector_filter = quickElement('p', selector_chosen, gettext('Select your choice(s) and click ')); + selector_filter.className = 'selector-filter'; + quickElement('img', selector_filter, '', 'src', admin_media_prefix + (is_stacked ? 'img/admin/selector_stacked-add.gif':'img/admin/selector-add.gif'), 'alt', 'Add'); + var to_box = quickElement('select', selector_chosen, '', 'id', field_id + '_to', 'multiple', 'multiple', 'size', from_box.size, 'name', from_box.getAttribute('name')); + to_box.className = 'filtered'; + var clear_all = quickElement('a', selector_chosen, gettext('Clear all'), 'href', 'javascript: (function() { SelectBox.move_all("' + field_id + '_to", "' + field_id + '_from");})()'); + clear_all.className = 'selector-clearall'; + + from_box.setAttribute('name', from_box.getAttribute('name') + '_old'); + + // Set up the JavaScript event handlers for the select box filter interface + addEvent(filter_input, 'keyup', function(e) { SelectFilter.filter_key_up(e, field_id); }); + addEvent(filter_input, 'keydown', function(e) { SelectFilter.filter_key_down(e, field_id); }); + addEvent(from_box, 'dblclick', function() { SelectBox.move(field_id + '_from', field_id + '_to'); }); + addEvent(to_box, 'dblclick', function() { SelectBox.move(field_id + '_to', field_id + '_from'); }); + addEvent(findForm(from_box), 'submit', function() { SelectBox.select_all(field_id + '_to'); }); + SelectBox.init(field_id + '_from'); + SelectBox.init(field_id + '_to'); + // Move selected from_box options to to_box + SelectBox.move(field_id + '_from', field_id + '_to'); + }, + filter_key_up: function(event, field_id) { + from = document.getElementById(field_id + '_from'); + // don't submit form if user pressed Enter + if ((event.which && event.which == 13) || (event.keyCode && event.keyCode == 13)) { + from.selectedIndex = 0; + SelectBox.move(field_id + '_from', field_id + '_to'); + from.selectedIndex = 0; + return false; + } + var temp = from.selectedIndex; + SelectBox.filter(field_id + '_from', document.getElementById(field_id + '_input').value); + from.selectedIndex = temp; + return true; + }, + filter_key_down: function(event, field_id) { + from = document.getElementById(field_id + '_from'); + // right arrow -- move across + if ((event.which && event.which == 39) || (event.keyCode && event.keyCode == 39)) { + var old_index = from.selectedIndex; + SelectBox.move(field_id + '_from', field_id + '_to'); + from.selectedIndex = (old_index == from.length) ? from.length - 1 : old_index; + return false; + } + // down arrow -- wrap around + if ((event.which && event.which == 40) || (event.keyCode && event.keyCode == 40)) { + from.selectedIndex = (from.length == from.selectedIndex + 1) ? 0 : from.selectedIndex + 1; + } + // up arrow -- wrap around + if ((event.which && event.which == 38) || (event.keyCode && event.keyCode == 38)) { + from.selectedIndex = (from.selectedIndex == 0) ? from.length - 1 : from.selectedIndex - 1; + } + return true; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/media/django/js/admin/CollapsedFieldsets.js Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,85 @@ +// Finds all fieldsets with class="collapse", collapses them, and gives each +// one a "Show" link that uncollapses it. The "Show" link becomes a "Hide" +// link when the fieldset is visible. + +function findForm(node) { + // returns the node of the form containing the given node + if (node.tagName.toLowerCase() != 'form') { + return findForm(node.parentNode); + } + return node; +} + +var CollapsedFieldsets = { + collapse_re: /\bcollapse\b/, // Class of fieldsets that should be dealt with. + collapsed_re: /\bcollapsed\b/, // Class that fieldsets get when they're hidden. + collapsed_class: 'collapsed', + init: function() { + var fieldsets = document.getElementsByTagName('fieldset'); + var collapsed_seen = false; + for (var i = 0, fs; fs = fieldsets[i]; i++) { + // Collapse this fieldset if it has the correct class, and if it + // doesn't have any errors. (Collapsing shouldn't apply in the case + // of error messages.) + if (fs.className.match(CollapsedFieldsets.collapse_re) && !CollapsedFieldsets.fieldset_has_errors(fs)) { + collapsed_seen = true; + // Give it an additional class, used by CSS to hide it. + fs.className += ' ' + CollapsedFieldsets.collapsed_class; + // (<a id="fieldsetcollapser3" class="collapse-toggle" href="#">Show</a>) + var collapse_link = document.createElement('a'); + collapse_link.className = 'collapse-toggle'; + collapse_link.id = 'fieldsetcollapser' + i; + collapse_link.onclick = new Function('CollapsedFieldsets.show('+i+'); return false;'); + collapse_link.href = '#'; + collapse_link.innerHTML = gettext('Show'); + var h2 = fs.getElementsByTagName('h2')[0]; + h2.appendChild(document.createTextNode(' (')); + h2.appendChild(collapse_link); + h2.appendChild(document.createTextNode(')')); + } + } + if (collapsed_seen) { + // Expand all collapsed fieldsets when form is submitted. + addEvent(findForm(document.getElementsByTagName('fieldset')[0]), 'submit', function() { CollapsedFieldsets.uncollapse_all(); }); + } + }, + fieldset_has_errors: function(fs) { + // Returns true if any fields in the fieldset have validation errors. + var divs = fs.getElementsByTagName('div'); + for (var i=0; i<divs.length; i++) { + if (divs[i].className.match(/\berrors\b/)) { + return true; + } + } + return false; + }, + show: function(fieldset_index) { + var fs = document.getElementsByTagName('fieldset')[fieldset_index]; + // Remove the class name that causes the "display: none". + fs.className = fs.className.replace(CollapsedFieldsets.collapsed_re, ''); + // Toggle the "Show" link to a "Hide" link + var collapse_link = document.getElementById('fieldsetcollapser' + fieldset_index); + collapse_link.onclick = new Function('CollapsedFieldsets.hide('+fieldset_index+'); return false;'); + collapse_link.innerHTML = gettext('Hide'); + }, + hide: function(fieldset_index) { + var fs = document.getElementsByTagName('fieldset')[fieldset_index]; + // Add the class name that causes the "display: none". + fs.className += ' ' + CollapsedFieldsets.collapsed_class; + // Toggle the "Hide" link to a "Show" link + var collapse_link = document.getElementById('fieldsetcollapser' + fieldset_index); + collapse_link.onclick = new Function('CollapsedFieldsets.show('+fieldset_index+'); return false;'); + collapse_link.innerHTML = gettext('Show'); + }, + + uncollapse_all: function() { + var fieldsets = document.getElementsByTagName('fieldset'); + for (var i=0; i<fieldsets.length; i++) { + if (fieldsets[i].className.match(CollapsedFieldsets.collapsed_re)) { + CollapsedFieldsets.show(i); + } + } + } +} + +addEvent(window, 'load', CollapsedFieldsets.init);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/media/django/js/admin/DateTimeShortcuts.js Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,255 @@ +// Inserts shortcut buttons after all of the following: +// <input type="text" class="vDateField"> +// <input type="text" class="vTimeField"> + +var DateTimeShortcuts = { + calendars: [], + calendarInputs: [], + clockInputs: [], + calendarDivName1: 'calendarbox', // name of calendar <div> that gets toggled + calendarDivName2: 'calendarin', // name of <div> that contains calendar + calendarLinkName: 'calendarlink',// name of the link that is used to toggle + clockDivName: 'clockbox', // name of clock <div> that gets toggled + clockLinkName: 'clocklink', // name of the link that is used to toggle + admin_media_prefix: '', + init: function() { + // Deduce admin_media_prefix by looking at the <script>s in the + // current document and finding the URL of *this* module. + var scripts = document.getElementsByTagName('script'); + for (var i=0; i<scripts.length; i++) { + if (scripts[i].src.match(/DateTimeShortcuts/)) { + var idx = scripts[i].src.indexOf('js/admin/DateTimeShortcuts'); + DateTimeShortcuts.admin_media_prefix = scripts[i].src.substring(0, idx); + break; + } + } + + var inputs = document.getElementsByTagName('input'); + for (i=0; i<inputs.length; i++) { + var inp = inputs[i]; + if (inp.getAttribute('type') == 'text' && inp.className.match(/vTimeField/)) { + DateTimeShortcuts.addClock(inp); + } + else if (inp.getAttribute('type') == 'text' && inp.className.match(/vDateField/)) { + DateTimeShortcuts.addCalendar(inp); + } + } + }, + // Add clock widget to a given field + addClock: function(inp) { + var num = DateTimeShortcuts.clockInputs.length; + DateTimeShortcuts.clockInputs[num] = inp; + + // Shortcut links (clock icon and "Now" link) + var shortcuts_span = document.createElement('span'); + inp.parentNode.insertBefore(shortcuts_span, inp.nextSibling); + var now_link = document.createElement('a'); + now_link.setAttribute('href', "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", new Date().getHourMinuteSecond());"); + now_link.appendChild(document.createTextNode(gettext('Now'))); + var clock_link = document.createElement('a'); + clock_link.setAttribute('href', 'javascript:DateTimeShortcuts.openClock(' + num + ');'); + clock_link.id = DateTimeShortcuts.clockLinkName + num; + quickElement('img', clock_link, '', 'src', DateTimeShortcuts.admin_media_prefix + 'img/admin/icon_clock.gif', 'alt', gettext('Clock')); + shortcuts_span.appendChild(document.createTextNode('\240')); + shortcuts_span.appendChild(now_link); + shortcuts_span.appendChild(document.createTextNode('\240|\240')); + shortcuts_span.appendChild(clock_link); + + // Create clock link div + // + // Markup looks like: + // <div id="clockbox1" class="clockbox module"> + // <h2>Choose a time</h2> + // <ul class="timelist"> + // <li><a href="#">Now</a></li> + // <li><a href="#">Midnight</a></li> + // <li><a href="#">6 a.m.</a></li> + // <li><a href="#">Noon</a></li> + // </ul> + // <p class="calendar-cancel"><a href="#">Cancel</a></p> + // </div> + + var clock_box = document.createElement('div'); + clock_box.style.display = 'none'; + clock_box.style.position = 'absolute'; + clock_box.className = 'clockbox module'; + clock_box.setAttribute('id', DateTimeShortcuts.clockDivName + num); + document.body.appendChild(clock_box); + addEvent(clock_box, 'click', DateTimeShortcuts.cancelEventPropagation); + + quickElement('h2', clock_box, gettext('Choose a time')); + time_list = quickElement('ul', clock_box, ''); + time_list.className = 'timelist'; + quickElement("a", quickElement("li", time_list, ""), gettext("Now"), "href", "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", new Date().getHourMinuteSecond());") + quickElement("a", quickElement("li", time_list, ""), gettext("Midnight"), "href", "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", '00:00:00');") + quickElement("a", quickElement("li", time_list, ""), gettext("6 a.m."), "href", "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", '06:00:00');") + quickElement("a", quickElement("li", time_list, ""), gettext("Noon"), "href", "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", '12:00:00');") + + cancel_p = quickElement('p', clock_box, ''); + cancel_p.className = 'calendar-cancel'; + quickElement('a', cancel_p, gettext('Cancel'), 'href', 'javascript:DateTimeShortcuts.dismissClock(' + num + ');'); + }, + openClock: function(num) { + var clock_box = document.getElementById(DateTimeShortcuts.clockDivName+num) + var clock_link = document.getElementById(DateTimeShortcuts.clockLinkName+num) + + // Recalculate the clockbox position + // is it left-to-right or right-to-left layout ? + if (getStyle(document.body,'direction')!='rtl') { + clock_box.style.left = findPosX(clock_link) + 17 + 'px'; + } + else { + // since style's width is in em, it'd be tough to calculate + // px value of it. let's use an estimated px for now + // TODO: IE returns wrong value for findPosX when in rtl mode + // (it returns as it was left aligned), needs to be fixed. + clock_box.style.left = findPosX(clock_link) - 110 + 'px'; + } + clock_box.style.top = findPosY(clock_link) - 30 + 'px'; + + // Show the clock box + clock_box.style.display = 'block'; + addEvent(window.document, 'click', function() { DateTimeShortcuts.dismissClock(num); return true; }); + }, + dismissClock: function(num) { + document.getElementById(DateTimeShortcuts.clockDivName + num).style.display = 'none'; + window.document.onclick = null; + }, + handleClockQuicklink: function(num, val) { + DateTimeShortcuts.clockInputs[num].value = val; + DateTimeShortcuts.dismissClock(num); + }, + // Add calendar widget to a given field. + addCalendar: function(inp) { + var num = DateTimeShortcuts.calendars.length; + + DateTimeShortcuts.calendarInputs[num] = inp; + + // Shortcut links (calendar icon and "Today" link) + var shortcuts_span = document.createElement('span'); + inp.parentNode.insertBefore(shortcuts_span, inp.nextSibling); + var today_link = document.createElement('a'); + today_link.setAttribute('href', 'javascript:DateTimeShortcuts.handleCalendarQuickLink(' + num + ', 0);'); + today_link.appendChild(document.createTextNode(gettext('Today'))); + var cal_link = document.createElement('a'); + cal_link.setAttribute('href', 'javascript:DateTimeShortcuts.openCalendar(' + num + ');'); + cal_link.id = DateTimeShortcuts.calendarLinkName + num; + quickElement('img', cal_link, '', 'src', DateTimeShortcuts.admin_media_prefix + 'img/admin/icon_calendar.gif', 'alt', gettext('Calendar')); + shortcuts_span.appendChild(document.createTextNode('\240')); + shortcuts_span.appendChild(today_link); + shortcuts_span.appendChild(document.createTextNode('\240|\240')); + shortcuts_span.appendChild(cal_link); + + // Create calendarbox div. + // + // Markup looks like: + // + // <div id="calendarbox3" class="calendarbox module"> + // <h2> + // <a href="#" class="link-previous">‹</a> + // <a href="#" class="link-next">›</a> February 2003 + // </h2> + // <div class="calendar" id="calendarin3"> + // <!-- (cal) --> + // </div> + // <div class="calendar-shortcuts"> + // <a href="#">Yesterday</a> | <a href="#">Today</a> | <a href="#">Tomorrow</a> + // </div> + // <p class="calendar-cancel"><a href="#">Cancel</a></p> + // </div> + var cal_box = document.createElement('div'); + cal_box.style.display = 'none'; + cal_box.style.position = 'absolute'; + cal_box.className = 'calendarbox module'; + cal_box.setAttribute('id', DateTimeShortcuts.calendarDivName1 + num); + document.body.appendChild(cal_box); + addEvent(cal_box, 'click', DateTimeShortcuts.cancelEventPropagation); + + // next-prev links + var cal_nav = quickElement('div', cal_box, ''); + var cal_nav_prev = quickElement('a', cal_nav, '<', 'href', 'javascript:DateTimeShortcuts.drawPrev('+num+');'); + cal_nav_prev.className = 'calendarnav-previous'; + var cal_nav_next = quickElement('a', cal_nav, '>', 'href', 'javascript:DateTimeShortcuts.drawNext('+num+');'); + cal_nav_next.className = 'calendarnav-next'; + + // main box + var cal_main = quickElement('div', cal_box, '', 'id', DateTimeShortcuts.calendarDivName2 + num); + cal_main.className = 'calendar'; + DateTimeShortcuts.calendars[num] = new Calendar(DateTimeShortcuts.calendarDivName2 + num, DateTimeShortcuts.handleCalendarCallback(num)); + DateTimeShortcuts.calendars[num].drawCurrent(); + + // calendar shortcuts + var shortcuts = quickElement('div', cal_box, ''); + shortcuts.className = 'calendar-shortcuts'; + quickElement('a', shortcuts, gettext('Yesterday'), 'href', 'javascript:DateTimeShortcuts.handleCalendarQuickLink(' + num + ', -1);'); + shortcuts.appendChild(document.createTextNode('\240|\240')); + quickElement('a', shortcuts, gettext('Today'), 'href', 'javascript:DateTimeShortcuts.handleCalendarQuickLink(' + num + ', 0);'); + shortcuts.appendChild(document.createTextNode('\240|\240')); + quickElement('a', shortcuts, gettext('Tomorrow'), 'href', 'javascript:DateTimeShortcuts.handleCalendarQuickLink(' + num + ', +1);'); + + // cancel bar + var cancel_p = quickElement('p', cal_box, ''); + cancel_p.className = 'calendar-cancel'; + quickElement('a', cancel_p, gettext('Cancel'), 'href', 'javascript:DateTimeShortcuts.dismissCalendar(' + num + ');'); + }, + openCalendar: function(num) { + var cal_box = document.getElementById(DateTimeShortcuts.calendarDivName1+num) + var cal_link = document.getElementById(DateTimeShortcuts.calendarLinkName+num) + var inp = DateTimeShortcuts.calendarInputs[num]; + + // Determine if the current value in the input has a valid date. + // If so, draw the calendar with that date's year and month. + if (inp.value) { + var date_parts = inp.value.split('-'); + var year = date_parts[0]; + var month = parseFloat(date_parts[1]); + if (year.match(/\d\d\d\d/) && month >= 1 && month <= 12) { + DateTimeShortcuts.calendars[num].drawDate(month, year); + } + } + + + // Recalculate the clockbox position + // is it left-to-right or right-to-left layout ? + if (getStyle(document.body,'direction')!='rtl') { + cal_box.style.left = findPosX(cal_link) + 17 + 'px'; + } + else { + // since style's width is in em, it'd be tough to calculate + // px value of it. let's use an estimated px for now + // TODO: IE returns wrong value for findPosX when in rtl mode + // (it returns as it was left aligned), needs to be fixed. + cal_box.style.left = findPosX(cal_link) - 180 + 'px'; + } + cal_box.style.top = findPosY(cal_link) - 75 + 'px'; + + cal_box.style.display = 'block'; + addEvent(window.document, 'click', function() { DateTimeShortcuts.dismissCalendar(num); return true; }); + }, + dismissCalendar: function(num) { + document.getElementById(DateTimeShortcuts.calendarDivName1+num).style.display = 'none'; + window.document.onclick = null; + }, + drawPrev: function(num) { + DateTimeShortcuts.calendars[num].drawPreviousMonth(); + }, + drawNext: function(num) { + DateTimeShortcuts.calendars[num].drawNextMonth(); + }, + handleCalendarCallback: function(num) { + return "function(y, m, d) { DateTimeShortcuts.calendarInputs["+num+"].value = y+'-'+(m<10?'0':'')+m+'-'+(d<10?'0':'')+d; document.getElementById(DateTimeShortcuts.calendarDivName1+"+num+").style.display='none';}"; + }, + handleCalendarQuickLink: function(num, offset) { + var d = new Date(); + d.setDate(d.getDate() + offset) + DateTimeShortcuts.calendarInputs[num].value = d.getISODate(); + DateTimeShortcuts.dismissCalendar(num); + }, + cancelEventPropagation: function(e) { + if (!e) e = window.event; + e.cancelBubble = true; + if (e.stopPropagation) e.stopPropagation(); + } +} + +addEvent(window, 'load', DateTimeShortcuts.init);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/media/django/js/admin/RelatedObjectLookups.js Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,92 @@ +// Handles related-objects functionality: lookup link for raw_id_fields +// and Add Another links. + +function html_unescape(text) { + // Unescape a string that was escaped using django.utils.html.escape. + text = text.replace(/</g, '<'); + text = text.replace(/>/g, '>'); + text = text.replace(/"/g, '"'); + text = text.replace(/'/g, "'"); + text = text.replace(/&/g, '&'); + return text; +} + +// IE doesn't accept periods or dashes in the window name, but the element IDs +// we use to generate popup window names may contain them, therefore we map them +// to allowed characters in a reversible way so that we can locate the correct +// element when the popup window is dismissed. +function id_to_windowname(text) { + text = text.replace(/\./g, '__dot__'); + text = text.replace(/\-/g, '__dash__'); + return text; +} + +function windowname_to_id(text) { + text = text.replace(/__dot__/g, '.'); + text = text.replace(/__dash__/g, '-'); + return text; +} + +function showRelatedObjectLookupPopup(triggeringLink) { + var name = triggeringLink.id.replace(/^lookup_/, ''); + name = id_to_windowname(name); + var href; + if (triggeringLink.href.search(/\?/) >= 0) { + href = triggeringLink.href + '&pop=1'; + } else { + href = triggeringLink.href + '?pop=1'; + } + var win = window.open(href, name, 'height=500,width=800,resizable=yes,scrollbars=yes'); + win.focus(); + return false; +} + +function dismissRelatedLookupPopup(win, chosenId) { + var name = windowname_to_id(win.name); + var elem = document.getElementById(name); + if (elem.className.indexOf('vManyToManyRawIdAdminField') != -1 && elem.value) { + elem.value += ',' + chosenId; + } else { + document.getElementById(name).value = chosenId; + } + win.close(); +} + +function showAddAnotherPopup(triggeringLink) { + var name = triggeringLink.id.replace(/^add_/, ''); + name = id_to_windowname(name); + href = triggeringLink.href + if (href.indexOf('?') == -1) { + href += '?_popup=1'; + } else { + href += '&_popup=1'; + } + var win = window.open(href, name, 'height=500,width=800,resizable=yes,scrollbars=yes'); + win.focus(); + return false; +} + +function dismissAddAnotherPopup(win, newId, newRepr) { + // newId and newRepr are expected to have previously been escaped by + // django.utils.html.escape. + newId = html_unescape(newId); + newRepr = html_unescape(newRepr); + var name = windowname_to_id(win.name); + var elem = document.getElementById(name); + if (elem) { + if (elem.nodeName == 'SELECT') { + var o = new Option(newRepr, newId); + elem.options[elem.options.length] = o; + o.selected = true; + } else if (elem.nodeName == 'INPUT') { + elem.value = newId; + } + } else { + var toId = name + "_to"; + elem = document.getElementById(toId); + var o = new Option(newRepr, newId); + SelectBox.add_to_cache(toId, o); + SelectBox.redisplay(toId); + } + win.close(); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/media/django/js/admin/ordering.js Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,137 @@ +addEvent(window, 'load', reorder_init); + +var lis; +var top = 0; +var left = 0; +var height = 30; + +function reorder_init() { + lis = document.getElementsBySelector('ul#orderthese li'); + var input = document.getElementsBySelector('input[name=order_]')[0]; + setOrder(input.value.split(',')); + input.disabled = true; + draw(); + // Now initialise the dragging behaviour + var limit = (lis.length - 1) * height; + for (var i = 0; i < lis.length; i++) { + var li = lis[i]; + var img = document.getElementById('handle'+li.id); + li.style.zIndex = 1; + Drag.init(img, li, left + 10, left + 10, top + 10, top + 10 + limit); + li.onDragStart = startDrag; + li.onDragEnd = endDrag; + img.style.cursor = 'move'; + } +} + +function submitOrderForm() { + var inputOrder = document.getElementsBySelector('input[name=order_]')[0]; + inputOrder.value = getOrder(); + inputOrder.disabled=false; +} + +function startDrag() { + this.style.zIndex = '10'; + this.className = 'dragging'; +} + +function endDrag(x, y) { + this.style.zIndex = '1'; + this.className = ''; + // Work out how far along it has been dropped, using x co-ordinate + var oldIndex = this.index; + var newIndex = Math.round((y - 10 - top) / height); + // 'Snap' to the correct position + this.style.top = (10 + top + newIndex * height) + 'px'; + this.index = newIndex; + moveItem(oldIndex, newIndex); +} + +function moveItem(oldIndex, newIndex) { + // Swaps two items, adjusts the index and left co-ord for all others + if (oldIndex == newIndex) { + return; // Nothing to swap; + } + var direction, lo, hi; + if (newIndex > oldIndex) { + lo = oldIndex; + hi = newIndex; + direction = -1; + } else { + direction = 1; + hi = oldIndex; + lo = newIndex; + } + var lis2 = new Array(); // We will build the new order in this array + for (var i = 0; i < lis.length; i++) { + if (i < lo || i > hi) { + // Position of items not between the indexes is unaffected + lis2[i] = lis[i]; + continue; + } else if (i == newIndex) { + lis2[i] = lis[oldIndex]; + continue; + } else { + // Item is between the two indexes - move it along 1 + lis2[i] = lis[i - direction]; + } + } + // Re-index everything + reIndex(lis2); + lis = lis2; + draw(); +// document.getElementById('hiddenOrder').value = getOrder(); + document.getElementsBySelector('input[name=order_]')[0].value = getOrder(); +} + +function reIndex(lis) { + for (var i = 0; i < lis.length; i++) { + lis[i].index = i; + } +} + +function draw() { + for (var i = 0; i < lis.length; i++) { + var li = lis[i]; + li.index = i; + li.style.position = 'absolute'; + li.style.left = (10 + left) + 'px'; + li.style.top = (10 + top + (i * height)) + 'px'; + } +} + +function getOrder() { + var order = new Array(lis.length); + for (var i = 0; i < lis.length; i++) { + order[i] = lis[i].id.substring(1, 100); + } + return order.join(','); +} + +function setOrder(id_list) { + /* Set the current order to match the lsit of IDs */ + var temp_lis = new Array(); + for (var i = 0; i < id_list.length; i++) { + var id = 'p' + id_list[i]; + temp_lis[temp_lis.length] = document.getElementById(id); + } + reIndex(temp_lis); + lis = temp_lis; + draw(); +} + +function addEvent(elm, evType, fn, useCapture) +// addEvent and removeEvent +// cross-browser event handling for IE5+, NS6 and Mozilla +// By Scott Andrew +{ + if (elm.addEventListener){ + elm.addEventListener(evType, fn, useCapture); + return true; + } else if (elm.attachEvent){ + var r = elm.attachEvent("on"+evType, fn); + return r; + } else { + elm['on'+evType] = fn; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/media/django/js/calendar.js Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,143 @@ +/* +calendar.js - Calendar functions by Adrian Holovaty +*/ + +function removeChildren(a) { // "a" is reference to an object + while (a.hasChildNodes()) a.removeChild(a.lastChild); +} + +// quickElement(tagType, parentReference, textInChildNode, [, attribute, attributeValue ...]); +function quickElement() { + var obj = document.createElement(arguments[0]); + if (arguments[2] != '' && arguments[2] != null) { + var textNode = document.createTextNode(arguments[2]); + obj.appendChild(textNode); + } + var len = arguments.length; + for (var i = 3; i < len; i += 2) { + obj.setAttribute(arguments[i], arguments[i+1]); + } + arguments[1].appendChild(obj); + return obj; +} + +// CalendarNamespace -- Provides a collection of HTML calendar-related helper functions +var CalendarNamespace = { + monthsOfYear: gettext('January February March April May June July August September October November December').split(' '), + daysOfWeek: gettext('S M T W T F S').split(' '), + isLeapYear: function(year) { + return (((year % 4)==0) && ((year % 100)!=0) || ((year % 400)==0)); + }, + getDaysInMonth: function(month,year) { + var days; + if (month==1 || month==3 || month==5 || month==7 || month==8 || month==10 || month==12) { + days = 31; + } + else if (month==4 || month==6 || month==9 || month==11) { + days = 30; + } + else if (month==2 && CalendarNamespace.isLeapYear(year)) { + days = 29; + } + else { + days = 28; + } + return days; + }, + draw: function(month, year, div_id, callback) { // month = 1-12, year = 1-9999 + month = parseInt(month); + year = parseInt(year); + var calDiv = document.getElementById(div_id); + removeChildren(calDiv); + var calTable = document.createElement('table'); + quickElement('caption', calTable, CalendarNamespace.monthsOfYear[month-1] + ' ' + year); + var tableBody = quickElement('tbody', calTable); + + // Draw days-of-week header + var tableRow = quickElement('tr', tableBody); + for (var i = 0; i < 7; i++) { + quickElement('th', tableRow, CalendarNamespace.daysOfWeek[i]); + } + + var startingPos = new Date(year, month-1, 1).getDay(); + var days = CalendarNamespace.getDaysInMonth(month, year); + + // Draw blanks before first of month + tableRow = quickElement('tr', tableBody); + for (var i = 0; i < startingPos; i++) { + var _cell = quickElement('td', tableRow, ' '); + _cell.style.backgroundColor = '#f3f3f3'; + } + + // Draw days of month + var currentDay = 1; + for (var i = startingPos; currentDay <= days; i++) { + if (i%7 == 0 && currentDay != 1) { + tableRow = quickElement('tr', tableBody); + } + var cell = quickElement('td', tableRow, ''); + quickElement('a', cell, currentDay, 'href', 'javascript:void(' + callback + '('+year+','+month+','+currentDay+'));'); + currentDay++; + } + + // Draw blanks after end of month (optional, but makes for valid code) + while (tableRow.childNodes.length < 7) { + var _cell = quickElement('td', tableRow, ' '); + _cell.style.backgroundColor = '#f3f3f3'; + } + + calDiv.appendChild(calTable); + } +} + +// Calendar -- A calendar instance +function Calendar(div_id, callback) { + // div_id (string) is the ID of the element in which the calendar will + // be displayed + // callback (string) is the name of a JavaScript function that will be + // called with the parameters (year, month, day) when a day in the + // calendar is clicked + this.div_id = div_id; + this.callback = callback; + this.today = new Date(); + this.currentMonth = this.today.getMonth() + 1; + this.currentYear = this.today.getFullYear(); +} +Calendar.prototype = { + drawCurrent: function() { + CalendarNamespace.draw(this.currentMonth, this.currentYear, this.div_id, this.callback); + }, + drawDate: function(month, year) { + this.currentMonth = month; + this.currentYear = year; + this.drawCurrent(); + }, + drawPreviousMonth: function() { + if (this.currentMonth == 1) { + this.currentMonth = 12; + this.currentYear--; + } + else { + this.currentMonth--; + } + this.drawCurrent(); + }, + drawNextMonth: function() { + if (this.currentMonth == 12) { + this.currentMonth = 1; + this.currentYear++; + } + else { + this.currentMonth++; + } + this.drawCurrent(); + }, + drawPreviousYear: function() { + this.currentYear--; + this.drawCurrent(); + }, + drawNextYear: function() { + this.currentYear++; + this.drawCurrent(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/media/django/js/core.js Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,176 @@ +// Core javascript helper functions + +// basic browser identification & version +var isOpera = (navigator.userAgent.indexOf("Opera")>=0) && parseFloat(navigator.appVersion); +var isIE = ((document.all) && (!isOpera)) && parseFloat(navigator.appVersion.split("MSIE ")[1].split(";")[0]); + +// Cross-browser event handlers. +function addEvent(obj, evType, fn) { + if (obj.addEventListener) { + obj.addEventListener(evType, fn, false); + return true; + } else if (obj.attachEvent) { + var r = obj.attachEvent("on" + evType, fn); + return r; + } else { + return false; + } +} + +function removeEvent(obj, evType, fn) { + if (obj.removeEventListener) { + obj.removeEventListener(evType, fn, false); + return true; + } else if (obj.detachEvent) { + obj.detachEvent("on" + evType, fn); + return true; + } else { + return false; + } +} + +// quickElement(tagType, parentReference, textInChildNode, [, attribute, attributeValue ...]); +function quickElement() { + var obj = document.createElement(arguments[0]); + if (arguments[2] != '' && arguments[2] != null) { + var textNode = document.createTextNode(arguments[2]); + obj.appendChild(textNode); + } + var len = arguments.length; + for (var i = 3; i < len; i += 2) { + obj.setAttribute(arguments[i], arguments[i+1]); + } + arguments[1].appendChild(obj); + return obj; +} + +// ---------------------------------------------------------------------------- +// Cross-browser xmlhttp object +// from http://jibbering.com/2002/4/httprequest.html +// ---------------------------------------------------------------------------- +var xmlhttp; +/*@cc_on @*/ +/*@if (@_jscript_version >= 5) + try { + xmlhttp = new ActiveXObject("Msxml2.XMLHTTP"); + } catch (e) { + try { + xmlhttp = new ActiveXObject("Microsoft.XMLHTTP"); + } catch (E) { + xmlhttp = false; + } + } +@else + xmlhttp = false; +@end @*/ +if (!xmlhttp && typeof XMLHttpRequest != 'undefined') { + xmlhttp = new XMLHttpRequest(); +} + +// ---------------------------------------------------------------------------- +// Find-position functions by PPK +// See http://www.quirksmode.org/js/findpos.html +// ---------------------------------------------------------------------------- +function findPosX(obj) { + var curleft = 0; + if (obj.offsetParent) { + while (obj.offsetParent) { + curleft += obj.offsetLeft - ((isOpera) ? 0 : obj.scrollLeft); + obj = obj.offsetParent; + } + // IE offsetParent does not include the top-level + if (isIE && obj.parentElement){ + curleft += obj.offsetLeft - obj.scrollLeft; + } + } else if (obj.x) { + curleft += obj.x; + } + return curleft; +} + +function findPosY(obj) { + var curtop = 0; + if (obj.offsetParent) { + while (obj.offsetParent) { + curtop += obj.offsetTop - ((isOpera) ? 0 : obj.scrollTop); + obj = obj.offsetParent; + } + // IE offsetParent does not include the top-level + if (isIE && obj.parentElement){ + curtop += obj.offsetTop - obj.scrollTop; + } + } else if (obj.y) { + curtop += obj.y; + } + return curtop; +} + +//----------------------------------------------------------------------------- +// Date object extensions +// ---------------------------------------------------------------------------- +Date.prototype.getCorrectYear = function() { + // Date.getYear() is unreliable -- + // see http://www.quirksmode.org/js/introdate.html#year + var y = this.getYear() % 100; + return (y < 38) ? y + 2000 : y + 1900; +} + +Date.prototype.getTwoDigitMonth = function() { + return (this.getMonth() < 9) ? '0' + (this.getMonth()+1) : (this.getMonth()+1); +} + +Date.prototype.getTwoDigitDate = function() { + return (this.getDate() < 10) ? '0' + this.getDate() : this.getDate(); +} + +Date.prototype.getTwoDigitHour = function() { + return (this.getHours() < 10) ? '0' + this.getHours() : this.getHours(); +} + +Date.prototype.getTwoDigitMinute = function() { + return (this.getMinutes() < 10) ? '0' + this.getMinutes() : this.getMinutes(); +} + +Date.prototype.getTwoDigitSecond = function() { + return (this.getSeconds() < 10) ? '0' + this.getSeconds() : this.getSeconds(); +} + +Date.prototype.getISODate = function() { + return this.getCorrectYear() + '-' + this.getTwoDigitMonth() + '-' + this.getTwoDigitDate(); +} + +Date.prototype.getHourMinute = function() { + return this.getTwoDigitHour() + ':' + this.getTwoDigitMinute(); +} + +Date.prototype.getHourMinuteSecond = function() { + return this.getTwoDigitHour() + ':' + this.getTwoDigitMinute() + ':' + this.getTwoDigitSecond(); +} + +// ---------------------------------------------------------------------------- +// String object extensions +// ---------------------------------------------------------------------------- +String.prototype.pad_left = function(pad_length, pad_string) { + var new_string = this; + for (var i = 0; new_string.length < pad_length; i++) { + new_string = pad_string + new_string; + } + return new_string; +} + +// ---------------------------------------------------------------------------- +// Get the computed style for and element +// ---------------------------------------------------------------------------- +function getStyle(oElm, strCssRule){ + var strValue = ""; + if(document.defaultView && document.defaultView.getComputedStyle){ + strValue = document.defaultView.getComputedStyle(oElm, "").getPropertyValue(strCssRule); + } + else if(oElm.currentStyle){ + strCssRule = strCssRule.replace(/\-(\w)/g, function (strMatch, p1){ + return p1.toUpperCase(); + }); + strValue = oElm.currentStyle[strCssRule]; + } + return strValue; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/media/django/js/dateparse.js Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,233 @@ +/* 'Magic' date parsing, by Simon Willison (6th October 2003) + http://simon.incutio.com/archive/2003/10/06/betterDateInput + Adapted for 6newslawrence.com, 28th January 2004 +*/ + +/* Finds the index of the first occurence of item in the array, or -1 if not found */ +if (typeof Array.prototype.indexOf == 'undefined') { + Array.prototype.indexOf = function(item) { + var len = this.length; + for (var i = 0; i < len; i++) { + if (this[i] == item) { + return i; + } + } + return -1; + }; +} +/* Returns an array of items judged 'true' by the passed in test function */ +if (typeof Array.prototype.filter == 'undefined') { + Array.prototype.filter = function(test) { + var matches = []; + var len = this.length; + for (var i = 0; i < len; i++) { + if (test(this[i])) { + matches[matches.length] = this[i]; + } + } + return matches; + }; +} + +var monthNames = gettext("January February March April May June July August September October November December").split(" "); +var weekdayNames = gettext("Sunday Monday Tuesday Wednesday Thursday Friday Saturday").split(" "); + +/* Takes a string, returns the index of the month matching that string, throws + an error if 0 or more than 1 matches +*/ +function parseMonth(month) { + var matches = monthNames.filter(function(item) { + return new RegExp("^" + month, "i").test(item); + }); + if (matches.length == 0) { + throw new Error("Invalid month string"); + } + if (matches.length > 1) { + throw new Error("Ambiguous month"); + } + return monthNames.indexOf(matches[0]); +} +/* Same as parseMonth but for days of the week */ +function parseWeekday(weekday) { + var matches = weekdayNames.filter(function(item) { + return new RegExp("^" + weekday, "i").test(item); + }); + if (matches.length == 0) { + throw new Error("Invalid day string"); + } + if (matches.length > 1) { + throw new Error("Ambiguous weekday"); + } + return weekdayNames.indexOf(matches[0]); +} + +/* Array of objects, each has 're', a regular expression and 'handler', a + function for creating a date from something that matches the regular + expression. Handlers may throw errors if string is unparseable. +*/ +var dateParsePatterns = [ + // Today + { re: /^tod/i, + handler: function() { + return new Date(); + } + }, + // Tomorrow + { re: /^tom/i, + handler: function() { + var d = new Date(); + d.setDate(d.getDate() + 1); + return d; + } + }, + // Yesterday + { re: /^yes/i, + handler: function() { + var d = new Date(); + d.setDate(d.getDate() - 1); + return d; + } + }, + // 4th + { re: /^(\d{1,2})(st|nd|rd|th)?$/i, + handler: function(bits) { + var d = new Date(); + d.setDate(parseInt(bits[1], 10)); + return d; + } + }, + // 4th Jan + { re: /^(\d{1,2})(?:st|nd|rd|th)? (\w+)$/i, + handler: function(bits) { + var d = new Date(); + d.setDate(parseInt(bits[1], 10)); + d.setMonth(parseMonth(bits[2])); + return d; + } + }, + // 4th Jan 2003 + { re: /^(\d{1,2})(?:st|nd|rd|th)? (\w+),? (\d{4})$/i, + handler: function(bits) { + var d = new Date(); + d.setDate(parseInt(bits[1], 10)); + d.setMonth(parseMonth(bits[2])); + d.setYear(bits[3]); + return d; + } + }, + // Jan 4th + { re: /^(\w+) (\d{1,2})(?:st|nd|rd|th)?$/i, + handler: function(bits) { + var d = new Date(); + d.setDate(parseInt(bits[2], 10)); + d.setMonth(parseMonth(bits[1])); + return d; + } + }, + // Jan 4th 2003 + { re: /^(\w+) (\d{1,2})(?:st|nd|rd|th)?,? (\d{4})$/i, + handler: function(bits) { + var d = new Date(); + d.setDate(parseInt(bits[2], 10)); + d.setMonth(parseMonth(bits[1])); + d.setYear(bits[3]); + return d; + } + }, + // next Tuesday - this is suspect due to weird meaning of "next" + { re: /^next (\w+)$/i, + handler: function(bits) { + var d = new Date(); + var day = d.getDay(); + var newDay = parseWeekday(bits[1]); + var addDays = newDay - day; + if (newDay <= day) { + addDays += 7; + } + d.setDate(d.getDate() + addDays); + return d; + } + }, + // last Tuesday + { re: /^last (\w+)$/i, + handler: function(bits) { + throw new Error("Not yet implemented"); + } + }, + // mm/dd/yyyy (American style) + { re: /(\d{1,2})\/(\d{1,2})\/(\d{4})/, + handler: function(bits) { + var d = new Date(); + d.setYear(bits[3]); + d.setDate(parseInt(bits[2], 10)); + d.setMonth(parseInt(bits[1], 10) - 1); // Because months indexed from 0 + return d; + } + }, + // yyyy-mm-dd (ISO style) + { re: /(\d{4})-(\d{1,2})-(\d{1,2})/, + handler: function(bits) { + var d = new Date(); + d.setYear(parseInt(bits[1])); + d.setMonth(parseInt(bits[2], 10) - 1); + d.setDate(parseInt(bits[3], 10)); + return d; + } + }, +]; + +function parseDateString(s) { + for (var i = 0; i < dateParsePatterns.length; i++) { + var re = dateParsePatterns[i].re; + var handler = dateParsePatterns[i].handler; + var bits = re.exec(s); + if (bits) { + return handler(bits); + } + } + throw new Error("Invalid date string"); +} + +function fmt00(x) { + // fmt00: Tags leading zero onto numbers 0 - 9. + // Particularly useful for displaying results from Date methods. + // + if (Math.abs(parseInt(x)) < 10){ + x = "0"+ Math.abs(x); + } + return x; +} + +function parseDateStringISO(s) { + try { + var d = parseDateString(s); + return d.getFullYear() + '-' + (fmt00(d.getMonth() + 1)) + '-' + fmt00(d.getDate()) + } + catch (e) { return s; } +} +function magicDate(input) { + var messagespan = input.id + 'Msg'; + try { + var d = parseDateString(input.value); + input.value = d.getFullYear() + '-' + (fmt00(d.getMonth() + 1)) + '-' + + fmt00(d.getDate()); + input.className = ''; + // Human readable date + if (document.getElementById(messagespan)) { + document.getElementById(messagespan).firstChild.nodeValue = d.toDateString(); + document.getElementById(messagespan).className = 'normal'; + } + } + catch (e) { + input.className = 'error'; + var message = e.message; + // Fix for IE6 bug + if (message.indexOf('is null or not an object') > -1) { + message = 'Invalid date string'; + } + if (document.getElementById(messagespan)) { + document.getElementById(messagespan).firstChild.nodeValue = message; + document.getElementById(messagespan).className = 'error'; + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/media/django/js/getElementsBySelector.js Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,167 @@ +/* document.getElementsBySelector(selector) + - returns an array of element objects from the current document + matching the CSS selector. Selectors can contain element names, + class names and ids and can be nested. For example: + + elements = document.getElementsBySelect('div#main p a.external') + + Will return an array of all 'a' elements with 'external' in their + class attribute that are contained inside 'p' elements that are + contained inside the 'div' element which has id="main" + + New in version 0.4: Support for CSS2 and CSS3 attribute selectors: + See http://www.w3.org/TR/css3-selectors/#attribute-selectors + + Version 0.4 - Simon Willison, March 25th 2003 + -- Works in Phoenix 0.5, Mozilla 1.3, Opera 7, Internet Explorer 6, Internet Explorer 5 on Windows + -- Opera 7 fails +*/ + +function getAllChildren(e) { + // Returns all children of element. Workaround required for IE5/Windows. Ugh. + return e.all ? e.all : e.getElementsByTagName('*'); +} + +document.getElementsBySelector = function(selector) { + // Attempt to fail gracefully in lesser browsers + if (!document.getElementsByTagName) { + return new Array(); + } + // Split selector in to tokens + var tokens = selector.split(' '); + var currentContext = new Array(document); + for (var i = 0; i < tokens.length; i++) { + token = tokens[i].replace(/^\s+/,'').replace(/\s+$/,'');; + if (token.indexOf('#') > -1) { + // Token is an ID selector + var bits = token.split('#'); + var tagName = bits[0]; + var id = bits[1]; + var element = document.getElementById(id); + if (tagName && element.nodeName.toLowerCase() != tagName) { + // tag with that ID not found, return false + return new Array(); + } + // Set currentContext to contain just this element + currentContext = new Array(element); + continue; // Skip to next token + } + if (token.indexOf('.') > -1) { + // Token contains a class selector + var bits = token.split('.'); + var tagName = bits[0]; + var className = bits[1]; + if (!tagName) { + tagName = '*'; + } + // Get elements matching tag, filter them for class selector + var found = new Array; + var foundCount = 0; + for (var h = 0; h < currentContext.length; h++) { + var elements; + if (tagName == '*') { + elements = getAllChildren(currentContext[h]); + } else { + try { + elements = currentContext[h].getElementsByTagName(tagName); + } + catch(e) { + elements = []; + } + } + for (var j = 0; j < elements.length; j++) { + found[foundCount++] = elements[j]; + } + } + currentContext = new Array; + var currentContextIndex = 0; + for (var k = 0; k < found.length; k++) { + if (found[k].className && found[k].className.match(new RegExp('\\b'+className+'\\b'))) { + currentContext[currentContextIndex++] = found[k]; + } + } + continue; // Skip to next token + } + // Code to deal with attribute selectors + if (token.match(/^(\w*)\[(\w+)([=~\|\^\$\*]?)=?"?([^\]"]*)"?\]$/)) { + var tagName = RegExp.$1; + var attrName = RegExp.$2; + var attrOperator = RegExp.$3; + var attrValue = RegExp.$4; + if (!tagName) { + tagName = '*'; + } + // Grab all of the tagName elements within current context + var found = new Array; + var foundCount = 0; + for (var h = 0; h < currentContext.length; h++) { + var elements; + if (tagName == '*') { + elements = getAllChildren(currentContext[h]); + } else { + elements = currentContext[h].getElementsByTagName(tagName); + } + for (var j = 0; j < elements.length; j++) { + found[foundCount++] = elements[j]; + } + } + currentContext = new Array; + var currentContextIndex = 0; + var checkFunction; // This function will be used to filter the elements + switch (attrOperator) { + case '=': // Equality + checkFunction = function(e) { return (e.getAttribute(attrName) == attrValue); }; + break; + case '~': // Match one of space seperated words + checkFunction = function(e) { return (e.getAttribute(attrName).match(new RegExp('\\b'+attrValue+'\\b'))); }; + break; + case '|': // Match start with value followed by optional hyphen + checkFunction = function(e) { return (e.getAttribute(attrName).match(new RegExp('^'+attrValue+'-?'))); }; + break; + case '^': // Match starts with value + checkFunction = function(e) { return (e.getAttribute(attrName).indexOf(attrValue) == 0); }; + break; + case '$': // Match ends with value - fails with "Warning" in Opera 7 + checkFunction = function(e) { return (e.getAttribute(attrName).lastIndexOf(attrValue) == e.getAttribute(attrName).length - attrValue.length); }; + break; + case '*': // Match ends with value + checkFunction = function(e) { return (e.getAttribute(attrName).indexOf(attrValue) > -1); }; + break; + default : + // Just test for existence of attribute + checkFunction = function(e) { return e.getAttribute(attrName); }; + } + currentContext = new Array; + var currentContextIndex = 0; + for (var k = 0; k < found.length; k++) { + if (checkFunction(found[k])) { + currentContext[currentContextIndex++] = found[k]; + } + } + // alert('Attribute Selector: '+tagName+' '+attrName+' '+attrOperator+' '+attrValue); + continue; // Skip to next token + } + // If we get here, token is JUST an element (not a class or ID selector) + tagName = token; + var found = new Array; + var foundCount = 0; + for (var h = 0; h < currentContext.length; h++) { + var elements = currentContext[h].getElementsByTagName(tagName); + for (var j = 0; j < elements.length; j++) { + found[foundCount++] = elements[j]; + } + } + currentContext = found; + } + return currentContext; +} + +/* That revolting regular expression explained +/^(\w+)\[(\w+)([=~\|\^\$\*]?)=?"?([^\]"]*)"?\]$/ + \---/ \---/\-------------/ \-------/ + | | | | + | | | The value + | | ~,|,^,$,* or = + | Attribute + Tag +*/
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/media/django/js/timeparse.js Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,94 @@ +var timeParsePatterns = [ + // 9 + { re: /^\d{1,2}$/i, + handler: function(bits) { + if (bits[0].length == 1) { + return '0' + bits[0] + ':00'; + } else { + return bits[0] + ':00'; + } + } + }, + // 13:00 + { re: /^\d{2}[:.]\d{2}$/i, + handler: function(bits) { + return bits[0].replace('.', ':'); + } + }, + // 9:00 + { re: /^\d[:.]\d{2}$/i, + handler: function(bits) { + return '0' + bits[0].replace('.', ':'); + } + }, + // 3 am / 3 a.m. / 3am + { re: /^(\d+)\s*([ap])(?:.?m.?)?$/i, + handler: function(bits) { + var hour = parseInt(bits[1]); + if (hour == 12) { + hour = 0; + } + if (bits[2].toLowerCase() == 'p') { + if (hour == 12) { + hour = 0; + } + return (hour + 12) + ':00'; + } else { + if (hour < 10) { + return '0' + hour + ':00'; + } else { + return hour + ':00'; + } + } + } + }, + // 3.30 am / 3:15 a.m. / 3.00am + { re: /^(\d+)[.:](\d{2})\s*([ap]).?m.?$/i, + handler: function(bits) { + var hour = parseInt(bits[1]); + var mins = parseInt(bits[2]); + if (mins < 10) { + mins = '0' + mins; + } + if (hour == 12) { + hour = 0; + } + if (bits[3].toLowerCase() == 'p') { + if (hour == 12) { + hour = 0; + } + return (hour + 12) + ':' + mins; + } else { + if (hour < 10) { + return '0' + hour + ':' + mins; + } else { + return hour + ':' + mins; + } + } + } + }, + // noon + { re: /^no/i, + handler: function(bits) { + return '12:00'; + } + }, + // midnight + { re: /^mid/i, + handler: function(bits) { + return '00:00'; + } + } +]; + +function parseTimeString(s) { + for (var i = 0; i < timeParsePatterns.length; i++) { + var re = timeParsePatterns[i].re; + var handler = timeParsePatterns[i].handler; + var bits = re.exec(s); + if (bits) { + return handler(bits); + } + } + return s; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/media/django/js/urlify.js Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,140 @@ +var LATIN_MAP = { + 'À': 'A', 'Ã': 'A', 'Â': 'A', 'Ã': 'A', 'Ä': 'A', 'Ã…': 'A', 'Æ': 'AE', 'Ç': + 'C', 'È': 'E', 'É': 'E', 'Ê': 'E', 'Ë': 'E', 'ÃŒ': 'I', 'Ã': 'I', 'ÃŽ': 'I', + 'Ã': 'I', 'Ã': 'D', 'Ñ': 'N', 'Ã’': 'O', 'Ó': 'O', 'Ô': 'O', 'Õ': 'O', 'Ö': + 'O', 'Å': 'O', 'Ø': 'O', 'Ù': 'U', 'Ú': 'U', 'Û': 'U', 'Ãœ': 'U', 'Å°': 'U', + 'Ã': 'Y', 'Þ': 'TH', 'ß': 'ss', 'à ':'a', 'á':'a', 'â': 'a', 'ã': 'a', 'ä': + 'a', 'Ã¥': 'a', 'æ': 'ae', 'ç': 'c', 'è': 'e', 'é': 'e', 'ê': 'e', 'ë': 'e', + 'ì': 'i', 'Ã': 'i', 'î': 'i', 'ï': 'i', 'ð': 'd', 'ñ': 'n', 'ò': 'o', 'ó': + 'o', 'ô': 'o', 'õ': 'o', 'ö': 'o', 'Å‘': 'o', 'ø': 'o', 'ù': 'u', 'ú': 'u', + 'û': 'u', 'ü': 'u', 'ű': 'u', 'ý': 'y', 'þ': 'th', 'ÿ': 'y' +} +var LATIN_SYMBOLS_MAP = { + '©':'(c)' +} +var GREEK_MAP = { + 'α':'a', 'β':'b', 'γ':'g', 'δ':'d', 'ε':'e', 'ζ':'z', 'η':'h', 'θ':'8', + 'ι':'i', 'κ':'k', 'λ':'l', 'μ':'m', 'ν':'n', 'ξ':'3', 'ο':'o', 'Ï€':'p', + 'Ï':'r', 'σ':'s', 'Ï„':'t', 'Ï…':'y', 'φ':'f', 'χ':'x', 'ψ':'ps', 'ω':'w', + 'ά':'a', 'Î':'e', 'ί':'i', 'ÏŒ':'o', 'Ï':'y', 'ή':'h', 'ÏŽ':'w', 'Ï‚':'s', + 'ÏŠ':'i', 'ΰ':'y', 'Ï‹':'y', 'Î':'i', + 'Α':'A', 'Î’':'B', 'Γ':'G', 'Δ':'D', 'Ε':'E', 'Ζ':'Z', 'Η':'H', 'Θ':'8', + 'Ι':'I', 'Κ':'K', 'Λ':'L', 'Îœ':'M', 'Î':'N', 'Ξ':'3', 'Ο':'O', 'Î ':'P', + 'Ρ':'R', 'Σ':'S', 'Τ':'T', 'Î¥':'Y', 'Φ':'F', 'Χ':'X', 'Ψ':'PS', 'Ω':'W', + 'Ά':'A', 'Έ':'E', 'Ί':'I', 'ÎŒ':'O', 'ÎŽ':'Y', 'Ή':'H', 'Î':'W', 'Ϊ':'I', + 'Ϋ':'Y' +} +var TURKISH_MAP = { + 'ÅŸ':'s', 'Åž':'S', 'ı':'i', 'Ä°':'I', 'ç':'c', 'Ç':'C', 'ü':'u', 'Ãœ':'U', + 'ö':'o', 'Ö':'O', 'ÄŸ':'g', 'Äž':'G' +} +var RUSSIAN_MAP = { + 'а':'a', 'б':'b', 'в':'v', 'г':'g', 'д':'d', 'е':'e', 'Ñ‘':'yo', 'ж':'zh', + 'з':'z', 'и':'i', 'й':'j', 'к':'k', 'л':'l', 'м':'m', 'н':'n', 'о':'o', + 'п':'p', 'Ñ€':'r', 'Ñ':'s', 'Ñ‚':'t', 'у':'u', 'Ñ„':'f', 'Ñ…':'h', 'ц':'c', + 'ч':'ch', 'ш':'sh', 'щ':'sh', 'ÑŠ':'', 'Ñ‹':'y', 'ÑŒ':'', 'Ñ':'e', 'ÑŽ':'yu', + 'Ñ':'ya', + 'Ð':'A', 'Б':'B', 'Ð’':'V', 'Г':'G', 'Д':'D', 'Е':'E', 'Ð':'Yo', 'Ж':'Zh', + 'З':'Z', 'И':'I', 'Й':'J', 'К':'K', 'Л':'L', 'Ðœ':'M', 'Ð':'N', 'О':'O', + 'П':'P', 'Ð ':'R', 'С':'S', 'Т':'T', 'У':'U', 'Ф':'F', 'Ð¥':'H', 'Ц':'C', + 'Ч':'Ch', 'Ш':'Sh', 'Щ':'Sh', 'Ъ':'', 'Ы':'Y', 'Ь':'', 'Ð':'E', 'Ю':'Yu', + 'Я':'Ya' +} +var UKRAINIAN_MAP = { + 'Є':'Ye', 'І':'I', 'Ї':'Yi', 'Ò':'G', 'Ñ”':'ye', 'Ñ–':'i', 'Ñ—':'yi', 'Ò‘':'g' +} +var CZECH_MAP = { + 'Ä':'c', 'Ä':'d', 'Ä›':'e', 'ň': 'n', 'Å™':'r', 'Å¡':'s', 'Å¥':'t', 'ů':'u', + 'ž':'z', 'ÄŒ':'C', 'ÄŽ':'D', 'Äš':'E', 'Ň': 'N', 'Ř':'R', 'Å ':'S', 'Ť':'T', + 'Å®':'U', 'Ž':'Z' +} + +var POLISH_MAP = { + 'Ä…':'a', 'ć':'c', 'Ä™':'e', 'Å‚':'l', 'Å„':'n', 'ó':'o', 'Å›':'s', 'ź':'z', + 'ż':'z', 'Ä„':'A', 'Ć':'C', 'Ę':'e', 'Å':'L', 'Ń':'N', 'Ó':'o', 'Åš':'S', + 'Ź':'Z', 'Å»':'Z' +} + +var LATVIAN_MAP = { + 'Ä':'a', 'Ä':'c', 'Ä“':'e', 'Ä£':'g', 'Ä«':'i', 'Ä·':'k', 'ļ':'l', 'ņ':'n', + 'Å¡':'s', 'Å«':'u', 'ž':'z', 'Ä€':'A', 'ÄŒ':'C', 'Ä’':'E', 'Ä¢':'G', 'Ī':'i', + 'Ķ':'k', 'Ä»':'L', 'Å…':'N', 'Å ':'S', 'Ū':'u', 'Ž':'Z' +} + +var ALL_DOWNCODE_MAPS=new Array() +ALL_DOWNCODE_MAPS[0]=LATIN_MAP +ALL_DOWNCODE_MAPS[1]=LATIN_SYMBOLS_MAP +ALL_DOWNCODE_MAPS[2]=GREEK_MAP +ALL_DOWNCODE_MAPS[3]=TURKISH_MAP +ALL_DOWNCODE_MAPS[4]=RUSSIAN_MAP +ALL_DOWNCODE_MAPS[5]=UKRAINIAN_MAP +ALL_DOWNCODE_MAPS[6]=CZECH_MAP +ALL_DOWNCODE_MAPS[7]=POLISH_MAP +ALL_DOWNCODE_MAPS[8]=LATVIAN_MAP + +var Downcoder = new Object(); +Downcoder.Initialize = function() +{ + if (Downcoder.map) // already made + return ; + Downcoder.map ={} + Downcoder.chars = '' ; + for(var i in ALL_DOWNCODE_MAPS) + { + var lookup = ALL_DOWNCODE_MAPS[i] + for (var c in lookup) + { + Downcoder.map[c] = lookup[c] ; + Downcoder.chars += c ; + } + } + Downcoder.regex = new RegExp('[' + Downcoder.chars + ']|[^' + Downcoder.chars + ']+','g') ; +} + +downcode= function( slug ) +{ + Downcoder.Initialize() ; + var downcoded ="" + var pieces = slug.match(Downcoder.regex); + if(pieces) + { + for (var i = 0 ; i < pieces.length ; i++) + { + if (pieces[i].length == 1) + { + var mapped = Downcoder.map[pieces[i]] ; + if (mapped != null) + { + downcoded+=mapped; + continue ; + } + } + downcoded+=pieces[i]; + } + } + else + { + downcoded = slug; + } + return downcoded; +} + + +function URLify(s, num_chars) { + // changes, e.g., "Petty theft" to "petty_theft" + // remove all these words from the string before urlifying + s = downcode(s); + removelist = ["a", "an", "as", "at", "before", "but", "by", "for", "from", + "is", "in", "into", "like", "of", "off", "on", "onto", "per", + "since", "than", "the", "this", "that", "to", "up", "via", + "with"]; + r = new RegExp('\\b(' + removelist.join('|') + ')\\b', 'gi'); + s = s.replace(r, ''); + // if downcode doesn't hit, the char will be stripped here + s = s.replace(/[^-\w\s]/g, ''); // remove unneeded chars + s = s.replace(/^\s+|\s+$/g, ''); // trim leading/trailing spaces + s = s.replace(/[-\s]+/g, '-'); // convert spaces to hyphens + s = s.toLowerCase(); // convert to lowercase + return s.substring(0, num_chars);// trim to first num_chars chars +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/media/js/jquery.js Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,11 @@ +/* + * jQuery 1.2.4a - New Wave Javascript + * + * Copyright (c) 2008 John Resig (jquery.com) + * Dual licensed under the MIT (MIT-LICENSE.txt) + * and GPL (GPL-LICENSE.txt) licenses. + * + * $Date: 2008-04-09 21:17:07 -0400 (Wed, 09 Apr 2008) $ + * $Rev: 5225 $ + */ +eval(function(p,a,c,k,e,r){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('(J(){7(1d.4d)L w=1d.4d;L E=1d.4d=J(a,b){K 1D E.2k.54(a,b)};7(1d.$)L D=1d.$;1d.$=E;L u=/^[^<]*(<(.|\\s)+>)[^>]*$|^#(\\w+)$/;L G=/^.[^:#\\[\\.]*$/;E.1g=E.2k={54:J(d,b){d=d||T;7(d.15){6[0]=d;6.M=1;K 6}N 7(1u d=="2h"){L c=u.2U(d);7(c&&(c[1]||!b)){7(c[1])d=E.4f([c[1]],b);N{L a=T.5S(c[3]);7(a)7(a.2m!=c[3])K E().2u(d);N{6[0]=a;6.M=1;K 6}N d=[]}}N K 1D E(b).2u(d)}N 7(E.1q(d))K 1D E(T)[E.1g.21?"21":"43"](d);K 6.6Q(d.1j==1I&&d||(d.4y||d.M&&d!=1d&&!d.15&&d[0]!=10&&d[0].15)&&E.2I(d)||[d])},4y:"1.2.4a",86:J(){K 6.M},M:0,23:J(a){K a==10?E.2I(6):6[a]},2H:J(b){L a=E(b);a.5c=6;K a},6Q:J(a){6.M=0;1I.2k.1k.1h(6,a);K 6},R:J(a,b){K E.R(6,a,b)},51:J(b){L a=-1;6.R(J(i){7(6==b)a=i});K a},1G:J(c,a,b){L d=c;7(c.1j==4k)7(a==10)K 6.M&&E[b||"1G"](6[0],c)||10;N{d={};d[c]=a}K 6.R(J(i){Q(c 1o d)E.1G(b?6.W:6,c,E.1m(6,d[c],b,i,c))})},1l:J(b,a){7((b==\'28\'||b==\'1S\')&&2J(a)<0)a=10;K 6.1G(b,a,"29")},1s:J(b){7(1u b!="3Z"&&b!=V)K 6.4H().3s((6[0]&&6[0].2t||T).5A(b));L a="";E.R(b||6,J(){E.R(6.3u,J(){7(6.15!=8)a+=6.15!=1?6.6T:E.1g.1s([6])})});K a},5v:J(b){7(6[0])E(b,6[0].2t).5N().3n(6[0]).2c(J(){L a=6;2a(a.1E)a=a.1E;K a}).3s(6);K 6},8w:J(a){K 6.R(J(){E(6).6J().5v(a)})},8p:J(a){K 6.R(J(){E(6).5v(a)})},3s:J(){K 6.3S(18,P,S,J(a){7(6.15==1)6.3j(a)})},6A:J(){K 6.3S(18,P,P,J(a){7(6.15==1)6.3n(a,6.1E)})},6x:J(){K 6.3S(18,S,S,J(a){6.1b.3n(a,6)})},5f:J(){K 6.3S(18,S,P,J(a){6.1b.3n(a,6.2D)})},3h:J(){K 6.5c||E([])},2u:J(b){L c=E.2c(6,J(a){K E.2u(b,a)});K 6.2H(/[^+>] [^+>]/.17(b)||b.1f("..")>-1?E.5e(c):c)},5N:J(e){L f=6.2c(J(){7(E.14.1c&&!E.3W(6)){L a=6.6h(P),56=T.3p("1y");56.3j(a);K E.4f([56.3d])[0]}N K 6.6h(P)});L d=f.2u("*").50().R(J(){7(6[F]!=10)6[F]=V});7(e===P)6.2u("*").50().R(J(i){7(6.15==3)K;L c=E.O(6,"36");Q(L a 1o c)Q(L b 1o c[a])E.16.1a(d[i],a,c[a][b],c[a][b].O)});K f},1B:J(b){K 6.2H(E.1q(b)&&E.3x(6,J(a,i){K b.1X(a,i)})||E.3b(b,6))},4S:J(b){7(b.1j==4k)7(G.17(b))K 6.2H(E.3b(b,6,P));N b=E.3b(b,6);L a=b.M&&b[b.M-1]!==10&&!b.15;K 6.1B(J(){K a?E.2W(6,b)<0:6!=b})},1a:J(a){K!a?6:6.2H(E.2P(6.23(),a.1j==4k?E(a).23():a.M!=10&&(!a.12||E.12(a,"3e"))?a:[a]))},3E:J(a){K a?E.3b(a,6).M>0:S},7s:J(a){K 6.3E("."+a)},5V:J(b){7(b==10){7(6.M){L c=6[0];7(E.12(c,"2q")){L e=c.3U,5P=[],11=c.11,2Y=c.U=="2q-2Y";7(e<0)K V;Q(L i=2Y?e:0,2f=2Y?e+1:11.M;i<2f;i++){L d=11[i];7(d.2x){b=E.14.1c&&!d.9K.1C.9B?d.1s:d.1C;7(2Y)K b;5P.1k(b)}}K 5P}N K(6[0].1C||"").1r(/\\r/g,"")}K 10}K 6.R(J(){7(6.15!=1)K;7(b.1j==1I&&/5D|5C/.17(6.U))6.3r=(E.2W(6.1C,b)>=0||E.2W(6.2X,b)>=0);N 7(E.12(6,"2q")){L a=b.1j==1I?b:[b];E("9b",6).R(J(){6.2x=(E.2W(6.1C,a)>=0||E.2W(6.1s,a)>=0)});7(!a.M)6.3U=-1}N 6.1C=b})},3o:J(a){K a==10?(6.M?6[0].3d:V):6.4H().3s(a)},6V:J(a){K 6.5f(a).1Q()},6U:J(i){K 6.2B(i,i+1)},2B:J(){K 6.2H(1I.2k.2B.1h(6,18))},2c:J(b){K 6.2H(E.2c(6,J(a,i){K b.1X(a,i,a)}))},50:J(){K 6.1a(6.5c)},O:J(d,b){L a=d.24(".");a[1]=a[1]?"."+a[1]:"";7(b==V){L c=6.5x("8S"+a[1]+"!",[a[0]]);7(c==10&&6.M)c=E.O(6[0],d);K c==V&&a[1]?6.O(a[0]):c}N K 6.1L("8N"+a[1]+"!",[a[0],b]).R(J(){E.O(6,d,b)})},38:J(a){K 6.R(J(){E.38(6,a)})},3S:J(g,f,h,d){L e=6.M>1,3k;K 6.R(J(){7(!3k){3k=E.4f(g,6.2t);7(h)3k.8E()}L b=6;7(f&&E.12(6,"1W")&&E.12(3k[0],"4B"))b=6.3X("1V")[0]||6.3j(6.2t.3p("1V"));L c=E([]);E.R(3k,J(){L a=e?E(6).5N(P)[0]:6;7(E.12(a,"1n")){c=c.1a(a)}N{7(a.15==1)c=c.1a(E("1n",a).1Q());d.1X(b,a)}});c.R(6K)})}};E.2k.54.2k=E.2k;J 6K(i,a){7(a.3V)E.3T({1e:a.3V,3l:S,1H:"1n"});N E.5o(a.1s||a.6H||a.3d||"");7(a.1b)a.1b.35(a)}E.1p=E.1g.1p=J(){L b=18[0]||{},i=1,M=18.M,4w=S,11;7(b.1j==8h){4w=b;b=18[1]||{};i=2}7(1u b!="3Z"&&1u b!="J")b={};7(M==1){b=6;i=0}Q(;i<M;i++)7((11=18[i])!=V)Q(L a 1o 11){7(b===11[a])6G;7(4w&&11[a]&&1u 11[a]=="3Z"&&b[a]&&!11[a].15)b[a]=E.1p(4w,b[a],11[a]);N 7(11[a]!=10)b[a]=11[a]}K b};L F="4d"+(1D 3Q()).3P(),6B=0,5j={};L H=/z-?51|8c-?89|1w|6v|85-?1S/i;E.1p({84:J(a){1d.$=D;7(a)1d.4d=w;K E},1q:J(a){K!!a&&1u a!="2h"&&!a.12&&a.1j!=1I&&/J/i.17(a+"")},3W:J(a){K a.1J&&!a.1i||a.26&&a.2t&&!a.2t.1i},5o:J(a){a=E.3g(a);7(a){L b=T.3X("6p")[0]||T.1J,1n=T.3p("1n");1n.U="1s/4q";7(E.14.1c)1n.1s=a;N 1n.3j(T.5A(a));b.3j(1n);b.35(1n)}},12:J(b,a){K b.12&&b.12.2F()==a.2F()},1T:{},O:J(c,d,b){c=c==1d?5j:c;L a=c[F];7(!a)a=c[F]=++6B;7(d&&!E.1T[a])E.1T[a]={};7(b!=10)E.1T[a][d]=b;K d?E.1T[a][d]:a},38:J(c,b){c=c==1d?5j:c;L a=c[F];7(b){7(E.1T[a]){2Q E.1T[a][b];b="";Q(b 1o E.1T[a])1P;7(!b)E.38(c)}}N{1U{2Q c[F]}1R(e){7(c.5a)c.5a(F)}2Q E.1T[a]}},R:J(c,a,b){7(b){7(c.M==10){Q(L d 1o c)7(a.1h(c[d],b)===S)1P}N Q(L i=0,M=c.M;i<M;i++)7(a.1h(c[i],b)===S)1P}N{7(c.M==10){Q(L d 1o c)7(a.1X(c[d],d,c[d])===S)1P}N Q(L i=0,M=c.M,1C=c[0];i<M&&a.1X(1C,i,1C)!==S;1C=c[++i]){}}K c},1m:J(b,a,c,i,d){7(E.1q(a))a=a.1X(b,i);K a&&a.1j==58&&c=="29"&&!H.17(d)?a+"2R":a},1t:{1a:J(c,b){E.R((b||"").24(/\\s+/),J(i,a){7(c.15==1&&!E.1t.3H(c.1t,a))c.1t+=(c.1t?" ":"")+a})},1Q:J(c,b){7(c.15==1)c.1t=b!=10?E.3x(c.1t.24(/\\s+/),J(a){K!E.1t.3H(b,a)}).6k(" "):""},3H:J(b,a){K E.2W(a,(b.1t||b).3G().24(/\\s+/))>-1}},6g:J(b,c,a){L e={};Q(L d 1o c){e[d]=b.W[d];b.W[d]=c[d]}a.1X(b);Q(L d 1o c)b.W[d]=e[d]},1l:J(d,e,c){7(e=="28"||e=="1S"){L b,40={3F:"55",53:"1Z",19:"46"},3t=e=="28"?["6a","69"]:["67","66"];J 4Z(){b=e=="28"?d.7S:d.7R;L a=0,2y=0;E.R(3t,J(){a+=2J(E.29(d,"4V"+6,P))||0;2y+=2J(E.29(d,"2y"+6+"3B",P))||0});b-=1Y.7P(a+2y)}7(E(d).3E(":4i"))4Z();N E.6g(d,40,4Z);K 1Y.2f(0,b)}K E.29(d,e,c)},29:J(e,k,j){L d;J 3w(b){7(!E.14.2b)K S;L a=T.4h.4R(b,V);K!a||a.4Q("3w")==""}7(k=="1w"&&E.14.1c){d=E.1G(e.W,"1w");K d==""?"1":d}7(E.14.2A&&k=="19"){L c=e.W.4P;e.W.4P="0 7A 7z";e.W.4P=c}7(k.1A(/4g/i))k=y;7(!j&&e.W&&e.W[k])d=e.W[k];N 7(T.4h&&T.4h.4R){7(k.1A(/4g/i))k="4g";k=k.1r(/([A-Z])/g,"-$1").2g();L h=T.4h.4R(e,V);7(h&&!3w(e))d=h.4Q(k);N{L f=[],2K=[];Q(L a=e;a&&3w(a);a=a.1b)2K.5i(a);Q(L i=0;i<2K.M;i++)7(3w(2K[i])){f[i]=2K[i].W.19;2K[i].W.19="46"}d=k=="19"&&f[2K.M-1]!=V?"2G":(h&&h.4Q(k))||"";Q(L i=0;i<f.M;i++)7(f[i]!=V)2K[i].W.19=f[i]}7(k=="1w"&&d=="")d="1"}N 7(e.4r){L g=k.1r(/\\-(\\w)/g,J(a,b){K b.2F()});d=e.4r[k]||e.4r[g];7(!/^\\d+(2R)?$/i.17(d)&&/^\\d/.17(d)){L l=e.W.25,3M=e.3M.25;e.3M.25=e.4r.25;e.W.25=d||0;d=e.W.ad+"2R";e.W.25=l;e.3M.25=3M}}K d},4f:J(l,h){L k=[];h=h||T;7(1u h.3p==\'10\')h=h.2t||h[0]&&h[0].2t||T;E.R(l,J(i,d){7(!d)K;7(d.1j==58)d=d.3G();7(1u d=="2h"){d=d.1r(/(<(\\w+)[^>]*?)\\/>/g,J(b,a,c){K c.1A(/^(ab|3Y|7o|a7|4K|7l|a2|3v|9Z|9X|9U)$/i)?b:a+"></"+c+">"});L f=E.3g(d).2g(),1y=h.3p("1y");L e=!f.1f("<9S")&&[1,"<2q 7h=\'7h\'>","</2q>"]||!f.1f("<9O")&&[1,"<7f>","</7f>"]||f.1A(/^<(9J|1V|9H|9D|9C)/)&&[1,"<1W>","</1W>"]||!f.1f("<4B")&&[2,"<1W><1V>","</1V></1W>"]||(!f.1f("<9A")||!f.1f("<9z"))&&[3,"<1W><1V><4B>","</4B></1V></1W>"]||!f.1f("<7o")&&[2,"<1W><1V></1V><74>","</74></1W>"]||E.14.1c&&[1,"1y<1y>","</1y>"]||[0,"",""];1y.3d=e[1]+d+e[2];2a(e[0]--)1y=1y.5E;7(E.14.1c){L g=!f.1f("<1W")&&f.1f("<1V")<0?1y.1E&&1y.1E.3u:e[1]=="<1W>"&&f.1f("<1V")<0?1y.3u:[];Q(L j=g.M-1;j>=0;--j)7(E.12(g[j],"1V")&&!g[j].3u.M)g[j].1b.35(g[j]);7(/^\\s/.17(d))1y.3n(h.5A(d.1A(/^\\s*/)[0]),1y.1E)}d=E.2I(1y.3u)}7(d.M===0&&(!E.12(d,"3e")&&!E.12(d,"2q")))K;7(d[0]==10||E.12(d,"3e")||d.11)k.1k(d);N k=E.2P(k,d)});K k},1G:J(d,e,c){7(!d||d.15==3||d.15==8)K 10;L f=E.3W(d)?{}:E.40;7(e=="2x"&&E.14.2b)d.1b.3U;7(f[e]){7(c!=10)d[f[e]]=c;K d[f[e]]}N 7(E.14.1c&&e=="W")K E.1G(d.W,"9w",c);N 7(c==10&&E.14.1c&&E.12(d,"3e")&&(e=="9t"||e=="9r"))K d.9p(e).6T;N 7(d.26){7(c!=10){7(e=="U"&&E.12(d,"4K")&&d.1b)6Z"U 9m 9l\'t 9j 9g";d.9d(e,""+c)}7(E.14.1c&&/6X|3V/.17(e)&&!E.3W(d))K d.4F(e,2);K d.4F(e)}N{7(e=="1w"&&E.14.1c){7(c!=10){d.6v=1;d.1B=(d.1B||"").1r(/6W\\([^)]*\\)/,"")+(2J(c).3G()=="9a"?"":"6W(1w="+c*70+")")}K d.1B&&d.1B.1f("1w=")>=0?(2J(d.1B.1A(/1w=([^)]*)/)[1])/70).3G():""}e=e.1r(/-([a-z])/98,J(a,b){K b.2F()});7(c!=10)d[e]=c;K d[e]}},3g:J(a){K(a||"").1r(/^\\s+|\\s+$/g,"")},2I:J(b){L a=[];7(b.1j!=1I)Q(L i=0,M=b.M;i<M;i++)a.1k(b[i]);N a=b.2B(0);K a},2W:J(b,a){Q(L i=0,M=a.M;i<M;i++)7(a[i]==b)K i;K-1},2P:J(a,b){7(E.14.1c){Q(L i=0;b[i];i++)7(b[i].15!=8)a.1k(b[i])}N Q(L i=0;b[i];i++)a.1k(b[i]);K a},5e:J(a){L c=[],2l={};1U{Q(L i=0,M=a.M;i<M;i++){L b=E.O(a[i]);7(!2l[b]){2l[b]=P;c.1k(a[i])}}}1R(e){c=a}K c},3x:J(c,a,d){L b=[];Q(L i=0,M=c.M;i<M;i++)7(!d&&a(c[i],i)||d&&!a(c[i],i))b.1k(c[i]);K b},2c:J(d,a){L c=[];Q(L i=0,M=d.M;i<M;i++){L b=a(d[i],i);7(b!==V&&b!=10){7(b.1j!=1I)b=[b];c=c.7d(b)}}K c}});L v=93.91.2g();E.14={5s:(v.1A(/.+(?:8Y|8X|8W|8U)[\\/: ]([\\d.]+)/)||[])[1],2b:/7g/.17(v),2A:/2A/.17(v),1c:/1c/.17(v)&&!/2A/.17(v),4b:/4b/.17(v)&&!/(8Q|7g)/.17(v)};L y=E.14.1c?"6R":"7i";E.1p({8M:!E.14.1c||T.6O=="7n",40:{"Q":"8J","8I":"1t","4g":y,7i:y,6R:y,3d:"3d",1t:"1t",1C:"1C",31:"31",3r:"3r",8G:"8F",2x:"2x",8D:"8C",3U:"3U",6M:"6M",26:"26",12:"12"}});E.R({6L:J(a){K a.1b},8B:J(a){K E.4A(a,"1b")},8A:J(a){K E.2T(a,2,"2D")},8z:J(a){K E.2T(a,2,"4z")},8y:J(a){K E.4A(a,"2D")},8x:J(a){K E.4A(a,"4z")},8v:J(a){K E.5p(a.1b.1E,a)},8u:J(a){K E.5p(a.1E)},6J:J(a){K E.12(a,"8t")?a.8s||a.8r.T:E.2I(a.3u)}},J(c,d){E.1g[c]=J(b){L a=E.2c(6,d);7(b&&1u b=="2h")a=E.3b(b,a);K 6.2H(E.5e(a))}});E.R({6I:"3s",8q:"6A",3n:"6x",8o:"5f",8n:"6V"},J(c,b){E.1g[c]=J(){L a=18;K 6.R(J(){Q(L i=0,M=a.M;i<M;i++)E(a[i])[b](6)})}});E.R({8m:J(a){E.1G(6,a,"");7(6.15==1)6.5a(a)},8l:J(a){E.1t.1a(6,a)},8k:J(a){E.1t.1Q(6,a)},8j:J(a){E.1t[E.1t.3H(6,a)?"1Q":"1a"](6,a)},1Q:J(a){7(!a||E.1B(a,[6]).r.M){E("*",6).1a(6).R(J(){E.16.1Q(6);E.38(6)});7(6.1b)6.1b.35(6)}},4H:J(){E(">*",6).1Q();2a(6.1E)6.35(6.1E)}},J(a,b){E.1g[a]=J(){K 6.R(b,18)}});E.R(["4x","3B"],J(i,c){L b=c.2g();E.1g[b]=J(a){K 6[0]==1d?E.14.2A&&T.1i["5n"+c]||E.14.2b&&1d["5m"+c]||T.6O=="7n"&&T.1J["5n"+c]||T.1i["5n"+c]:6[0]==T?1Y.2f(1Y.2f(T.1i["5l"+c],T.1J["5l"+c]),1Y.2f(T.1i["5k"+c],T.1J["5k"+c])):a==10?(6.M?E.1l(6[0],b):V):6.1l(b,a.1j==4k?a:a+"2R")}});L C=E.14.2b&&3R(E.14.5s)<8i?"(?:[\\\\w*4v-]|\\\\\\\\.)":"(?:[\\\\w\\8g-\\8f*4v-]|\\\\\\\\.)",6F=1D 4u("^>\\\\s*("+C+"+)"),6E=1D 4u("^("+C+"+)(#)("+C+"+)"),6D=1D 4u("^([#.]?)("+C+"*)");E.1p({6C:{"":J(a,i,m){K m[2]=="*"||E.12(a,m[2])},"#":J(a,i,m){K a.4F("2m")==m[2]},":":{8e:J(a,i,m){K i<m[3]-0},8d:J(a,i,m){K i>m[3]-0},2T:J(a,i,m){K m[3]-0==i},6U:J(a,i,m){K m[3]-0==i},3i:J(a,i){K i==0},3N:J(a,i,m,r){K i==r.M-1},6z:J(a,i){K i%2==0},6y:J(a,i){K i%2},"3i-4t":J(a){K a.1b.3X("*")[0]==a},"3N-4t":J(a){K E.2T(a.1b.5E,1,"4z")==a},"8b-4t":J(a){K!E.2T(a.1b.5E,2,"4z")},6L:J(a){K a.1E},4H:J(a){K!a.1E},8a:J(a,i,m){K(a.6H||a.88||E(a).1s()||"").1f(m[3])>=0},4i:J(a){K"1Z"!=a.U&&E.1l(a,"19")!="2G"&&E.1l(a,"53")!="1Z"},1Z:J(a){K"1Z"==a.U||E.1l(a,"19")=="2G"||E.1l(a,"53")=="1Z"},87:J(a){K!a.31},31:J(a){K a.31},3r:J(a){K a.3r},2x:J(a){K a.2x||E.1G(a,"2x")},1s:J(a){K"1s"==a.U},5D:J(a){K"5D"==a.U},5C:J(a){K"5C"==a.U},5h:J(a){K"5h"==a.U},3K:J(a){K"3K"==a.U},5g:J(a){K"5g"==a.U},6u:J(a){K"6u"==a.U},6t:J(a){K"6t"==a.U},2C:J(a){K"2C"==a.U||E.12(a,"2C")},4K:J(a){K/4K|2q|6s|2C/i.17(a.12)},3H:J(a,i,m){K E.2u(m[3],a).M},83:J(a){K/h\\d/i.17(a.12)},82:J(a){K E.3x(E.3J,J(b){K a==b.Y}).M}}},6q:[/^(\\[) *@?([\\w-]+) *([!*$^~=]*) *(\'?"?)(.*?)\\4 *\\]/,/^(:)([\\w-]+)\\("?\'?(.*?(\\(.*?\\))?[^(]*?)"?\'?\\)/,1D 4u("^([:.#]*)("+C+"+)")],3b:J(a,c,b){L d,2j=[];2a(a&&a!=d){d=a;L f=E.1B(a,c,b);a=f.t.1r(/^\\s*,\\s*/,"");2j=b?c=f.r:E.2P(2j,f.r)}K 2j},2u:J(t,p){7(1u t!="2h")K[t];7(p&&p.15!=1&&p.15!=9)K[];p=p||T;L d=[p],2l=[],3N,12;2a(t&&3N!=t){L r=[];3N=t;t=E.3g(t);L o=S;L g=6F;L m=g.2U(t);7(m){12=m[1].2F();Q(L i=0;d[i];i++)Q(L c=d[i].1E;c;c=c.2D)7(c.15==1&&(12=="*"||c.12.2F()==12))r.1k(c);d=r;t=t.1r(g,"");7(t.1f(" ")==0)6G;o=P}N{g=/^([>+~])\\s*(\\w*)/i;7((m=g.2U(t))!=V){r=[];L l={};12=m[2].2F();m=m[1];Q(L j=0,3f=d.M;j<3f;j++){L n=m=="~"||m=="+"?d[j].2D:d[j].1E;Q(;n;n=n.2D)7(n.15==1){L h=E.O(n);7(m=="~"&&l[h])1P;7(!12||n.12.2F()==12){7(m=="~")l[h]=P;r.1k(n)}7(m=="+")1P}}d=r;t=E.3g(t.1r(g,""));o=P}}7(t&&!o){7(!t.1f(",")){7(p==d[0])d.4p();2l=E.2P(2l,d);r=d=[p];t=" "+t.6o(1,t.M)}N{L k=6E;L m=k.2U(t);7(m){m=[0,m[2],m[3],m[1]]}N{k=6D;m=k.2U(t)}m[2]=m[2].1r(/\\\\/g,"");L f=d[d.M-1];7(m[1]=="#"&&f&&f.5S&&!E.3W(f)){L q=f.5S(m[2]);7((E.14.1c||E.14.2A)&&q&&1u q.2m=="2h"&&q.2m!=m[2])q=E(\'[@2m="\'+m[2]+\'"]\',f)[0];d=r=q&&(!m[3]||E.12(q,m[3]))?[q]:[]}N{Q(L i=0;d[i];i++){L a=m[1]=="#"&&m[3]?m[3]:m[1]!=""||m[0]==""?"*":m[2];7(a=="*"&&d[i].12.2g()=="3Z")a="3v";r=E.2P(r,d[i].3X(a))}7(m[1]==".")r=E.5d(r,m[2]);7(m[1]=="#"){L e=[];Q(L i=0;r[i];i++)7(r[i].4F("2m")==m[2]){e=[r[i]];1P}r=e}d=r}t=t.1r(k,"")}}7(t){L b=E.1B(t,r);d=r=b.r;t=E.3g(b.t)}}7(t)d=[];7(d&&p==d[0])d.4p();2l=E.2P(2l,d);K 2l},5d:J(r,m,a){m=" "+m+" ";L c=[];Q(L i=0;r[i];i++){L b=(" "+r[i].1t+" ").1f(m)>=0;7(!a&&b||a&&!b)c.1k(r[i])}K c},1B:J(t,r,h){L d;2a(t&&t!=d){d=t;L p=E.6q,m;Q(L i=0;p[i];i++){m=p[i].2U(t);7(m){t=t.81(m[0].M);m[2]=m[2].1r(/\\\\/g,"");1P}}7(!m)1P;7(m[1]==":"&&m[2]=="4S")r=G.17(m[3])?E.1B(m[3],r,P).r:E(r).4S(m[3]);N 7(m[1]==".")r=E.5d(r,m[2],h);N 7(m[1]=="["){L g=[],U=m[3];Q(L i=0,3f=r.M;i<3f;i++){L a=r[i],z=a[E.40[m[2]]||m[2]];7(z==V||/6X|3V|2x/.17(m[2]))z=E.1G(a,m[2])||\'\';7((U==""&&!!z||U=="="&&z==m[5]||U=="!="&&z!=m[5]||U=="^="&&z&&!z.1f(m[5])||U=="$="&&z.6o(z.M-m[5].M)==m[5]||(U=="*="||U=="~=")&&z.1f(m[5])>=0)^h)g.1k(a)}r=g}N 7(m[1]==":"&&m[2]=="2T-4t"){L e={},g=[],17=/(-?)(\\d*)n((?:\\+|-)?\\d*)/.2U(m[3]=="6z"&&"2n"||m[3]=="6y"&&"2n+1"||!/\\D/.17(m[3])&&"80+"+m[3]||m[3]),3i=(17[1]+(17[2]||1))-0,d=17[3]-0;Q(L i=0,3f=r.M;i<3f;i++){L j=r[i],1b=j.1b,2m=E.O(1b);7(!e[2m]){L c=1;Q(L n=1b.1E;n;n=n.2D)7(n.15==1)n.4o=c++;e[2m]=P}L b=S;7(3i==0){7(j.4o==d)b=P}N 7((j.4o-d)%3i==0&&(j.4o-d)/3i>=0)b=P;7(b^h)g.1k(j)}r=g}N{L f=E.6C[m[1]];7(1u f=="3Z")f=f[m[2]];7(1u f=="2h")f=6n("S||J(a,i){K "+f+";}");r=E.3x(r,J(a,i){K f(a,i,m,r)},h)}}K{r:r,t:t}},4A:J(b,c){L d=[];L a=b[c];2a(a&&a!=T){7(a.15==1)d.1k(a);a=a[c]}K d},2T:J(a,e,c,b){e=e||1;L d=0;Q(;a;a=a[c])7(a.15==1&&++d==e)1P;K a},5p:J(n,a){L r=[];Q(;n;n=n.2D){7(n.15==1&&(!a||n!=a))r.1k(n)}K r}});E.16={1a:J(f,i,g,e){7(f.15==3||f.15==8)K;7(E.14.1c&&f.5b!=10)f=1d;7(!g.2E)g.2E=6.2E++;7(e!=10){L h=g;g=J(){K h.1h(6,18)};g.O=e;g.2E=h.2E}L j=E.O(f,"36")||E.O(f,"36",{}),1z=E.O(f,"1z")||E.O(f,"1z",J(){L a;7(1u E=="10"||E.16.59)K a;a=E.16.1z.1h(18.3O.Y,18);K a});1z.Y=f;E.R(i.24(/\\s+/),J(c,b){L a=b.24(".");b=a[0];g.U=a[1];L d=j[b];7(!d){d=j[b]={};7(!E.16.2i[b]||E.16.2i[b].4n.1X(f)===S){7(f.3I)f.3I(b,1z,S);N 7(f.6m)f.6m("4m"+b,1z)}}d[g.2E]=g;E.16.27[b]=P});f=V},2E:1,27:{},1Q:J(e,h,f){7(e.15==3||e.15==8)K;L i=E.O(e,"36"),2e,51;7(i){7(h==10||(1u h=="2h"&&h.7Z(0)=="."))Q(L g 1o i)6.1Q(e,g+(h||""));N{7(h.U){f=h.2o;h=h.U}E.R(h.24(/\\s+/),J(b,a){L c=a.24(".");a=c[0];7(i[a]){7(f)2Q i[a][f.2E];N Q(f 1o i[a])7(!c[1]||i[a][f].U==c[1])2Q i[a][f];Q(2e 1o i[a])1P;7(!2e){7(!E.16.2i[a]||E.16.2i[a].4l.1X(e)===S){7(e.6j)e.6j(a,E.O(e,"1z"),S);N 7(e.6i)e.6i("4m"+a,E.O(e,"1z"))}2e=V;2Q i[a]}}})}Q(2e 1o i)1P;7(!2e){L d=E.O(e,"1z");7(d)d.Y=V;E.38(e,"36");E.38(e,"1z")}}},1L:J(g,c,d,f,h){c=E.2I(c||[]);7(g.1f("!")>=0){g=g.2B(0,-1);L a=P}7(!d){7(6.27[g])E("*").1a([1d,T]).1L(g,c)}N{7(d.15==3||d.15==8)K 10;L b,2e,1g=E.1q(d[g]||V),16=!c[0]||!c[0].32;7(16)c.5i(6.5t({U:g,2N:d}));c[0].U=g;7(a)c[0].6f=P;7(E.1q(E.O(d,"1z")))b=E.O(d,"1z").1h(d,c);7(!1g&&d["4m"+g]&&d["4m"+g].1h(d,c)===S)b=S;7(16)c.4p();7(h&&E.1q(h)){2e=h.1h(d,b==V?c:c.7d(b));7(2e!==10)b=2e}7(1g&&f!==S&&b!==S&&!(E.12(d,\'a\')&&g=="57")){6.59=P;1U{d[g]()}1R(e){}}6.59=S}K b},1z:J(c){L a;c=E.16.5t(c||1d.16||{});L b=c.U.24(".");c.U=b[0];L f=E.O(6,"36")&&E.O(6,"36")[c.U],42=1I.2k.2B.1X(18,1);42.5i(c);Q(L j 1o f){L d=f[j];42[0].2o=d;42[0].O=d.O;7(!b[1]&&!c.6f||d.U==b[1]){L e=d.1h(6,42);7(a!==S)a=e;7(e===S){c.32();c.41()}}}7(E.14.1c)c.2N=c.32=c.41=c.2o=c.O=V;K a},5t:J(c){L a=c;c=E.1p({},a);c.32=J(){7(a.32)a.32();a.7Y=S};c.41=J(){7(a.41)a.41();a.7X=P};7(!c.2N)c.2N=c.7W||T;7(c.2N.15==3)c.2N=a.2N.1b;7(!c.52&&c.5I)c.52=c.5I==c.2N?c.7V:c.5I;7(c.6d==V&&c.6c!=V){L b=T.1J,1i=T.1i;c.6d=c.6c+(b&&b.2w||1i&&1i.2w||0)-(b.6b||0);c.7U=c.7T+(b&&b.2p||1i&&1i.2p||0)-(b.68||0)}7(!c.3t&&((c.4I||c.4I===0)?c.4I:c.7b))c.3t=c.4I||c.7b;7(!c.7c&&c.65)c.7c=c.65;7(!c.3t&&c.2C)c.3t=(c.2C&1?1:(c.2C&2?3:(c.2C&4?2:0)));K c},2i:{21:{4n:J(){5F();K},4l:J(){K}},49:{4n:J(){7(E.14.1c)K S;E(6).2v("4Y",E.16.2i.49.2o);K P},4l:J(){7(E.14.1c)K S;E(6).3D("4Y",E.16.2i.49.2o);K P},2o:J(a){7(I(a,6))K P;18[0].U="49";K E.16.1z.1h(6,18)}},3C:{4n:J(){7(E.14.1c)K S;E(6).2v("4X",E.16.2i.3C.2o);K P},4l:J(){7(E.14.1c)K S;E(6).3D("4X",E.16.2i.3C.2o);K P},2o:J(a){7(I(a,6))K P;18[0].U="3C";K E.16.1z.1h(6,18)}}}};E.1g.1p({2v:J(c,a,b){K c=="4W"?6.2Y(c,a,b):6.R(J(){E.16.1a(6,c,b||a,b&&a)})},2Y:J(d,b,c){K 6.R(J(){E.16.1a(6,d,J(a){E(6).3D(a);K(c||b).1h(6,18)},c&&b)})},3D:J(a,b){K 6.R(J(){E.16.1Q(6,a,b)})},1L:J(c,a,b){K 6.R(J(){E.16.1L(c,a,6,P,b)})},5x:J(c,a,b){7(6[0])K E.16.1L(c,a,6[0],S,b);K 10},2z:J(){L b=18;K 6.57(J(a){6.5T=0==6.5T?1:0;a.32();K b[6.5T].1h(6,18)||S})},7Q:J(a,b){K 6.2v(\'49\',a).2v(\'3C\',b)},21:J(a){5F();7(E.2Z)a.1X(T,E);N E.3A.1k(J(){K a.1X(6,E)});K 6}});E.1p({2Z:S,3A:[],21:J(){7(!E.2Z){E.2Z=P;7(E.3A){E.R(E.3A,J(){6.1h(T)});E.3A=V}E(T).5x("21")}}});L x=S;J 5F(){7(x)K;x=P;7(T.3I&&!E.14.2A)T.3I("64",E.21,S);7(E.14.1c&&1d==3m)(J(){7(E.2Z)K;1U{T.1J.7O("25")}1R(39){3y(18.3O,0);K}E.21()})();7(E.14.2A)T.3I("64",J(){7(E.2Z)K;Q(L i=0;i<T.4U.M;i++)7(T.4U[i].31){3y(18.3O,0);K}E.21()},S);7(E.14.2b){L a;(J(){7(E.2Z)K;7(T.3c!="63"&&T.3c!="1x"){3y(18.3O,0);K}7(a===10)a=E("W, 7l[7N=7M]").M;7(T.4U.M!=a){3y(18.3O,0);K}E.21()})()}E.16.1a(1d,"43",E.21)}E.R(("7K,7J,43,7I,5l,4W,57,7H,"+"7G,7F,7E,4Y,4X,7D,2q,"+"5g,7L,7C,7B,39").24(","),J(i,b){E.1g[b]=J(a){K a?6.2v(b,a):6.1L(b)}});L I=J(a,c){L b=a.52;2a(b&&b!=c)1U{b=b.1b}1R(39){b=c}K b==c};E(1d).2v("4W",J(){E("*").1a(T).3D()});E.1g.1p({43:J(g,d,c){7(E.1q(g))K 6.2v("43",g);L e=g.1f(" ");7(e>=0){L i=g.2B(e,g.M);g=g.2B(0,e)}c=c||J(){};L f="4T";7(d)7(E.1q(d)){c=d;d=V}N{d=E.3v(d);f="62"}L h=6;E.3T({1e:g,U:f,1H:"3o",O:d,1x:J(a,b){7(b=="1O"||b=="61")h.3o(i?E("<1y/>").3s(a.4j.1r(/<1n(.|\\s)*?\\/1n>/g,"")).2u(i):a.4j);h.R(c,[a.4j,b,a])}});K 6},7y:J(){K E.3v(6.60())},60:J(){K 6.2c(J(){K E.12(6,"3e")?E.2I(6.7x):6}).1B(J(){K 6.2X&&!6.31&&(6.3r||/2q|6s/i.17(6.12)||/1s|1Z|3K/i.17(6.U))}).2c(J(i,c){L b=E(6).5V();K b==V?V:b.1j==1I?E.2c(b,J(a,i){K{2X:c.2X,1C:a}}):{2X:c.2X,1C:b}}).23()}});E.R("5Z,6e,5Y,6l,5X,6r".24(","),J(i,o){E.1g[o]=J(f){K 6.2v(o,f)}});L B=(1D 3Q).3P();E.1p({23:J(d,b,a,c){7(E.1q(b)){a=b;b=V}K E.3T({U:"4T",1e:d,O:b,1O:a,1H:c})},7w:J(b,a){K E.23(b,V,a,"1n")},7v:J(c,b,a){K E.23(c,b,a,"3a")},7u:J(d,b,a,c){7(E.1q(b)){a=b;b={}}K E.3T({U:"62",1e:d,O:b,1O:a,1H:c})},7t:J(a){E.1p(E.4O,a)},4O:{27:P,U:"4T",2O:0,5W:"4s/x-7r-3e-7q",5U:P,3l:P,O:V,6w:V,3K:V,4e:{3L:"4s/3L, 1s/3L",3o:"1s/3o",1n:"1s/4q, 4s/4q",3a:"4s/3a, 1s/4q",1s:"1s/ac",4N:"*/*"}},4M:{},3T:J(s){L f,2S=/=\\?(&|$)/g,1v,O;s=E.1p(P,s,E.1p(P,{},E.4O,s));7(s.O&&s.5U&&1u s.O!="2h")s.O=E.3v(s.O);7(s.1H=="4L"){7(s.U.2g()=="23"){7(!s.1e.1A(2S))s.1e+=(s.1e.1A(/\\?/)?"&":"?")+(s.4L||"7p")+"=?"}N 7(!s.O||!s.O.1A(2S))s.O=(s.O?s.O+"&":"")+(s.4L||"7p")+"=?";s.1H="3a"}7(s.1H=="3a"&&(s.O&&s.O.1A(2S)||s.1e.1A(2S))){f="4L"+B++;7(s.O)s.O=(s.O+"").1r(2S,"="+f+"$1");s.1e=s.1e.1r(2S,"="+f+"$1");s.1H="1n";1d[f]=J(a){O=a;1O();1x();1d[f]=10;1U{2Q 1d[f]}1R(e){}7(h)h.35(g)}}7(s.1H=="1n"&&s.1T==V)s.1T=S;7(s.1T===S&&s.U.2g()=="23"){L i=(1D 3Q()).3P();L j=s.1e.1r(/(\\?|&)4v=.*?(&|$)/,"$aa="+i+"$2");s.1e=j+((j==s.1e)?(s.1e.1A(/\\?/)?"&":"?")+"4v="+i:"")}7(s.O&&s.U.2g()=="23"){s.1e+=(s.1e.1A(/\\?/)?"&":"?")+s.O;s.O=V}7(s.27&&!E.5Q++)E.16.1L("5Z");7((!s.1e.1f("a6")||!s.1e.1f("//"))&&s.1H=="1n"&&s.U.2g()=="23"){L h=T.3X("6p")[0];L g=T.3p("1n");g.3V=s.1e;7(s.7m)g.a5=s.7m;7(!f){L l=S;g.a4=g.a3=J(){7(!l&&(!6.3c||6.3c=="63"||6.3c=="1x")){l=P;1O();1x();h.35(g)}}}h.3j(g);K 10}L m=S;L k=1d.7k?1D 7k("a1.a0"):1D 7j();k.9Y(s.U,s.1e,s.3l,s.6w,s.3K);1U{7(s.O)k.4J("9W-9V",s.5W);7(s.5q)k.4J("9T-5K-9R",E.4M[s.1e]||"9Q, 9P 9N 9M 5r:5r:5r 9L");k.4J("X-9I-9G","7j");k.4J("9F",s.1H&&s.4e[s.1H]?s.4e[s.1H]+", */*":s.4e.4N)}1R(e){}7(s.7a)s.7a(k);7(s.27)E.16.1L("6r",[k,s]);L c=J(a){7(!m&&k&&(k.3c==4||a=="2O")){m=P;7(d){79(d);d=V}1v=a=="2O"&&"2O"||!E.78(k)&&"39"||s.5q&&E.6S(k,s.1e)&&"61"||"1O";7(1v=="1O"){1U{O=E.77(k,s.1H)}1R(e){1v="5u"}}7(1v=="1O"){L b;1U{b=k.5w("75-5K")}1R(e){}7(s.5q&&b)E.4M[s.1e]=b;7(!f)1O()}N E.5y(s,k,1v);1x();7(s.3l)k=V}};7(s.3l){L d=5b(c,13);7(s.2O>0)3y(J(){7(k){k.9y();7(!m)c("2O")}},s.2O)}1U{k.9x(s.O)}1R(e){E.5y(s,k,V,e)}7(!s.3l)c();J 1O(){7(s.1O)s.1O(O,1v);7(s.27)E.16.1L("5X",[k,s])}J 1x(){7(s.1x)s.1x(k,1v);7(s.27)E.16.1L("5Y",[k,s]);7(s.27&&!--E.5Q)E.16.1L("6e")}K k},5y:J(s,a,b,e){7(s.39)s.39(a,b,e);7(s.27)E.16.1L("6l",[a,s,e])},5Q:0,78:J(r){1U{K!r.1v&&9v.9u=="5h:"||(r.1v>=73&&r.1v<9s)||r.1v==72||r.1v==9q||E.14.2b&&r.1v==10}1R(e){}K S},6S:J(a,c){1U{L b=a.5w("75-5K");K a.1v==72||b==E.4M[c]||E.14.2b&&a.1v==10}1R(e){}K S},77:J(r,b){L c=r.5w("9o-U");L d=b=="3L"||!b&&c&&c.1f("3L")>=0;L a=d?r.9n:r.4j;7(d&&a.1J.26=="5u")6Z"5u";7(b=="1n")E.5o(a);7(b=="3a")a=6n("("+a+")");K a},3v:J(a){L s=[];7(a.1j==1I||a.4y)E.R(a,J(){s.1k(3q(6.2X)+"="+3q(6.1C))});N Q(L j 1o a)7(a[j]&&a[j].1j==1I)E.R(a[j],J(){s.1k(3q(j)+"="+3q(6))});N s.1k(3q(j)+"="+3q(a[j]));K s.6k("&").1r(/%20/g,"+")}});E.1g.1p({1F:J(c,b){K c?6.2d({1S:"1F",28:"1F",1w:"1F"},c,b):6.1B(":1Z").R(J(){6.W.19=6.5B||"";7(E.1l(6,"19")=="2G"){L a=E("<"+6.26+" />").6I("1i");6.W.19=a.1l("19");7(6.W.19=="2G")6.W.19="46";a.1Q()}}).3h()},1K:J(b,a){K b?6.2d({1S:"1K",28:"1K",1w:"1K"},b,a):6.1B(":4i").R(J(){6.5B=6.5B||E.1l(6,"19");6.W.19="2G"}).3h()},6Y:E.1g.2z,2z:J(a,b){K E.1q(a)&&E.1q(b)?6.6Y(a,b):a?6.2d({1S:"2z",28:"2z",1w:"2z"},a,b):6.R(J(){E(6)[E(6).3E(":1Z")?"1F":"1K"]()})},9k:J(b,a){K 6.2d({1S:"1F"},b,a)},9i:J(b,a){K 6.2d({1S:"1K"},b,a)},9h:J(b,a){K 6.2d({1S:"2z"},b,a)},9f:J(b,a){K 6.2d({1w:"1F"},b,a)},9e:J(b,a){K 6.2d({1w:"1K"},b,a)},9c:J(c,a,b){K 6.2d({1w:a},c,b)},2d:J(l,k,j,h){L i=E.7e(k,j,h);K 6[i.33===S?"R":"33"](J(){7(6.15!=1)K S;L g=E.1p({},i);L f=E(6).3E(":1Z"),4G=6;Q(L p 1o l){7(l[p]=="1K"&&f||l[p]=="1F"&&!f)K E.1q(g.1x)&&g.1x.1h(6);7(p=="1S"||p=="28"){g.19=E.1l(6,"19");g.34=6.W.34}}7(g.34!=V)6.W.34="1Z";g.3z=E.1p({},l);E.R(l,J(c,a){L e=1D E.2s(4G,g,c);7(/2z|1F|1K/.17(a))e[a=="2z"?f?"1F":"1K":a](l);N{L b=a.3G().1A(/^([+-]=)?([\\d+-.]+)(.*)$/),22=e.2j(P)||0;7(b){L d=2J(b[2]),2L=b[3]||"2R";7(2L!="2R"){4G.W[c]=(d||1)+2L;22=((d||1)/e.2j(P))*22;4G.W[c]=22+2L}7(b[1])d=((b[1]=="-="?-1:1)*d)+22;e.45(22,d,2L)}N e.45(22,a,"")}});K P})},33:J(a,b){7(E.1q(a)||(a&&a.1j==1I)){b=a;a="2s"}7(!a||(1u a=="2h"&&!b))K A(6[0],a);K 6.R(J(){7(b.1j==1I)A(6,a,b);N{A(6,a).1k(b);7(A(6,a).M==1)b.1h(6)}})},99:J(b,c){L a=E.3J;7(b)6.33([]);6.R(J(){Q(L i=a.M-1;i>=0;i--)7(a[i].Y==6){7(c)a[i](P);a.71(i,1)}});7(!c)6.5z();K 6}});L A=J(b,c,a){7(!b)K 10;c=c||"2s";L q=E.O(b,c+"33");7(!q||a)q=E.O(b,c+"33",a?E.2I(a):[]);K q};E.1g.5z=J(a){a=a||"2s";K 6.R(J(){L q=A(6,a);q.4p();7(q.M)q[0].1h(6)})};E.1p({7e:J(b,a,c){L d=b&&b.1j==97?b:{1x:c||!c&&a||E.1q(b)&&b,2r:b,44:c&&a||a&&a.1j!=96&&a};d.2r=(d.2r&&d.2r.1j==58?d.2r:{95:94,9E:73}[d.2r])||92;d.5G=d.1x;d.1x=J(){7(d.33!==S)E(6).5z();7(E.1q(d.5G))d.5G.1h(6)};K d},44:{76:J(p,n,b,a){K b+a*p},5H:J(p,n,b,a){K((-1Y.90(p*1Y.8Z)/2)+0.5)*a+b}},3J:[],48:V,2s:J(b,c,a){6.11=c;6.Y=b;6.1m=a;7(!c.47)c.47={}}});E.2s.2k={4E:J(){7(6.11.30)6.11.30.1h(6.Y,[6.2M,6]);(E.2s.30[6.1m]||E.2s.30.4N)(6);7(6.1m=="1S"||6.1m=="28")6.Y.W.19="46"},2j:J(a){7(6.Y[6.1m]!=V&&6.Y.W[6.1m]==V)K 6.Y[6.1m];L r=2J(E.1l(6.Y,6.1m,a));K r&&r>-8V?r:2J(E.29(6.Y,6.1m))||0},45:J(c,b,d){6.5L=(1D 3Q()).3P();6.22=c;6.3h=b;6.2L=d||6.2L||"2R";6.2M=6.22;6.4D=6.4C=0;6.4E();L e=6;J t(a){K e.30(a)}t.Y=6.Y;E.3J.1k(t);7(E.48==V){E.48=5b(J(){L a=E.3J;Q(L i=0;i<a.M;i++)7(!a[i]())a.71(i--,1);7(!a.M){79(E.48);E.48=V}},13)}},1F:J(){6.11.47[6.1m]=E.1G(6.Y.W,6.1m);6.11.1F=P;6.45(0,6.2j());7(6.1m=="28"||6.1m=="1S")6.Y.W[6.1m]="8T";E(6.Y).1F()},1K:J(){6.11.47[6.1m]=E.1G(6.Y.W,6.1m);6.11.1K=P;6.45(6.2j(),0)},30:J(a){L t=(1D 3Q()).3P();7(a||t>6.11.2r+6.5L){6.2M=6.3h;6.4D=6.4C=1;6.4E();6.11.3z[6.1m]=P;L b=P;Q(L i 1o 6.11.3z)7(6.11.3z[i]!==P)b=S;7(b){7(6.11.19!=V){6.Y.W.34=6.11.34;6.Y.W.19=6.11.19;7(E.1l(6.Y,"19")=="2G")6.Y.W.19="46"}7(6.11.1K)6.Y.W.19="2G";7(6.11.1K||6.11.1F)Q(L p 1o 6.11.3z)E.1G(6.Y.W,p,6.11.47[p])}7(b&&E.1q(6.11.1x))6.11.1x.1h(6.Y);K S}N{L n=t-6.5L;6.4C=n/6.11.2r;6.4D=E.44[6.11.44||(E.44.5H?"5H":"76")](6.4C,n,0,1,6.11.2r);6.2M=6.22+((6.3h-6.22)*6.4D);6.4E()}K P}};E.2s.30={2w:J(a){a.Y.2w=a.2M},2p:J(a){a.Y.2p=a.2M},1w:J(a){E.1G(a.Y.W,"1w",a.2M)},4N:J(a){a.Y.W[a.1m]=a.2M+a.2L}};E.1g.5k=J(){L b=0,3m=0,Y=6[0],5J;7(Y)8R(E.14){L d=Y.1b,4c=Y,1M=Y.1M,1N=Y.2t,5M=2b&&3R(5s)<8P&&!/8O/i.17(v),37=E.1l(Y,"3F")=="37";7(Y.6P){L c=Y.6P();1a(c.25+1Y.2f(1N.1J.2w,1N.1i.2w),c.3m+1Y.2f(1N.1J.2p,1N.1i.2p));1a(-1N.1J.6b,-1N.1J.68)}N{1a(Y.5O,Y.5R);2a(1M){1a(1M.5O,1M.5R);7(4b&&!/^t(8L|d|h)$/i.17(1M.26)||2b&&!5M)2y(1M);7(!37&&E.1l(1M,"3F")=="37")37=P;4c=/^1i$/i.17(1M.26)?4c:1M;1M=1M.1M}2a(d&&d.26&&!/^1i|3o$/i.17(d.26)){7(!/^8K|1W.*$/i.17(E.1l(d,"19")))1a(-d.2w,-d.2p);7(4b&&E.1l(d,"34")!="4i")2y(d);d=d.1b}7((5M&&(37||E.1l(4c,"3F")=="55"))||(4b&&E.1l(4c,"3F")!="55"))1a(-1N.1i.5O,-1N.1i.5R);7(37)1a(1Y.2f(1N.1J.2w,1N.1i.2w),1Y.2f(1N.1J.2p,1N.1i.2p))}5J={3m:3m,25:b}}J 2y(a){1a(E.29(a,"a8",P),E.29(a,"a9",P))}J 1a(l,t){b+=3R(l)||0;3m+=3R(t)||0}K 5J};E.R(["4x","3B"],J(i,b){L c=b=="4x"?"67":"6a",3Y=b=="4x"?"66":"69";E.1g["5m"+b]=J(){K 6[b.2g()]()+2V(6,"4V"+c)+2V(6,"4V"+3Y)};E.1g["8H"+b]=J(a){K 6["5m"+b]()+2V(6,"2y"+c+"3B")+2V(6,"2y"+3Y+"3B")+(!!a?2V(6,"6N"+c)+2V(6,"6N"+3Y):0)}});J 2V(a,b){a=a.4y?a[0]:a;K a&&3R(E.29(a,b,P))||0}})();',62,634,'||||||this|if||||||||||||||||||||||||||||||||||||||function|return|var|length|else|data|true|for|each|false|document|type|null|style||elem||undefined|options|nodeName||browser|nodeType|event|test|arguments|display|add|parentNode|msie|window|url|indexOf|fn|apply|body|constructor|push|css|prop|script|in|extend|isFunction|replace|text|className|typeof|status|opacity|complete|div|handle|match|filter|value|new|firstChild|show|attr|dataType|Array|documentElement|hide|trigger|offsetParent|doc|success|break|remove|catch|height|cache|try|tbody|table|call|Math|hidden||ready|start|get|split|left|tagName|global|width|curCSS|while|safari|map|animate|ret|max|toLowerCase|string|special|cur|prototype|done|id||handler|scrollTop|select|duration|fx|ownerDocument|find|bind|scrollLeft|selected|border|toggle|opera|slice|button|nextSibling|guid|toUpperCase|none|pushStack|makeArray|parseFloat|stack|unit|now|target|timeout|merge|delete|px|jsre|nth|exec|num|inArray|name|one|isReady|step|disabled|preventDefault|queue|overflow|removeChild|events|fixed|removeData|error|json|multiFilter|readyState|innerHTML|form|rl|trim|end|first|appendChild|elems|async|top|insertBefore|html|createElement|encodeURIComponent|checked|append|which|childNodes|param|color|grep|setTimeout|curAnim|readyList|Width|mouseleave|unbind|is|position|toString|has|addEventListener|timers|password|xml|runtimeStyle|last|callee|getTime|Date|parseInt|domManip|ajax|selectedIndex|src|isXMLDoc|getElementsByTagName|br|object|props|stopPropagation|args|load|easing|custom|block|orig|timerId|mouseenter||mozilla|offsetChild|jQuery|accepts|clean|float|defaultView|visible|responseText|String|teardown|on|setup|nodeIndex|shift|javascript|currentStyle|application|child|RegExp|_|deep|Height|jquery|previousSibling|dir|tr|state|pos|update|getAttribute|self|empty|charCode|setRequestHeader|input|jsonp|lastModified|_default|ajaxSettings|outline|getPropertyValue|getComputedStyle|not|GET|styleSheets|padding|unload|mouseout|mouseover|getWH|andSelf|index|relatedTarget|visibility|init|absolute|container|click|Number|triggered|removeAttribute|setInterval|prevObject|classFilter|unique|after|submit|file|unshift|windowData|offset|scroll|inner|client|globalEval|sibling|ifModified|00|version|fix|parsererror|wrapAll|getResponseHeader|triggerHandler|handleError|dequeue|createTextNode|oldblock|checkbox|radio|lastChild|bindReady|old|swing|fromElement|results|Modified|startTime|safari2|clone|offsetLeft|values|active|offsetTop|getElementById|lastToggle|processData|val|contentType|ajaxSuccess|ajaxComplete|ajaxStart|serializeArray|notmodified|POST|loaded|DOMContentLoaded|ctrlKey|Bottom|Top|clientTop|Right|Left|clientLeft|clientX|pageX|ajaxStop|exclusive|swap|cloneNode|detachEvent|removeEventListener|join|ajaxError|attachEvent|eval|substr|head|parse|ajaxSend|textarea|reset|image|zoom|username|before|odd|even|prepend|uuid|expr|quickClass|quickID|quickChild|continue|textContent|appendTo|contents|evalScript|parent|defaultValue|margin|compatMode|getBoundingClientRect|setArray|styleFloat|httpNotModified|nodeValue|eq|replaceWith|alpha|href|_toggle|throw|100|splice|304|200|colgroup|Last|linear|httpData|httpSuccess|clearInterval|beforeSend|keyCode|metaKey|concat|speed|fieldset|webkit|multiple|cssFloat|XMLHttpRequest|ActiveXObject|link|scriptCharset|CSS1Compat|col|callback|urlencoded|www|hasClass|ajaxSetup|post|getJSON|getScript|elements|serialize|black|solid|keyup|keypress|change|mousemove|mouseup|mousedown|dblclick|resize|focus|blur|keydown|stylesheet|rel|doScroll|round|hover|offsetHeight|offsetWidth|clientY|pageY|toElement|srcElement|cancelBubble|returnValue|charAt|0n|substring|animated|header|noConflict|line|size|enabled|innerText|weight|contains|only|font|gt|lt|uFFFF|u0128|Boolean|417|toggleClass|removeClass|addClass|removeAttr|replaceAll|insertAfter|wrap|prependTo|contentWindow|contentDocument|iframe|children|siblings|wrapInner|prevAll|nextAll|prev|next|parents|maxLength|maxlength|reverse|readOnly|readonly|outer|class|htmlFor|inline|able|boxModel|setData|adobeair|522|compatible|with|getData|1px|ie|10000|ra|it|rv|PI|cos|userAgent|400|navigator|600|slow|Function|Object|ig|stop|NaN|option|fadeTo|setAttribute|fadeOut|fadeIn|changed|slideToggle|slideUp|be|slideDown|can|property|responseXML|content|getAttributeNode|1223|method|300|action|protocol|location|cssText|send|abort|th|td|specified|cap|colg|fast|Accept|With|tfoot|Requested|thead|attributes|GMT|1970|Jan|leg|01|Thu|Since|opt|If|embed|Type|Content|area|open|hr|XMLHTTP|Microsoft|meta|onreadystatechange|onload|charset|http|img|borderLeftWidth|borderTopWidth|1_|abbr|plain|pixelLeft'.split('|'),0,{})) \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/media/js/thickbox.js Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,319 @@ +/* + * Thickbox 3.1 - One Box To Rule Them All. + * By Cody Lindley (http://www.codylindley.com) + * Copyright (c) 2007 cody lindley + * Licensed under the MIT License: http://www.opensource.org/licenses/mit-license.php +*/ + +var tb_pathToImage = "/media/images/loadingAnimation.gif"; + +/*!!!!!!!!!!!!!!!!! edit below this line at your own risk !!!!!!!!!!!!!!!!!!!!!!!*/ + +//on page load call tb_init +$(document).ready(function(){ + tb_init('a.thickbox, area.thickbox, input.thickbox');//pass where to apply thickbox + imgLoader = new Image();// preload image + imgLoader.src = tb_pathToImage; +}); + +//add thickbox to href & area elements that have a class of .thickbox +function tb_init(domChunk){ + $(domChunk).click(function(){ + var t = this.title || this.name || null; + var a = this.href || this.alt; + var g = this.rel || false; + tb_show(t,a,g); + this.blur(); + return false; + }); +} + +function tb_show(caption, url, imageGroup) {//function called when the user clicks on a thickbox link + + try { + if (typeof document.body.style.maxHeight === "undefined") {//if IE 6 + $("body","html").css({height: "100%", width: "100%"}); + $("html").css("overflow","hidden"); + if (document.getElementById("TB_HideSelect") === null) {//iframe to hide select elements in ie6 + $("body").append("<iframe id='TB_HideSelect'></iframe><div id='TB_overlay'></div><div id='TB_window'></div>"); + $("#TB_overlay").click(tb_remove); + } + }else{//all others + if(document.getElementById("TB_overlay") === null){ + $("body").append("<div id='TB_overlay'></div><div id='TB_window'></div>"); + $("#TB_overlay").click(tb_remove); + } + } + + if(tb_detectMacXFF()){ + $("#TB_overlay").addClass("TB_overlayMacFFBGHack");//use png overlay so hide flash + }else{ + $("#TB_overlay").addClass("TB_overlayBG");//use background and opacity + } + + if(caption===null){caption="";} + $("body").append("<div id='TB_load'><img src='"+imgLoader.src+"' /></div>");//add loader to the page + $('#TB_load').show();//show loader + + var baseURL; + if(url.indexOf("?")!==-1){ //ff there is a query string involved + baseURL = url.substr(0, url.indexOf("?")); + }else{ + baseURL = url; + } + + var urlString = /\.jpg$|\.jpeg$|\.png$|\.gif$|\.bmp$/; + var urlType = baseURL.toLowerCase().match(urlString); + + if(urlType == '.jpg' || urlType == '.jpeg' || urlType == '.png' || urlType == '.gif' || urlType == '.bmp'){//code to show images + + TB_PrevCaption = ""; + TB_PrevURL = ""; + TB_PrevHTML = ""; + TB_NextCaption = ""; + TB_NextURL = ""; + TB_NextHTML = ""; + TB_imageCount = ""; + TB_FoundURL = false; + if(imageGroup){ + TB_TempArray = $("a[@rel="+imageGroup+"]").get(); + for (TB_Counter = 0; ((TB_Counter < TB_TempArray.length) && (TB_NextHTML === "")); TB_Counter++) { + var urlTypeTemp = TB_TempArray[TB_Counter].href.toLowerCase().match(urlString); + if (!(TB_TempArray[TB_Counter].href == url)) { + if (TB_FoundURL) { + TB_NextCaption = TB_TempArray[TB_Counter].title; + TB_NextURL = TB_TempArray[TB_Counter].href; + TB_NextHTML = "<span id='TB_next'> <a href='#'>Next ></a></span>"; + } else { + TB_PrevCaption = TB_TempArray[TB_Counter].title; + TB_PrevURL = TB_TempArray[TB_Counter].href; + TB_PrevHTML = "<span id='TB_prev'> <a href='#'>< Prev</a></span>"; + } + } else { + TB_FoundURL = true; + TB_imageCount = "Image " + (TB_Counter + 1) +" of "+ (TB_TempArray.length); + } + } + } + + imgPreloader = new Image(); + imgPreloader.onload = function(){ + imgPreloader.onload = null; + + // Resizing large images - orginal by Christian Montoya edited by me. + var pagesize = tb_getPageSize(); + var x = pagesize[0] - 150; + var y = pagesize[1] - 150; + var imageWidth = imgPreloader.width; + var imageHeight = imgPreloader.height; + if (imageWidth > x) { + imageHeight = imageHeight * (x / imageWidth); + imageWidth = x; + if (imageHeight > y) { + imageWidth = imageWidth * (y / imageHeight); + imageHeight = y; + } + } else if (imageHeight > y) { + imageWidth = imageWidth * (y / imageHeight); + imageHeight = y; + if (imageWidth > x) { + imageHeight = imageHeight * (x / imageWidth); + imageWidth = x; + } + } + // End Resizing + + TB_WIDTH = imageWidth + 30; + TB_HEIGHT = imageHeight + 60; + $("#TB_window").append("<a href='' id='TB_ImageOff' title='Close'><img id='TB_Image' src='"+url+"' width='"+imageWidth+"' height='"+imageHeight+"' alt='"+caption+"'/></a>" + "<div id='TB_caption'>"+caption+"<div id='TB_secondLine'>" + TB_imageCount + TB_PrevHTML + TB_NextHTML + "</div></div><div id='TB_closeWindow'><a href='#' id='TB_closeWindowButton' title='Close'>close</a> or Esc Key</div>"); + + $("#TB_closeWindowButton").click(tb_remove); + + if (!(TB_PrevHTML === "")) { + function goPrev(){ + if($(document).unbind("click",goPrev)){$(document).unbind("click",goPrev);} + $("#TB_window").remove(); + $("body").append("<div id='TB_window'></div>"); + tb_show(TB_PrevCaption, TB_PrevURL, imageGroup); + return false; + } + $("#TB_prev").click(goPrev); + } + + if (!(TB_NextHTML === "")) { + function goNext(){ + $("#TB_window").remove(); + $("body").append("<div id='TB_window'></div>"); + tb_show(TB_NextCaption, TB_NextURL, imageGroup); + return false; + } + $("#TB_next").click(goNext); + + } + + document.onkeydown = function(e){ + if (e == null) { // ie + keycode = event.keyCode; + } else { // mozilla + keycode = e.which; + } + if(keycode == 27){ // close + tb_remove(); + } else if(keycode == 190){ // display previous image + if(!(TB_NextHTML == "")){ + document.onkeydown = ""; + goNext(); + } + } else if(keycode == 188){ // display next image + if(!(TB_PrevHTML == "")){ + document.onkeydown = ""; + goPrev(); + } + } + }; + + tb_position(); + $("#TB_load").remove(); + $("#TB_ImageOff").click(tb_remove); + $("#TB_window").css({display:"block"}); //for safari using css instead of show + }; + + imgPreloader.src = url; + }else{//code to show html + + var queryString = url.replace(/^[^\?]+\??/,''); + var params = tb_parseQuery( queryString ); + + TB_WIDTH = (params['width']*1) + 30 || 630; //defaults to 630 if no paramaters were added to URL + TB_HEIGHT = (params['height']*1) + 40 || 440; //defaults to 440 if no paramaters were added to URL + ajaxContentW = TB_WIDTH - 30; + ajaxContentH = TB_HEIGHT - 45; + + if(url.indexOf('TB_iframe') != -1){// either iframe or ajax window + urlNoQuery = url.split('TB_'); + $("#TB_iframeContent").remove(); + if(params['modal'] != "true"){//iframe no modal + $("#TB_window").append("<div id='TB_title'><div id='TB_ajaxWindowTitle'>"+caption+"</div><div id='TB_closeAjaxWindow'><a href='#' id='TB_closeWindowButton' title='Close'>close</a> or Esc Key</div></div><iframe frameborder='0' hspace='0' src='"+urlNoQuery[0]+"' id='TB_iframeContent' name='TB_iframeContent"+Math.round(Math.random()*1000)+"' onload='tb_showIframe()' style='width:"+(ajaxContentW + 29)+"px;height:"+(ajaxContentH + 17)+"px;' > </iframe>"); + }else{//iframe modal + $("#TB_overlay").unbind(); + $("#TB_window").append("<iframe frameborder='0' hspace='0' src='"+urlNoQuery[0]+"' id='TB_iframeContent' name='TB_iframeContent"+Math.round(Math.random()*1000)+"' onload='tb_showIframe()' style='width:"+(ajaxContentW + 29)+"px;height:"+(ajaxContentH + 17)+"px;'> </iframe>"); + } + }else{// not an iframe, ajax + if($("#TB_window").css("display") != "block"){ + if(params['modal'] != "true"){//ajax no modal + $("#TB_window").append("<div id='TB_title'><div id='TB_ajaxWindowTitle'>"+caption+"</div><div id='TB_closeAjaxWindow'><a href='#' id='TB_closeWindowButton'>close</a> or Esc Key</div></div><div id='TB_ajaxContent' style='width:"+ajaxContentW+"px;height:"+ajaxContentH+"px'></div>"); + }else{//ajax modal + $("#TB_overlay").unbind(); + $("#TB_window").append("<div id='TB_ajaxContent' class='TB_modal' style='width:"+ajaxContentW+"px;height:"+ajaxContentH+"px;'></div>"); + } + }else{//this means the window is already up, we are just loading new content via ajax + $("#TB_ajaxContent")[0].style.width = ajaxContentW +"px"; + $("#TB_ajaxContent")[0].style.height = ajaxContentH +"px"; + $("#TB_ajaxContent")[0].scrollTop = 0; + $("#TB_ajaxWindowTitle").html(caption); + } + } + + $("#TB_closeWindowButton").click(tb_remove); + + if(url.indexOf('TB_inline') != -1){ + $("#TB_ajaxContent").append($('#' + params['inlineId']).children()); + $("#TB_window").unload(function () { + $('#' + params['inlineId']).append( $("#TB_ajaxContent").children() ); // move elements back when you're finished + }); + tb_position(); + $("#TB_load").remove(); + $("#TB_window").css({display:"block"}); + }else if(url.indexOf('TB_iframe') != -1){ + tb_position(); + if($.browser.safari){//safari needs help because it will not fire iframe onload + $("#TB_load").remove(); + $("#TB_window").css({display:"block"}); + } + }else{ + $("#TB_ajaxContent").load(url += "&random=" + (new Date().getTime()),function(){//to do a post change this load method + tb_position(); + $("#TB_load").remove(); + tb_init("#TB_ajaxContent a.thickbox"); + $("#TB_window").css({display:"block"}); + }); + } + + } + + if(!params['modal']){ + document.onkeyup = function(e){ + if (e == null) { // ie + keycode = event.keyCode; + } else { // mozilla + keycode = e.which; + } + if(keycode == 27){ // close + tb_remove(); + } + }; + } + + } catch(e) { + //nothing here + } +} + +//helper functions below +function tb_showIframe(){ + $("#TB_load").remove(); + $("#TB_window").css({display:"block"}); +} + +function tb_remove() { + $("#TB_imageOff").unbind("click"); + $("#TB_closeWindowButton").unbind("click"); + $("#TB_window").fadeOut("fast",function(){$('#TB_window,#TB_overlay,#TB_HideSelect').trigger("unload").unbind().remove();}); + $("#TB_load").remove(); + if (typeof document.body.style.maxHeight == "undefined") {//if IE 6 + $("body","html").css({height: "auto", width: "auto"}); + $("html").css("overflow",""); + } + document.onkeydown = ""; + document.onkeyup = ""; + return false; +} + +function tb_position() { +$("#TB_window").css({marginLeft: '-' + parseInt((TB_WIDTH / 2),10) + 'px', width: TB_WIDTH + 'px'}); + if ( !(jQuery.browser.msie && jQuery.browser.version < 7)) { // take away IE6 + $("#TB_window").css({marginTop: '-' + parseInt((TB_HEIGHT / 2),10) + 'px'}); + } +} + +function tb_parseQuery ( query ) { + var Params = {}; + if ( ! query ) {return Params;}// return empty object + var Pairs = query.split(/[;&]/); + for ( var i = 0; i < Pairs.length; i++ ) { + var KeyVal = Pairs[i].split('='); + if ( ! KeyVal || KeyVal.length != 2 ) {continue;} + var key = unescape( KeyVal[0] ); + var val = unescape( KeyVal[1] ); + val = val.replace(/\+/g, ' '); + Params[key] = val; + } + return Params; +} + +function tb_getPageSize(){ + var de = document.documentElement; + var w = window.innerWidth || self.innerWidth || (de&&de.clientWidth) || document.body.clientWidth; + var h = window.innerHeight || self.innerHeight || (de&&de.clientHeight) || document.body.clientHeight; + arrayPageSize = [w,h]; + return arrayPageSize; +} + +function tb_detectMacXFF() { + var userAgent = navigator.userAgent.toLowerCase(); + if (userAgent.indexOf('mac') != -1 && userAgent.indexOf('firefox')!=-1) { + return true; + } +} + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mysite/band/admin.py Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,239 @@ +####################################################################### +# +# PyBand Copyright (C) 2008 by Brian Neal +# +####################################################################### + +from django.contrib import admin + +from mysite.band.models import Article +from mysite.band.models import Album +from mysite.band.models import Album_Merchant +from mysite.band.models import Album_Track +from mysite.band.models import Band +from mysite.band.models import City +from mysite.band.models import Fan +from mysite.band.models import Gear +from mysite.band.models import Gig +from mysite.band.models import Label_Release +from mysite.band.models import Member +from mysite.band.models import Merchandise +from mysite.band.models import Mp3 +from mysite.band.models import Mp3_Set +from mysite.band.models import News +from mysite.band.models import Record_Label +from mysite.band.models import SiteConfig +from mysite.band.models import State +from mysite.band.models import Venue +from mysite.band.models import Video +from mysite.band.models import Video_Set + +####################################################################### + +admin.site.register(Video) + +####################################################################### + +class SiteConfigAdmin(admin.ModelAdmin): + list_display = ('band_name', 'url', 'contact_email') + fieldsets = ( + (None, { 'fields' : ('band_name', 'url', 'contact_email', 'intro_text', 'ordering_info', + 'intro_photo') }), + ) + +admin.site.register(SiteConfig, SiteConfigAdmin) + +####################################################################### + +class GearInline(admin.TabularInline): + model = Gear + +class GearAdmin(admin.ModelAdmin): + list_display = ('item', 'member') + list_filter = ('member', ) + +admin.site.register(Gear, GearAdmin) + +####################################################################### + +class MemberAdmin(admin.ModelAdmin): + list_display = ('name', 'instrument', 'is_active') + inlines = [ + GearInline, + ] + +admin.site.register(Member, MemberAdmin) + +####################################################################### + +class CityInline(admin.TabularInline): + model = City + +class CityAdmin(admin.ModelAdmin): + list_display = ('name', 'state') + list_filter = ('state', ) + search_fields = ('name', ) + +admin.site.register(City, CityAdmin) + +####################################################################### + +class StateAdmin(admin.ModelAdmin): + inlines = [ + CityInline, + ] + +admin.site.register(State, StateAdmin) + +####################################################################### + +class VenueAdmin(admin.ModelAdmin): + list_filter = ('city', ) + list_display = ('name', 'city', ) + search_fields = ('name', ) + +admin.site.register(Venue, VenueAdmin) + +####################################################################### + +class BandAdmin(admin.ModelAdmin): + search_fields = ('name', ) + +admin.site.register(Band, BandAdmin) + +####################################################################### + +class GigAdmin(admin.ModelAdmin): + list_filter = ('date', 'venue') + save_on_top = True + filter_horizontal = ('bands', ) + +admin.site.register(Gig, GigAdmin) + +####################################################################### + +class NewsAdmin(admin.ModelAdmin): + save_on_top = True + list_filter = ('date', ) + list_display = ('date', 'title') + search_fields = ('text', 'title') + +admin.site.register(News, NewsAdmin) + +####################################################################### + +class ArticleAdmin(admin.ModelAdmin): + save_on_top = True + list_filter = ('date', ) + list_display = ('title', 'date') + search_fields = ('text', 'title') + +admin.site.register(Article, ArticleAdmin) + +####################################################################### + +class Mp3Inline(admin.TabularInline): + model = Mp3 + +class Mp3Admin(admin.ModelAdmin): + prepopulated_fields = {'slug' : ('title', 'desc')} + +admin.site.register(Mp3, Mp3Admin) + +####################################################################### + +class Mp3_SetAdmin(admin.ModelAdmin): + list_filter = ('date', ) + list_display = ('title', 'date') + inlines = [ + Mp3Inline, + ] + +admin.site.register(Mp3_Set, Mp3_SetAdmin) + +####################################################################### + +class VideoInline(admin.TabularInline): + model = Video + +class Video_SetAdmin(admin.ModelAdmin): + list_filter = ('date', ) + list_display = ('title', 'date') + inlines = [ + VideoInline, + ] + +admin.site.register(Video_Set, Video_SetAdmin) + +####################################################################### + +class Album_TrackInline(admin.TabularInline): + model = Album_Track + +class Album_TrackAdmin(admin.ModelAdmin): + list_display = ('track_name', 'album') + list_filter = ('album', ) + +admin.site.register(Album_Track, Album_TrackAdmin) + +####################################################################### + +class Label_ReleaseInline(admin.TabularInline): + model = Label_Release + +class Label_ReleaseAdmin(admin.ModelAdmin): + list_display = ('catalog_number', 'album', 'record_label', 'release_date') + list_filter = ('record_label', 'album') + +admin.site.register(Label_Release, Label_ReleaseAdmin) + +####################################################################### + +class Record_LabelAdmin(admin.ModelAdmin): + inlines = [ + Label_ReleaseInline, + ] + +admin.site.register(Record_Label, Record_LabelAdmin) + +####################################################################### + +class Album_MerchantInline(admin.TabularInline): + model = Album_Merchant + +class Album_MerchantAdmin(admin.ModelAdmin): + list_display = ('name', 'album') + list_filter = ('album', ) + +admin.site.register(Album_Merchant, Album_MerchantAdmin) + +####################################################################### + +class AlbumAdmin(admin.ModelAdmin): + save_on_top = True + inlines = [ + Album_TrackInline, + Label_ReleaseInline, + Album_MerchantInline, + ] + +admin.site.register(Album, AlbumAdmin) + +####################################################################### + +class MerchandiseAdmin(admin.ModelAdmin): + list_display = ('name', 'price', 'in_stock') + list_filter = ('in_stock', ) + +admin.site.register(Merchandise, MerchandiseAdmin) + +####################################################################### + +class FanAdmin(admin.ModelAdmin): + list_display = ('name', 'email', 'current_status') + search_fields = ('name', 'email') + +admin.site.register(Fan, FanAdmin) + +####################################################################### +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mysite/band/admin_views.py Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,77 @@ +####################################################################### +# +# PyBand Copyright (C) 2008 by Brian Neal +# +####################################################################### +from django import forms +from django.core.urlresolvers import reverse +from django.core.mail import EmailMessage +from django.template import RequestContext +from django.http import HttpResponseRedirect +from django.shortcuts import render_to_response +from django.contrib.admin.views.decorators import staff_member_required + +from mysite.band.models import SiteConfig +from mysite.band.models import Fan + +####################################################################### + +unsubscribeText = ''' + +---- +You are receiving this message because you are subscribed to our mailing list. +If you would like to unsubscribe please visit %s. +''' + +####################################################################### + +class EmailForm(forms.Form): + subject = forms.CharField(max_length = 255, required = True, label = 'Subject:', + widget = forms.TextInput(attrs = {'class' : 'vTextField required', 'size' : '60'})) + message = forms.CharField(label = 'Message:', + widget = forms.Textarea(attrs = {'class' : 'vLargeTextField required'})) + +####################################################################### + +def email_sent(request): + return render_to_response('admin/band/email_sent.html', + {}, + context_instance = RequestContext(request)) + +####################################################################### + +def email(request): + + config = SiteConfig.objects.get(pk = 1) + bandTag = '[%s] ' % (config.band_name, ) + + if request.method == 'POST': + form = EmailForm(request.POST) + if form.is_valid(): + subject = form.cleaned_data['subject'] + message = form.cleaned_data['message'] + + unsubscribeUrl = config.url + if unsubscribeUrl[-1] != '/': + unsubscribeUrl += '/' + unsubscribeUrl += 'contact' + + footer = unsubscribeText % (unsubscribeUrl, ) + message += footer + + fans = Fan.objects.all() + bcc = [fan.email for fan in fans] + + email = EmailMessage(subject, message, config.contact_email, + [config.contact_email], bcc) + email.send() + return HttpResponseRedirect(reverse(email_sent)) + + else: + form = EmailForm(initial = { 'subject' : bandTag }) + + return render_to_response('admin/band/email.html', + { 'form' : form }, + context_instance = RequestContext(request)) + +email = staff_member_required(email)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mysite/band/models.py Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,374 @@ +from django.db import models +from django.contrib.localflavor.us.models import USStateField +from django.contrib.localflavor.us.models import PhoneNumberField + +from mysite.photologue.models import Photo +import datetime +import random +import string + +####################################################################### + +class SiteConfig(models.Model): + band_name = models.CharField(max_length = 50) + url = models.URLField(verify_exists = False, max_length = 200) + contact_email = models.EmailField() + ordering_info = models.TextField(help_text = 'Enter instructions on how to order merchandise here') + intro_text = models.TextField(help_text = 'This text appears on the home page.') + intro_photo = models.ForeignKey(Photo) + + def __unicode__(self): + return self.band_name + + class Meta: + verbose_name = "Site Configuration" + verbose_name_plural = "Site Configuration" + +####################################################################### + +class Member(models.Model): + name = models.CharField(max_length = 50, db_index = True) + nickname = models.CharField(max_length = 50, blank = True) + instrument = models.CharField(max_length = 255) + bio = models.TextField(blank = True) + photo = models.FileField(upload_to = 'images/bio/', blank = True) + order = models.SmallIntegerField(help_text = '''Controls order of display on the bio page, lower numbers displayed + first''') + is_active = models.BooleanField(db_index = True) + start_date = models.DateField() + end_date = models.DateField(blank = True, help_text = 'Only used if the member is not active', + default = datetime.date(1985, 1, 1)) + email = models.EmailField() + + def __unicode__(self): + return self.name + + class Meta: + ordering = ('-is_active', 'name') + +####################################################################### + +class Gear(models.Model): + member = models.ForeignKey(Member) + item = models.CharField(max_length = 255) + + def __unicode__(self): + return self.item + + class Meta: + verbose_name_plural = 'Gear List' + +####################################################################### + +class State(models.Model): + name = models.CharField(max_length = 16) + abbrev = USStateField() + + class Meta: + ordering = ('name', ) + + def __unicode__(self): + return self.name + +####################################################################### + +class City(models.Model): + name = models.CharField(max_length = 50) + state = models.ForeignKey(State, null = True, blank = True) + + class Meta: + verbose_name_plural = 'Cities' + ordering = ('name', ) + + def __unicode__(self): + if self.state: + return self.name + u', ' + self.state.abbrev + return self.name + +####################################################################### + +class Venue(models.Model): + name = models.CharField(max_length = 50, db_index = True) + url = models.URLField(verify_exists = False, blank = True) + address = models.CharField(max_length = 255, blank = True) + phone = PhoneNumberField(help_text = "Format: XXX-XXX-XXXX", blank = True) + city = models.ForeignKey(City) + + class Meta: + ordering = ('name', ) + + def __unicode__(self): + return self.name + +####################################################################### + +class Band(models.Model): + name = models.CharField(max_length = 64) + url = models.URLField(verify_exists = False, blank = True) + + class Meta: + ordering = ('name', ) + + def __unicode__(self): + return self.name + +####################################################################### + +class Gig(models.Model): + title = models.CharField(max_length = 50, blank = True, help_text = "Optional; e.g. Some Festival") + url = models.URLField(verify_exists = False, blank = True, help_text = "Optional; e.g. Some Festival's Website") + date = models.DateField(db_index = True) + time = models.TimeField(null = True, blank = True) + venue = models.ForeignKey(Venue, null = True, blank = True) + notes = models.TextField(blank = True) + bands = models.ManyToManyField(Band, blank = True) + flyer = models.ForeignKey(Photo, null = True, blank = True) + + def __unicode__(self): + if self.title: + return u'%s %s %s' % (self.date.strftime('%m/%d/%Y'), self.title, self.venue.name) + elif self.venue: + return u'%s %s' % (self.date.strftime('%m/%d/%Y'), self.venue.name) + else: + return u'' + self.date.strftime('%m/%d/%Y') + + class Meta: + ordering = ('-date', 'time') + +####################################################################### + +class News(models.Model): + title = models.CharField(max_length = 64, blank = True) + date = models.DateField(db_index = True) + author = models.CharField(max_length = 50, blank = True) + text = models.TextField() + markup_enabled = models.BooleanField(default = True, + help_text = 'Check this box to allow Textile style markup in the text field') + photo = models.FileField(upload_to = 'images/news/%Y/%m/%d/', blank = True) + photo_caption = models.CharField(max_length = 50, blank = True) + + def __unicode__(self): + return u'%s %s' % (self.date.strftime('%m/%d/%Y'), self.title) + + class Meta: + ordering = ('-date', ) + verbose_name_plural = "News" + +####################################################################### + +class Article(models.Model): + title = models.CharField(max_length = 64) + date = models.DateField(db_index = True) + text = models.TextField() + markup_enabled = models.BooleanField(default = True, + help_text = 'Check this box to allow Textile style markup in the text field') + source = models.TextField(help_text = '''Enter the source/author for the article, copyright info, etc; it will appear under + the article.''') + url = models.URLField(blank = True, help_text = 'Link to original article; optional') + pdf = models.FileField(upload_to = 'pdf/articles/%Y/%m/%d/', blank = True, + help_text = '''If you want to make the original article available as a PDF download, you may upload it + here.''') + + def __unicode__(self): + return self.title + + class Meta: + ordering = ('date', ) + +####################################################################### + +class Mp3_Set(models.Model): + date = models.DateField(auto_now_add = True, editable = False) + title = models.CharField(max_length = 64) + text = models.TextField() + + def __unicode__(self): + return self.title + + class Meta: + ordering = ('date', ) + verbose_name = "MP3 Set" + +####################################################################### + +class Mp3(models.Model): + mp3_set = models.ForeignKey(Mp3_Set) + title = models.CharField(max_length = 64) + desc = models.CharField(max_length = 128, blank = True) + file = models.FileField(upload_to = 'mp3s/%Y/%m/%d/') + slug = models.SlugField(unique = True) + + def __unicode__(self): + return self.title + + class Meta: + ordering = ('title', ) + verbose_name = "MP3" + +####################################################################### + +class Video_Set(models.Model): + date = models.DateField(auto_now_add = True, editable = False) + title = models.CharField(max_length = 64) + text = models.TextField() + + def __unicode__(self): + return self.title + + class Meta: + ordering = ('date', ) + verbose_name = "Video Set" + +####################################################################### + +class Video(models.Model): + video_set = models.ForeignKey(Video_Set) + title = models.CharField(max_length = 64) + embed_code = models.CharField(max_length = 1024) + + def __unicode__(self): + return self.title + + class Meta: + ordering = ('title', ) + +####################################################################### + +class Record_Label(models.Model): + name = models.CharField(max_length = 64) + url = models.URLField(verify_exists = False, max_length = 200) + + def __unicode__(self): + return self.name + + class Meta: + verbose_name = 'Record Label' + +####################################################################### + +class Album(models.Model): + title = models.CharField(max_length = 64) + photo = models.ForeignKey(Photo) + desc = models.TextField(blank = True) + + def __unicode__(self): + return self.title + + class Meta: + pass + +####################################################################### + +class Album_Track(models.Model): + album = models.ForeignKey(Album) + track_number = models.SmallIntegerField() + track_name = models.CharField(max_length = 64) + + def __unicode__(self): + return self.track_name + + class Meta: + verbose_name = 'Album Track' + ordering = ('album', 'track_number', ) + +####################################################################### + +class Label_Release(models.Model): + record_label = models.ForeignKey(Record_Label) + album = models.ForeignKey(Album) + catalog_number = models.CharField(max_length = 32) + release_date = models.DateField() + + def __unicode__(self): + return u'%s %s %s' % (self.record_label.name, self.album.title, self.catalog_number) + + class Meta: + verbose_name = 'Label Release' + +####################################################################### + +class Album_Merchant(models.Model): + album = models.ForeignKey(Album) + name = models.CharField(max_length = 64) + url = models.URLField(verify_exists = False, max_length = 200) + + def __unicode__(self): + return u'%s (%s)' % (self.name, self.album.title) + + class Meta: + verbose_name = 'Album Merchant' + ordering = ('name', ) + +####################################################################### + +class Merchandise(models.Model): + name = models.CharField(max_length = 64) + desc = models.TextField() + price = models.DecimalField(max_digits = 5, decimal_places = 2) + in_stock = models.BooleanField() + photo = models.ForeignKey(Photo) + + def __unicode__(self): + return self.name + + class Meta: + verbose_name_plural = "Merchandise" + +####################################################################### + +class Fan(models.Model): + statusCodes = (('P', 'Pending'), ('A', 'Active'), ('L', 'Leaving')) + keyLength = 16 + + name = models.CharField(max_length = 32, blank = True) + email = models.EmailField(db_index = True) + location = models.CharField(max_length = 64, blank = True) + status = models.CharField(max_length = 1, choices = statusCodes, default = 'A', + editable = False, db_index = True) + key = models.CharField(max_length = keyLength, editable = False, blank = True, db_index = True) + status_date = models.DateField(default = datetime.date.today, editable = False, db_index = True) + + def __unicode__(self): + if self.name: + return u'%s <%s>' % (self.name, self.email) + return self.email + + class Meta: + ordering = ('name', 'email') + + def setPending(self): + self.status = 'P' + self.status_date = datetime.date.today() + self.genKey() + + def setActive(self): + self.status = 'A' + self.status_date = datetime.date.today() + + def setLeaving(self): + self.status = 'L' + self.status_date = datetime.date.today() + self.genKey() + + def isPending(self): + return self.status == 'P' + + def isLeaving(self): + return self.status == 'L' + + def isActive(self): + return self.status == 'A' + + def current_status(self): + if self.status == 'P': + return 'Pending' + elif self.status == 'L': + return 'Leaving' + elif self.status == 'A': + return 'Active' + else: + return 'Unknown' + + def genKey(self): + self.key = ''.join(random.sample(string.ascii_letters + string.digits, self.keyLength)) +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mysite/band/urls.py Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,28 @@ +from django.conf.urls.defaults import * + +urlpatterns = patterns('mysite.band.views', + (r'^$', 'index'), + (r'^bio/$', 'bio'), + (r'^buy/$', 'buy'), + (r'^contact/$', 'contact'), + (r'^gigs/$', 'gigs'), + (r'^gigs/flyers$', 'flyers'), + (r'^mail/$', 'mail'), + (r'^mail/confirm/([a-zA-Z0-9]+)$', 'mail_confirm'), + (r'^mail/not_found$', 'mail_not_found'), + (r'^mail/thanks$', 'mail_thanks'), + (r'^mail/unsubscribe$', 'mail_unsubscribe'), + (r'^news/$', 'news'), + (r'^photos/$', 'photos_index'), + (r'^photos/(\d+)$', 'photo_detail'), + (r'^press/$', 'press_index'), + (r'^press/(\d+)$', 'press_detail'), + (r'^songs/$', 'songs'), + (r'^videos/$', 'videos_index'), + (r'^videos/(\d+)$', 'video_detail'), +) + +urlpatterns += patterns('mysite.band.admin_views', + (r'^admin/band/email/$', 'email'), + (r'^admin/band/email_sent/$', 'email_sent'), +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mysite/band/views.py Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,319 @@ +####################################################################### +# +# PyBand Copyright (C) 2008 by Brian Neal +# +####################################################################### +from django import forms +from django.core.urlresolvers import reverse +from django.http import HttpResponse +from django.http import HttpResponseRedirect +from django.shortcuts import render_to_response +from django.shortcuts import get_object_or_404 +from django.template import RequestContext +from django.template.loader import render_to_string +from django.core.mail import send_mail +from django.db.models import Q +from mysite.band.models import Article +from mysite.band.models import Album +from mysite.band.models import Fan +from mysite.band.models import Gear +from mysite.band.models import Gig +from mysite.band.models import Member +from mysite.band.models import Merchandise +from mysite.band.models import Mp3 +from mysite.band.models import Mp3_Set +from mysite.band.models import News +from mysite.band.models import SiteConfig +from mysite.band.models import Video_Set +from mysite.photologue.models import Gallery +from mysite.photologue.models import Photo +import datetime +import random + +####################################################################### + +def index(request): + config = SiteConfig.objects.get(pk = 1) + carpe = Photo.objects.get(title_slug = 'carpe-noctem') + sandstorm = Photo.objects.get(title_slug = 'sandstorm-cover') + ruins = Photo.objects.get(title_slug = 'ruins-cover') + + upcomingDates = Gig.objects.filter(date__gte = datetime.date.today).order_by('date')[:5] + + #tourPhotos = Photo.objects.filter( + # Q(slug = 'flyer-aug-1-2008-san-diego') | + # Q(slug = 'flyer-aug-2-2008-hermosa-beach') + # ).order_by('id') + + return render_to_response('band/index.html', + { + 'config' : config, + 'carpe' : carpe, + 'sandstorm' : sandstorm, + 'ruins' : ruins, + 'upcomingDates' : upcomingDates, + # 'tourPhotos' : tourPhotos, + }, + context_instance = RequestContext(request)) + +####################################################################### + +def bio(request): + members = Member.objects.exclude(is_active__exact = 0) + + return render_to_response('band/bio.html', + { 'members' : members, }, + context_instance = RequestContext(request)) + +####################################################################### + +def gigs(request): + upcoming = Gig.objects.select_related().filter(date__gte = datetime.date.today).order_by('date') + previous = Gig.objects.select_related().filter(date__lt = datetime.date.today) + #upcoming = Gig.objects.filter(date__gte = datetime.date.today).order_by('date') + #previous = Gig.objects.filter(date__lt = datetime.date.today) + + stats = {} + venues = set([]) + cities = set([]) + states = set([]) + bands = set([]) + for gig in previous: + if gig.venue.id not in venues: + venues.add(gig.venue.id) + if gig.venue.city.id not in cities: + cities.add(gig.venue.city.id) + if gig.venue.city.state and gig.venue.city.state.id not in states: + states.add(gig.venue.city.state.id) + for band in gig.bands.all(): + if band.id not in bands: + bands.add(band.id) + + stats['count'] = previous.count() + stats['venues'] = len(venues) + stats['cities'] = len(cities) + stats['states'] = len(states) + stats['bands'] = len(bands) + + flyerGigs = Gig.objects.exclude(flyer__isnull = True).order_by('-date') + + return render_to_response('band/gigs.html', + { + 'upcoming' : upcoming, + 'previous' : previous, + 'stats' : stats, + 'flyerGigs' : flyerGigs, + }, + context_instance = RequestContext(request)) + +####################################################################### + +def news(request): + news = News.objects.order_by('-date') + + return render_to_response('band/news.html', + { + 'news' : news + }, + context_instance = RequestContext(request)) + +####################################################################### + +def press_index(request): + articles = Article.objects.order_by('-date') + + return render_to_response('band/press.html', + { + 'articles' : articles + }, + context_instance = RequestContext(request)) + +####################################################################### + +def press_detail(request, id): + article = get_object_or_404(Article, pk = id) + + return render_to_response('band/press_detail.html', + { 'article' : article }, + context_instance = RequestContext(request)) + +####################################################################### + +def songs(request): + mp3Sets = Mp3_Set.objects.order_by('-date', '-id') + + return render_to_response('band/songs.html', + { 'mp3Sets' : mp3Sets }, + context_instance = RequestContext(request)) + +####################################################################### + +def photos_index(request): + galleries = Gallery.objects.values('title', 'id').order_by('-id') + + photos = Photo.objects.filter(is_public__exact = 1) + randomPhotos = random.sample(photos, 4) + + return render_to_response('band/photos.html', + { 'galleries' : galleries, 'randomPhotos' : randomPhotos }, + context_instance = RequestContext(request)) + +####################################################################### + +def photo_detail(request, id): + gallery = get_object_or_404(Gallery, pk = id) + return render_to_response('band/photo_detail.html', + { 'gallery' : gallery }, + context_instance = RequestContext(request)) + +####################################################################### + +def videos_index(request): + vidsets = Video_Set.objects.values('title', 'id').order_by('-id') + return render_to_response('band/videos.html', + { 'vidsets' : vidsets }, + context_instance = RequestContext(request)) + +####################################################################### + +def video_detail(request, id): + vidset = get_object_or_404(Video_Set, pk = id) + + return render_to_response('band/video_detail.html', + { 'vidset' : vidset }, + context_instance = RequestContext(request)) + +####################################################################### + +def buy(request): + albums = Album.objects.all().order_by('-id') + merchandise = Merchandise.objects.all().order_by('-id') + config = SiteConfig.objects.values('ordering_info').get(pk = 1) + return render_to_response('band/buy.html', + { 'albums' : albums, 'merchandise' : merchandise, 'config' : config }, + context_instance = RequestContext(request)) + +####################################################################### + +def confirmEmail(config, to, subscribe, key): + band = config.band_name + fromEmail = config.contact_email + url = config.url + if url[-1] != '/': + url += '/' + url += 'mail/confirm/' + key + + if subscribe: + emailTemplate = 'band/email_subscribe.txt' + else: + emailTemplate = 'band/email_unsubscribe.txt' + + msg = render_to_string(emailTemplate, { 'band' : band, 'url' : url, 'band_url' : config.url }) + + subject = '[' + band + '] Mailing List Confirmation' + + send_mail(subject, msg, fromEmail, [to]) + +####################################################################### + +def contact(request): + config = SiteConfig.objects.get(pk = 1) + band = Member.objects.exclude(is_active__exact = 0).order_by('order') + return render_to_response('band/contact.html', + { 'config' : config, 'band' : band }, + context_instance = RequestContext(request)) + +####################################################################### + +class ContactForm(forms.Form): + name = forms.CharField(max_length = 32, required = False, + widget = forms.TextInput(attrs = {'class' : 'form-box'})) + email = forms.EmailField(widget = forms.TextInput(attrs = {'class' : 'form-box'})) + location = forms.CharField(max_length = 32, required = False, + widget = forms.TextInput(attrs = {'class' : 'form-box'})) + option = forms.ChoiceField(choices = (('subscribe', 'Subscribe'), ('unsubscribe', 'Unsubscribe')), + widget = forms.Select(attrs = {'class' : 'form-box'})) + +def mail(request): + config = SiteConfig.objects.get(pk = 1) + form = ContactForm() + if request.method == 'POST': + form = ContactForm(request.POST) + if form.is_valid(): + if form.cleaned_data['option'] == 'unsubscribe': + try: + fan = Fan.objects.get(email = form.cleaned_data['email']) + except Fan.DoesNotExist: + return HttpResponseRedirect(reverse(mail_not_found)) + + fan.setLeaving() + fan.save() + confirmEmail(config, fan.email, False, fan.key) + return HttpResponseRedirect(reverse(mail_unsubscribe)) + + elif form.cleaned_data['option'] == 'subscribe': + try: + fan = Fan.objects.get(email = form.cleaned_data['email']) + except Fan.DoesNotExist: + fan = Fan(name = form.cleaned_data['name'], + email = form.cleaned_data['email'], + location = form.cleaned_data['location']) + + fan.setPending() + fan.save() + confirmEmail(config, fan.email, True, fan.key) + return HttpResponseRedirect(reverse(mail_thanks)) + + return render_to_response('band/mail.html', + { 'form' : form }, + context_instance = RequestContext(request)) + +####################################################################### + +def mail_not_found(request): + return render_to_response('band/mail_not_found.html', + {}, + context_instance = RequestContext(request)) + +####################################################################### + +def mail_thanks(request): + return render_to_response('band/mail_thanks.html', + {}, + context_instance = RequestContext(request)) + +####################################################################### + +def mail_unsubscribe(request): + return render_to_response('band/mail_unsubscribe.html', + {}, + context_instance = RequestContext(request)) + +####################################################################### + +def mail_confirm(request, key): + fan = get_object_or_404(Fan, key = key) + + email = fan.email + action = 'subscribed' + + if fan.isPending(): + fan.setActive() + fan.save() + elif fan.isLeaving(): + fan.delete() + action = 'unsubscribed' + + return render_to_response('band/mail_confirm.html', + { 'email' : email, 'action' : action }, + context_instance = RequestContext(request)) + +####################################################################### + +def flyers(request): + + gigs = Gig.objects.exclude(flyer__isnull = True).order_by('-date') + + return render_to_response('band/flyers.html', + { 'gigs' : gigs }, + context_instance = RequestContext(request))
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mysite/manage.py Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,11 @@ +#!/usr/bin/env python +from django.core.management import execute_manager +try: + import settings # Assumed to be in the same directory. +except ImportError: + import sys + sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__) + sys.exit(1) + +if __name__ == "__main__": + execute_manager(settings)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mysite/photologue/LICENSE.txt Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,27 @@ +Copyright (c) 2007-2008, Justin C. Driscoll +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of django-photologue nor the names of its contributors may be used + to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mysite/photologue/README.txt Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,38 @@ +Installation + +Step 1 - Download Photologue + +Photologue can be downloaded below or from the project page. Older versions are also available from the project page and users who like to live on the edge can checkout a copy of the latest trunk revision. + +Step 2 - Add Photologue To Your Project + +Copy the entire Photologue application folder (the folder named 'photologue' that contains 'models.py') to a location on your Python path such as your project root. Your project root is typically the directory where your 'settings.py' is found. + +Step 3 - Configure Your Settings + +Add 'photologue' to your INSTALLED_APPS setting: + + INSTALLED_APPS = ( + # ...other installed applications, + 'photologue', + ) + +Confirm that your MEDIA_ROOT and MEDIA_URL settings are correct. + +If you want to tweak things even more you can also over-ride a few default settings (optional, see documentation for more information on the available settings). + +Step 4 - Register Photologue with the Django Admin + +Add the following to your projects urls.py file: + + from django.contrib import admin + + admin.autodiscover() + +Step 4 - Sync Your Database + +Run the 'manage.py syndb' command to create the appropriate tables. After the database in initialized, Photologue will walk you through creating some default models. + +Additional documentation available here: + +http://code.google.com/p/django-photologue/w/list
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mysite/photologue/admin.py Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,60 @@ +""" Newforms Admin configuration for Photologue + +""" +from django.contrib import admin +from models import * + +class GalleryAdmin(admin.ModelAdmin): + list_display = ('title', 'date_added', 'photo_count', 'is_public') + list_filter = ['date_added', 'is_public'] + date_hierarchy = 'date_added' + prepopulated_fields = {'title_slug': ('title',)} + filter_horizontal = ('photos',) + +class PhotoAdmin(admin.ModelAdmin): + list_display = ('title', 'date_taken', 'date_added', 'is_public', 'tags', 'view_count', 'admin_thumbnail') + list_filter = ['date_added', 'is_public'] + list_per_page = 10 + prepopulated_fields = {'title_slug': ('title',)} + +class PhotoEffectAdmin(admin.ModelAdmin): + list_display = ('name', 'description', 'admin_sample') + fieldsets = ( + (None, { + 'fields': ('name', 'description') + }), + ('Adjustments', { + 'fields': ('color', 'brightness', 'contrast', 'sharpness') + }), + ('Filters', { + 'fields': ('filters',) + }), + ('Reflection', { + 'fields': ('reflection_size', 'reflection_strength', 'background_color') + }), + ) + +class PhotoSizeAdmin(admin.ModelAdmin): + list_display = ('name', 'width', 'height', 'crop', 'pre_cache', 'effect', 'increment_count') + fieldsets = ( + (None, { + 'fields': ('name', 'width', 'height', 'quality') + }), + ('Options', { + 'fields': ('upscale', 'crop', 'pre_cache', 'increment_count') + }), + ('Enhancements', { + 'fields': ('effect', 'watermark',) + }), + ) + +class WatermarkAdmin(admin.ModelAdmin): + list_display = ('name', 'opacity', 'style') + + +admin.site.register(Gallery, GalleryAdmin) +admin.site.register(GalleryUpload) +admin.site.register(Photo, PhotoAdmin) +admin.site.register(PhotoEffect, PhotoEffectAdmin) +admin.site.register(PhotoSize, PhotoSizeAdmin) +admin.site.register(Watermark, WatermarkAdmin) \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mysite/photologue/locale/pl/LC_MESSAGES/django.po Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,419 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: Photologue Preview 2\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2008-07-22 23:05+0200\n" +"PO-Revision-Date: 2008-07-22 23:08+0100\n" +"Last-Translator: Jakub Wiśniowski <restless.being@gmail.com>\n" +"Language-Team: Jakub Wiśniowski <restless.being@gmail.com>\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\\n\n" +"X-Poedit-Language: Polish\n" +"X-Poedit-Country: POLAND\n" +"X-Poedit-SourceCharset: utf-8\n" + +#: models.py:32 +msgid "Photologue was unable to import the Python Imaging Library. Please confirm it`s installed and available on your current Python path." +msgstr "Photologue nie był w stanie zaimportować Python Imaging Library. Upewnij się, że pakiet ten jest zainstalowany i znajduje się w ścieżce dostępnej dla Pythona." + +#: models.py:38 +msgid "Separate tags with spaces, put quotes around multiple-word tags." +msgstr "Rozdziel tagi spacjami, ujmij w cudzysłowy tagi złożone z wielu słów." + +#: models.py:47 +msgid "Django-tagging was not found, tags will be treated as plain text." +msgstr "Django-tagging nie zostało znalezione. Tagi będą traktowane jako czysty tekst." + +#: models.py:64 +msgid "Very Low" +msgstr "Bardzo niska" + +#: models.py:65 +msgid "Low" +msgstr "Niska" + +#: models.py:66 +msgid "Medium-Low" +msgstr "Niższa średnia" + +#: models.py:67 +msgid "Medium" +msgstr "Średnia" + +#: models.py:68 +msgid "Medium-High" +msgstr "Wyższa średnia" + +#: models.py:69 +msgid "High" +msgstr "Wysoka" + +#: models.py:70 +msgid "Very High" +msgstr "Bardzo wysoka" + +#: models.py:75 +msgid "Top" +msgstr "Góra" + +#: models.py:76 +msgid "Right" +msgstr "Prawo" + +#: models.py:77 +msgid "Bottom" +msgstr "Dół" + +#: models.py:78 +msgid "Left" +msgstr "Lewo" + +#: models.py:79 +msgid "Center (Default)" +msgstr "Środek (Domyślnie)" + +#: models.py:83 +msgid "Flip left to right" +msgstr "Odbij w poziomie" + +#: models.py:84 +msgid "Flip top to bottom" +msgstr "Odbij w pionie" + +#: models.py:85 +msgid "Rotate 90 degrees counter-clockwise" +msgstr "Odwróć 90 stopni w lewo" + +#: models.py:86 +msgid "Rotate 90 degrees clockwise" +msgstr "Odwróć 90 stopni w prawo" + +#: models.py:87 +msgid "Rotate 180 degrees" +msgstr "Obróć o 180 stopni" + +#: models.py:91 +msgid "Tile" +msgstr "Kafelki" + +#: models.py:92 +msgid "Scale" +msgstr "Skaluj" + +#: models.py:102 +#, python-format +msgid "Chain multiple filters using the following pattern \"FILTER_ONE->FILTER_TWO->FILTER_THREE\". Image filters will be applied in order. The following filter are available: %s." +msgstr "Połącz wiele filtrów używając następującego wzorca: \"FILTR_PIERWSZY->FILTR_DRUGI->FILTR_TRZECI\". Filtry obrazów będą zastosowane w kolejności. Dostępne są następujące filtry: %s." + +#: models.py:107 +msgid "date published" +msgstr "data publikacji" + +#: models.py:108 +#: models.py:164 +#: models.py:448 +msgid "title" +msgstr "tytuł" + +#: models.py:109 +msgid "title slug" +msgstr "tytuł - slug " + +#: models.py:110 +msgid "A \"slug\" is a unique URL-friendly title for an object." +msgstr "\"Slug\" jest unikalnym, zgodnym z formatem dla URL-i tytułem obiektu." + +#: models.py:111 +#: models.py:166 +#: models.py:483 +msgid "description" +msgstr "opis" + +#: models.py:112 +#: models.py:167 +#: models.py:453 +msgid "is public" +msgstr "jest publiczna" + +#: models.py:113 +msgid "Public galleries will be displayed in the default views." +msgstr "Galerie publiczne będą wyświetlana w domyślnych widokach." + +#: models.py:114 +#: models.py:460 +msgid "photos" +msgstr "zdjęcia" + +#: models.py:116 +#: models.py:168 +#: models.py:454 +msgid "tags" +msgstr "tagi" + +#: models.py:121 +msgid "gallery" +msgstr "galeria" + +#: models.py:122 +msgid "galleries" +msgstr "galerie" + +#: models.py:155 +msgid "count" +msgstr "ilość" + +#: models.py:162 +msgid "images file (.zip)" +msgstr "plik z obrazami (.zip)" + +#: models.py:163 +msgid "Select a .zip file of images to upload into a new Gallery." +msgstr "Wybierz plik .zip zawierający zdjęcia które chcesz załadować do nowej Galerii." + +#: models.py:164 +msgid "All photos in the gallery will be given a title made up of the gallery title + a sequential number." +msgstr "Wszystkie " + +#: models.py:165 +#: models.py:451 +msgid "caption" +msgstr "podpis" + +#: models.py:165 +msgid "Caption will be added to all photos." +msgstr "Podpis będzie dodany do wszystkich zdjęć." + +#: models.py:166 +msgid "A description of this Gallery." +msgstr "Opis tej Galerii." + +#: models.py:167 +msgid "Uncheck this to make the uploaded gallery and included photographs private." +msgstr "Odznacz aby uczynić wrzucaną galerię oraz zawarte w niej zdjęcia prywatnymi." + +#: models.py:171 +msgid "gallery upload" +msgstr "wrzucona galeria" + +#: models.py:172 +msgid "gallery uploads" +msgstr "wrzucone galerie" + +#: models.py:228 +#: models.py:594 +msgid "image" +msgstr "obraz" + +#: models.py:229 +msgid "date taken" +msgstr "data wykonania" + +#: models.py:231 +msgid "crop from" +msgstr "obetnij z" + +#: models.py:232 +msgid "effect" +msgstr "efekt" + +#: models.py:250 +msgid "An \"admin_thumbnail\" photo size has not been defined." +msgstr "Rozmiar zdjęcia \"admin_thumbnail\" nie został zdefiniowany." + +#: models.py:258 +msgid "Thumbnail" +msgstr "Miniaturka" + +#: models.py:449 +msgid "slug" +msgstr "slug" + +#: models.py:452 +msgid "date added" +msgstr "data dodania" + +#: models.py:453 +msgid "Public photographs will be displayed in the default views." +msgstr "Publiczne zdjęcia będą wyświetlane w domyślnych widokach." + +#: models.py:459 +msgid "photo" +msgstr "zdjęcie" + +#: models.py:482 +#: models.py:608 +msgid "name" +msgstr "nazwa" + +#: models.py:554 +msgid "rotate or flip" +msgstr "obróć lub odbij" + +#: models.py:555 +#: models.py:562 +msgid "color" +msgstr "kolor" + +#: models.py:555 +msgid "A factor of 0.0 gives a black and white image, a factor of 1.0 gives the original image." +msgstr "Współczynnik 0.0 daje czarno-biały obraz, współczynnik 1.0 daje obraz oryginalny." + +#: models.py:556 +msgid "brightness" +msgstr "jasność" + +#: models.py:556 +msgid "A factor of 0.0 gives a black image, a factor of 1.0 gives the original image." +msgstr "Współczynnik 0.0 daje czarny obraz, współczynnik 1.0 daje obraz oryginalny." + +#: models.py:557 +msgid "contrast" +msgstr "kontrast" + +#: models.py:557 +msgid "A factor of 0.0 gives a solid grey image, a factor of 1.0 gives the original image." +msgstr "Współczynnik 0.0 daje jednolity szary obraz, współczynnik 1.0 daje obraz oryginalny." + +#: models.py:558 +msgid "sharpness" +msgstr "ostrość" + +#: models.py:558 +msgid "A factor of 0.0 gives a blurred image, a factor of 1.0 gives the original image." +msgstr "Współczynnik 0.0 daje rozmazany obraz, współczynnik 1.0 daje obraz oryginalny." + +#: models.py:559 +msgid "filters" +msgstr "filtry" + +#: models.py:560 +msgid "size" +msgstr "rozmiar" + +#: models.py:560 +msgid "The height of the reflection as a percentage of the orignal image. A factor of 0.0 adds no reflection, a factor of 1.0 adds a reflection equal to the height of the orignal image." +msgstr "Wysokość odbicia jako procent oryginalnego obrazu. Współczynnik 0.0 nie dodaje odbicia, współczynnik 1.0 dodaje odbicie równe wysokości oryginalnego obrazu." + +#: models.py:561 +msgid "strength" +msgstr "intensywność" + +#: models.py:565 +#: models.py:616 +msgid "photo effect" +msgstr "efekt zdjęcia" + +#: models.py:566 +msgid "photo effects" +msgstr "efekty zdjęć" + +#: models.py:595 +msgid "style" +msgstr "styl" + +#: models.py:596 +msgid "opacity" +msgstr "przeźroczystość" + +#: models.py:596 +msgid "The opacity of the overlay." +msgstr "Poziom przezroczystości" + +#: models.py:599 +msgid "watermark" +msgstr "znak wodny" + +#: models.py:600 +msgid "watermarks" +msgstr "znaki wodne" + +#: models.py:608 +msgid "Photo size name should contain only letters, numbers and underscores. Examples: \"thumbnail\", \"display\", \"small\", \"main_page_widget\"." +msgstr "Nazwa rozmiaru zdjęcia powinna zawierać tylko litery, cyfry i podkreślenia. Przykłady: \"miniatura\", \"wystawa\", \"male\", \"widget_strony_glownej\"." + +#: models.py:609 +msgid "width" +msgstr "szerokość" + +#: models.py:609 +msgid "If width is set to \"0\" the image will be scaled to the supplied height." +msgstr "Jeśli szerokość jest ustawiona na \"0\" to obraz będzie skalowany do podanej wysokości." + +#: models.py:610 +msgid "height" +msgstr "wysokość" + +#: models.py:610 +msgid "If height is set to \"0\" the image will be scaled to the supplied width" +msgstr "Jeśli wysokość jest ustawiona na \"0\" to obraz będzie skalowany do podanej szerokości." + +#: models.py:611 +msgid "quality" +msgstr "jakość" + +#: models.py:611 +msgid "JPEG image quality." +msgstr "Jakość obrazu JPEG" + +#: models.py:612 +msgid "upscale images?" +msgstr "skalować obrazy w górę?" + +#: models.py:612 +msgid "If selected the image will be scaled up if necessary to fit the supplied dimensions. Cropped sizes will be upscaled regardless of this setting." +msgstr "Jeśli zaznaczone to obraz będzie skalowany w górę tak aby pasował do podanych wymiarów. Obcinane rozmiary będą skalowane niezależnie od tego ustawienia." + +#: models.py:613 +msgid "crop to fit?" +msgstr "przyciąć aby pasował?" + +#: models.py:613 +msgid "If selected the image will be scaled and cropped to fit the supplied dimensions." +msgstr "Jeśli zaznaczone to obraz będzie skalowany i przycinany tak aby pasował do podanych wymiarów." + +#: models.py:614 +msgid "pre-cache?" +msgstr "wstępnie cachować?" + +#: models.py:614 +msgid "If selected this photo size will be pre-cached as photos are added." +msgstr "Jesli zaznaczone to ten rozmiar zdjęć będzie wstępnie cachowany przy dodawaniu zdjęć." + +#: models.py:615 +msgid "increment view count?" +msgstr "zwiększyć licznik odsłon?" + +#: models.py:615 +msgid "If selected the image's \"view_count\" will be incremented when this photo size is displayed." +msgstr "Jeśli zaznaczone to \"licznik_odslon\" będzie zwiększany gdy ten rozmiar zdjęcia będzie wyświetlany." + +#: models.py:617 +msgid "watermark image" +msgstr "oznacz kluczem wodnym" + +#: models.py:621 +msgid "photo size" +msgstr "rozmiar zdjęcia" + +#: models.py:622 +msgid "photo sizes" +msgstr "rozmiary zdjęć" + +#: models.py:640 +msgid "A PhotoSize must have a positive height or width." +msgstr "PhotoSize musi mieć dodatnią wysokość i szerokość." + +#~ msgid "Leave to size the image to the set height" +#~ msgstr "Ustaw aby przeskalować obraz do wybranej wysokości" +#~ msgid "Leave to size the image to the set width" +#~ msgstr "Ustaw aby przeskalować obraz do wybranej szerokości" +#~ msgid "original image" +#~ msgstr "oryginalny obraz" +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mysite/photologue/management/__init__.py Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,1 @@ +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mysite/photologue/management/commands/__init__.py Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,37 @@ +from photologue.models import PhotoSize + +def get_response(msg, func=int, default=None): + while True: + resp = raw_input(msg) + if not resp and default is not None: + return default + try: + return func(resp) + except: + print 'Invalid input.' + +def create_photosize(name, width=0, height=0, crop=False, pre_cache=False, increment_count=False): + try: + size = PhotoSize.objects.get(name=name) + exists = True + except PhotoSize.DoesNotExist: + size = PhotoSize(name=name) + exists = False + if exists: + msg = 'A "%s" photo size already exists. Do you want to replace it? (yes, no):' % name + if not get_response(msg, lambda inp: inp == 'yes', False): + return + print '\nWe will now define the "%s" photo size:\n' % size + w = get_response('Width (in pixels):', lambda inp: int(inp), width) + h = get_response('Height (in pixels):', lambda inp: int(inp), height) + c = get_response('Crop to fit? (yes, no):', lambda inp: inp == 'yes', crop) + p = get_response('Pre-cache? (yes, no):', lambda inp: inp == 'yes', pre_cache) + i = get_response('Increment count? (yes, no):', lambda inp: inp == 'yes', increment_count) + size.width = w + size.height = h + size.crop = c + size.pre_cache = p + size.increment_count = i + size.save() + print '\nA "%s" photo size has been created.\n' % name + return size \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mysite/photologue/management/commands/plcache.py Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,43 @@ +from django.core.management.base import BaseCommand, CommandError +from optparse import make_option +from photologue.models import PhotoSize, ImageModel + +class Command(BaseCommand): + option_list = BaseCommand.option_list + ( + make_option('--reset', '-r', action='store_true', dest='reset', help='Reset photo cache before generating'), + ) + + help = ('Manages Photologue cache file for the given sizes.') + args = '[sizes]' + + requires_model_validation = True + can_import_settings = True + + def handle(self, *args, **options): + return create_cache(args, options) + +def create_cache(sizes, options): + """ + Creates the cache for the given files + """ + reset = options.get('reset', None) + + size_list = [size.strip(' ,') for size in sizes] + + if len(size_list) < 1: + sizes = PhotoSize.objects.filter(pre_cache=True) + else: + sizes = PhotoSize.objects.filter(name__in=size_list) + + if not len(sizes): + raise CommandError('No photo sizes were found.') + + print 'Caching photos, this may take a while...' + + for cls in ImageModel.__subclasses__(): + for photosize in sizes: + print 'Cacheing %s size images' % photosize.name + for obj in cls.objects.all(): + if reset: + obj.remove_size(photosize) + obj.create_size(photosize)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mysite/photologue/management/commands/plcreatesize.py Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,13 @@ +from django.core.management.base import BaseCommand, CommandError +from photologue.management.commands import create_photosize + +class Command(BaseCommand): + help = ('Creates a new Photologue photo size interactively.') + requires_model_validation = True + can_import_settings = True + + def handle(self, *args, **options): + create_size(args[0]) + +def create_size(size): + create_photosize(size) \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mysite/photologue/management/commands/plflush.py Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,35 @@ +from django.core.management.base import BaseCommand, CommandError +from optparse import make_option +from photologue.models import PhotoSize, ImageModel + +class Command(BaseCommand): + help = ('Clears the Photologue cache for the given sizes.') + args = '[sizes]' + + requires_model_validation = True + can_import_settings = True + + def handle(self, *args, **options): + return create_cache(args, options) + +def create_cache(sizes, options): + """ + Clears the cache for the given files + """ + size_list = [size.strip(' ,') for size in sizes] + + if len(size_list) < 1: + sizes = PhotoSize.objects.all() + else: + sizes = PhotoSize.objects.filter(name__in=size_list) + + if not len(sizes): + raise CommandError('No photo sizes were found.') + + print 'Flushing cache...' + + for cls in ImageModel.__subclasses__(): + for photosize in sizes: + print 'Flushing %s size images' % photosize.name + for obj in cls.objects.all(): + obj.remove_size(photosize)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mysite/photologue/management/commands/plinit.py Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,30 @@ +from django.core.management.base import BaseCommand, CommandError +from photologue.management.commands import get_response, create_photosize +from photologue.models import PhotoEffect + +class Command(BaseCommand): + help = ('Prompts the user to set up the default photo sizes required by Photologue.') + requires_model_validation = True + can_import_settings = True + + def handle(self, *args, **kwargs): + return init(*args, **kwargs) + +def init(*args, **kwargs): + msg = '\nPhotologue requires a specific photo size to display thumbnail previews in the Django admin application.\nWould you like to generate this size now? (yes, no):' + if get_response(msg, lambda inp: inp == 'yes', False): + admin_thumbnail = create_photosize('admin_thumbnail', width=100, height=75, crop=True, pre_cache=True) + msg = 'Would you like to apply a sample enhancement effect to your admin thumbnails? (yes, no):' + if get_response(msg, lambda inp: inp == 'yes', False): + effect, created = PhotoEffect.objects.get_or_create(name='Enhance Thumbnail', description="Increases sharpness and contrast. Works well for smaller image sizes such as thumbnails.", contrast=1.2, sharpness=1.3) + admin_thumbnail.effect = effect + admin_thumbnail.save() + msg = '\nPhotologue comes with a set of templates for setting up a complete photo gallery. These templates require you to define both a "thumbnail" and "display" size.\nWould you like to define them now? (yes, no):' + if get_response(msg, lambda inp: inp == 'yes', False): + thumbnail = create_photosize('thumbnail', width=100, height=75) + display = create_photosize('display', width=400, increment_count=True) + msg = 'Would you like to apply a sample reflection effect to your display images? (yes, no):' + if get_response(msg, lambda inp: inp == 'yes', False): + effect, created = PhotoEffect.objects.get_or_create(name='Display Reflection', description="Generates a reflection with a white background", reflection_size=0.4) + display.effect = effect + display.save() \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mysite/photologue/models.py Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,700 @@ +import os +import random +import shutil +import zipfile + +from datetime import datetime +from inspect import isclass + +from django.db import models +from django.db.models.signals import post_init +from django.conf import settings +from django.core.files.base import ContentFile +from django.core.urlresolvers import reverse +from django.template.defaultfilters import slugify +from django.utils.functional import curry +from django.utils.translation import ugettext_lazy as _ + +# Required PIL classes may or may not be available from the root namespace +# depending on the installation method used. +try: + import Image + import ImageFile + import ImageFilter + import ImageEnhance +except ImportError: + try: + from PIL import Image + from PIL import ImageFile + from PIL import ImageFilter + from PIL import ImageEnhance + except ImportError: + raise ImportError(_('Photologue was unable to import the Python Imaging Library. Please confirm it`s installed and available on your current Python path.')) + +# attempt to load the django-tagging TagField from default location, +# otherwise we substitude a dummy TagField. +try: + from tagging.fields import TagField + tagfield_help_text = _('Separate tags with spaces, put quotes around multiple-word tags.') +except ImportError: + class TagField(models.CharField): + def __init__(self, **kwargs): + default_kwargs = {'max_length': 255, 'blank': True} + default_kwargs.update(kwargs) + super(TagField, self).__init__(**default_kwargs) + def get_internal_type(self): + return 'CharField' + tagfield_help_text = _('Django-tagging was not found, tags will be treated as plain text.') + +from utils import EXIF +from utils.reflection import add_reflection +from utils.watermark import apply_watermark + +# Path to sample image +SAMPLE_IMAGE_PATH = getattr(settings, 'SAMPLE_IMAGE_PATH', os.path.join(os.path.dirname(__file__), 'res', 'sample.jpg')) # os.path.join(settings.PROJECT_PATH, 'photologue', 'res', 'sample.jpg' + +# Modify image file buffer size. +ImageFile.MAXBLOCK = getattr(settings, 'PHOTOLOGUE_MAXBLOCK', 256 * 2 ** 10) + +# Photologue image path relative to media root +PHOTOLOGUE_DIR = getattr(settings, 'PHOTOLOGUE_DIR', 'photologue') + +# Look for user function to define file paths +PHOTOLOGUE_PATH = getattr(settings, 'PHOTOLOGUE_PATH', None) +if PHOTOLOGUE_PATH is not None: + if callable(PHOTOLOGUE_PATH): + get_storage_path = PHOTOLOGUE_PATH + else: + parts = PHOTOLOGUE_PATH.split('.') + module_name = '.'.join(parts[:-1]) + module = __import__(module_name) + get_storage_path = getattr(module, parts[-1]) +else: + def get_storage_path(instance, filename): + return os.path.join(PHOTOLOGUE_DIR, 'photos', filename) + +# Quality options for JPEG images +JPEG_QUALITY_CHOICES = ( + (30, _('Very Low')), + (40, _('Low')), + (50, _('Medium-Low')), + (60, _('Medium')), + (70, _('Medium-High')), + (80, _('High')), + (90, _('Very High')), +) + +# choices for new crop_anchor field in Photo +CROP_ANCHOR_CHOICES = ( + ('top', _('Top')), + ('right', _('Right')), + ('bottom', _('Bottom')), + ('left', _('Left')), + ('center', _('Center (Default)')), +) + +IMAGE_TRANSPOSE_CHOICES = ( + ('FLIP_LEFT_RIGHT', _('Flip left to right')), + ('FLIP_TOP_BOTTOM', _('Flip top to bottom')), + ('ROTATE_90', _('Rotate 90 degrees counter-clockwise')), + ('ROTATE_270', _('Rotate 90 degrees clockwise')), + ('ROTATE_180', _('Rotate 180 degrees')), +) + +WATERMARK_STYLE_CHOICES = ( + ('tile', _('Tile')), + ('scale', _('Scale')), +) + +# Prepare a list of image filters +filter_names = [] +for n in dir(ImageFilter): + klass = getattr(ImageFilter, n) + if isclass(klass) and issubclass(klass, ImageFilter.BuiltinFilter) and \ + hasattr(klass, 'name'): + filter_names.append(klass.__name__) +IMAGE_FILTERS_HELP_TEXT = _('Chain multiple filters using the following pattern "FILTER_ONE->FILTER_TWO->FILTER_THREE". Image filters will be applied in order. The following filters are available: %s.' % (', '.join(filter_names))) + + +class Gallery(models.Model): + date_added = models.DateTimeField(_('date published'), default=datetime.now) + title = models.CharField(_('title'), max_length=100, unique=True) + title_slug = models.SlugField(_('title slug'), unique=True, + help_text=_('A "slug" is a unique URL-friendly title for an object.')) + description = models.TextField(_('description'), blank=True) + is_public = models.BooleanField(_('is public'), default=True, + help_text=_('Public galleries will be displayed in the default views.')) + photos = models.ManyToManyField('Photo', related_name='galleries', verbose_name=_('photos'), + null=True, blank=True) + tags = TagField(help_text=tagfield_help_text, verbose_name=_('tags')) + + class Meta: + ordering = ['-date_added'] + get_latest_by = 'date_added' + verbose_name = _('gallery') + verbose_name_plural = _('galleries') + + def __unicode__(self): + return self.title + + def __str__(self): + return self.__unicode__() + + def get_absolute_url(self): + return reverse('pl-gallery', args=[self.title_slug]) + + def latest(self, limit=0, public=True): + if limit == 0: + limit = self.photo_count() + if public: + return self.public()[:limit] + else: + return self.photos.all()[:limit] + + def sample(self, count=0, public=True): + if count == 0 or count > self.photo_count(): + count = self.photo_count() + if public: + photo_set = self.public() + else: + photo_set = self.photos.all() + return random.sample(photo_set, count) + + def photo_count(self, public=True): + if public: + return self.public().count() + else: + return self.photos.all().count() + photo_count.short_description = _('count') + + def public(self): + return self.photos.filter(is_public=True) + + +class GalleryUpload(models.Model): + zip_file = models.FileField(_('images file (.zip)'), upload_to=PHOTOLOGUE_DIR+"/temp", + help_text=_('Select a .zip file of images to upload into a new Gallery.')) + gallery = models.ForeignKey(Gallery, null=True, blank=True, help_text=_('Select a gallery to add these images to. leave this empty to create a new gallery from the supplied title.')) + title = models.CharField(_('title'), max_length=75, help_text=_('All photos in the gallery will be given a title made up of the gallery title + a sequential number.')) + caption = models.TextField(_('caption'), blank=True, help_text=_('Caption will be added to all photos.')) + description = models.TextField(_('description'), blank=True, help_text=_('A description of this Gallery.')) + is_public = models.BooleanField(_('is public'), default=True, help_text=_('Uncheck this to make the uploaded gallery and included photographs private.')) + tags = models.CharField(max_length=255, blank=True, help_text=tagfield_help_text, verbose_name=_('tags')) + + class Meta: + verbose_name = _('gallery upload') + verbose_name_plural = _('gallery uploads') + + def save(self): + super(GalleryUpload, self).save() + self.process_zipfile() + super(GalleryUpload, self).delete() + + def process_zipfile(self): + if os.path.isfile(self.zip_file.path): + # TODO: implement try-except here + zip = zipfile.ZipFile(self.zip_file.path) + bad_file = zip.testzip() + if bad_file: + raise Exception('"%s" in the .zip archive is corrupt.' % bad_file) + count = 1 + if self.gallery: + gallery = self.gallery + else: + gallery = Gallery.objects.create(title=self.title, + title_slug=slugify(self.title), + description=self.description, + is_public=self.is_public, + tags=self.tags) + from cStringIO import StringIO + for filename in zip.namelist(): + if filename.startswith('__'): # do not process meta files + continue + data = zip.read(filename) + if len(data): + try: + # the following is taken from django.newforms.fields.ImageField: + # load() is the only method that can spot a truncated JPEG, + # but it cannot be called sanely after verify() + trial_image = Image.open(StringIO(data)) + trial_image.load() + # verify() is the only method that can spot a corrupt PNG, + # but it must be called immediately after the constructor + trial_image = Image.open(StringIO(data)) + trial_image.verify() + except Exception: + # if a "bad" file is found we just skip it. + continue + while 1: + title = ' '.join([self.title, str(count)]) + slug = slugify(title) + try: + p = Photo.objects.get(title_slug=slug) + except Photo.DoesNotExist: + photo = Photo(title=title, title_slug=slug, + caption=self.caption, + is_public=self.is_public, + tags=self.tags) + photo.image.save(filename, ContentFile(data)) + gallery.photos.add(photo) + count = count + 1 + break + count = count + 1 + zip.close() + + +class ImageModel(models.Model): + image = models.ImageField(_('image'), upload_to=get_storage_path) + date_taken = models.DateTimeField(_('date taken'), null=True, blank=True, editable=False) + view_count = models.PositiveIntegerField(default=0, editable=False) + crop_from = models.CharField(_('crop from'), blank=True, max_length=10, default='center', choices=CROP_ANCHOR_CHOICES) + effect = models.ForeignKey('PhotoEffect', null=True, blank=True, related_name="%(class)s_related", verbose_name=_('effect')) + + class Meta: + abstract = True + + @property + def EXIF(self): + try: + return EXIF.process_file(open(self.image.path, 'rb')) + except: + try: + return EXIF.process_file(open(self.image.path, 'rb'), details=False) + except: + return {} + + def admin_thumbnail(self): + func = getattr(self, 'get_admin_thumbnail_url', None) + if func is None: + return _('An "admin_thumbnail" photo size has not been defined.') + else: + if hasattr(self, 'get_absolute_url'): + return u'<a href="%s"><img src="%s"></a>' % \ + (self.get_absolute_url(), func()) + else: + return u'<a href="%s"><img src="%s"></a>' % \ + (self.image.url, func()) + admin_thumbnail.short_description = _('Thumbnail') + admin_thumbnail.allow_tags = True + + def cache_path(self): + return os.path.join(os.path.dirname(self.image.path), "cache") + + def cache_url(self): + return '/'.join([os.path.dirname(self.image.url), "cache"]) + + def image_filename(self): + return os.path.basename(self.image.path) + + def _get_filename_for_size(self, size): + size = getattr(size, 'name', size) + base, ext = os.path.splitext(self.image_filename()) + return ''.join([base, '_', size, ext]) + + def _get_SIZE_photosize(self, size): + return PhotoSizeCache().sizes.get(size) + + def _get_SIZE_size(self, size): + photosize = PhotoSizeCache().sizes.get(size) + if not self.size_exists(photosize): + self.create_size(photosize) + return Image.open(self._get_SIZE_filename(size)).size + + def _get_SIZE_url(self, size): + photosize = PhotoSizeCache().sizes.get(size) + if not self.size_exists(photosize): + self.create_size(photosize) + if photosize.increment_count: + self.view_count += 1 + self.save(update=True) + return '/'.join([self.cache_url(), self._get_filename_for_size(photosize.name)]) + + def _get_SIZE_filename(self, size): + photosize = PhotoSizeCache().sizes.get(size) + return os.path.join(self.cache_path(), + self._get_filename_for_size(photosize.name)) + + def add_accessor_methods(self, *args, **kwargs): + for size in PhotoSizeCache().sizes.keys(): + setattr(self, 'get_%s_size' % size, + curry(self._get_SIZE_size, size=size)) + setattr(self, 'get_%s_photosize' % size, + curry(self._get_SIZE_photosize, size=size)) + setattr(self, 'get_%s_url' % size, + curry(self._get_SIZE_url, size=size)) + setattr(self, 'get_%s_filename' % size, + curry(self._get_SIZE_filename, size=size)) + + def size_exists(self, photosize): + func = getattr(self, "get_%s_filename" % photosize.name, None) + if func is not None: + if os.path.isfile(func()): + return True + return False + + def resize_image(self, im, photosize): + cur_width, cur_height = im.size + new_width, new_height = photosize.size + if photosize.crop: + ratio = max(float(new_width)/cur_width,float(new_height)/cur_height) + x = (cur_width * ratio) + y = (cur_height * ratio) + xd = abs(new_width - x) + yd = abs(new_height - y) + x_diff = int(xd / 2) + y_diff = int(yd / 2) + if self.crop_from == 'top': + box = (int(x_diff), 0, int(x_diff+new_width), new_height) + elif self.crop_from == 'left': + box = (0, int(y_diff), new_width, int(y_diff+new_height)) + elif self.crop_from == 'bottom': + box = (int(x_diff), int(yd), int(x_diff+new_width), int(y)) # y - yd = new_height + elif self.crop_from == 'right': + box = (int(xd), int(y_diff), int(x), int(y_diff+new_height)) # x - xd = new_width + else: + box = (int(x_diff), int(y_diff), int(x_diff+new_width), int(y_diff+new_height)) + im = im.resize((int(x), int(y)), Image.ANTIALIAS).crop(box) + else: + if not new_width == 0 and not new_height == 0: + ratio = min(float(new_width)/cur_width, + float(new_height)/cur_height) + else: + if new_width == 0: + ratio = float(new_height)/cur_height + else: + ratio = float(new_width)/cur_width + new_dimensions = (int(round(cur_width*ratio)), + int(round(cur_height*ratio))) + if new_dimensions[0] > cur_width or \ + new_dimensions[1] > cur_height: + if not photosize.upscale: + return im + im = im.resize(new_dimensions, Image.ANTIALIAS) + return im + + def create_size(self, photosize): + if self.size_exists(photosize): + return + if not os.path.isdir(self.cache_path()): + os.makedirs(self.cache_path()) + try: + im = Image.open(self.image.path) + except IOError: + return + # Apply effect if found + if self.effect is not None: + im = self.effect.pre_process(im) + elif photosize.effect is not None: + im = photosize.effect.pre_process(im) + # Resize/crop image + if im.size != photosize.size: + im = self.resize_image(im, photosize) + # Apply watermark if found + if photosize.watermark is not None: + im = photosize.watermark.post_process(im) + # Apply effect if found + if self.effect is not None: + im = self.effect.post_process(im) + elif photosize.effect is not None: + im = photosize.effect.post_process(im) + # Save file + im_filename = getattr(self, "get_%s_filename" % photosize.name)() + try: + if im.format == 'JPEG': + im.save(im_filename, 'JPEG', quality=int(photosize.quality), optimize=True) + else: + im.save(im_filename) + except IOError, e: + if os.path.isfile(im_filename): + os.unlink(im_filename) + raise e + + def remove_size(self, photosize, remove_dirs=True): + if not self.size_exists(photosize): + return + filename = getattr(self, "get_%s_filename" % photosize.name)() + if os.path.isfile(filename): + os.remove(filename) + if remove_dirs: + self.remove_cache_dirs() + + def clear_cache(self): + cache = PhotoSizeCache() + for photosize in cache.sizes.values(): + self.remove_size(photosize, False) + self.remove_cache_dirs() + + def pre_cache(self): + cache = PhotoSizeCache() + for photosize in cache.sizes.values(): + if photosize.pre_cache: + self.create_size(photosize) + + def remove_cache_dirs(self): + try: + os.removedirs(self.cache_path()) + except: + pass + + def save(self, update=False): + if update: + models.Model.save(self) + return + if self.date_taken is None: + try: + exif_date = self.EXIF.get('EXIF DateTimeOriginal', None) + if exif_date is not None: + d, t = str.split(exif_date.values) + year, month, day = d.split(':') + hour, minute, second = t.split(':') + self.date_taken = datetime(int(year), int(month), int(day), + int(hour), int(minute), int(second)) + except: + pass + if self.date_taken is None: + self.date_taken = datetime.now() + if self._get_pk_val(): + self.clear_cache() + super(ImageModel, self).save() + self.pre_cache() + + def delete(self): + self.clear_cache() + super(ImageModel, self).delete() + + +class Photo(ImageModel): + title = models.CharField(_('title'), max_length=100, unique=True) + title_slug = models.SlugField(_('slug'), unique=True, + help_text=('A "slug" is a unique URL-friendly title for an object.')) + caption = models.TextField(_('caption'), blank=True) + date_added = models.DateTimeField(_('date added'), default=datetime.now, editable=False) + is_public = models.BooleanField(_('is public'), default=True, help_text=_('Public photographs will be displayed in the default views.')) + tags = TagField(help_text=tagfield_help_text, verbose_name=_('tags')) + + class Meta: + ordering = ['-date_added'] + get_latest_by = 'date_added' + verbose_name = _("photo") + verbose_name_plural = _("photos") + + def __unicode__(self): + return self.title + + def __str__(self): + return self.__unicode__() + + def save(self, update=False): + if self.title_slug is None: + self.title_slug = slugify(self.title) + super(Photo, self).save(update) + + def get_absolute_url(self): + return reverse('pl-photo', args=[self.title_slug]) + + def public_galleries(self): + """Return the public galleries to which this photo belongs.""" + return self.galleries.filter(is_public=True) + + +class BaseEffect(models.Model): + name = models.CharField(_('name'), max_length=30, unique=True) + description = models.TextField(_('description'), blank=True) + + class Meta: + abstract = True + + def sample_dir(self): + return os.path.join(settings.MEDIA_ROOT, PHOTOLOGUE_DIR, 'samples') + + def sample_url(self): + return settings.MEDIA_URL + '/'.join([PHOTOLOGUE_DIR, 'samples', '%s %s.jpg' % (self.name.lower(), 'sample')]) + + def sample_filename(self): + return os.path.join(self.sample_dir(), '%s %s.jpg' % (self.name.lower(), 'sample')) + + def create_sample(self): + if not os.path.isdir(self.sample_dir()): + os.makedirs(self.sample_dir()) + try: + im = Image.open(SAMPLE_IMAGE_PATH) + except IOError: + raise IOError('Photologue was unable to open the sample image: %s.' % SAMPLE_IMAGE_PATH) + im = self.process(im) + im.save(self.sample_filename(), 'JPEG', quality=90, optimize=True) + + def admin_sample(self): + return u'<img src="%s">' % self.sample_url() + admin_sample.short_description = 'Sample' + admin_sample.allow_tags = True + + def pre_process(self, im): + return im + + def post_process(self, im): + return im + + def process(self, im): + im = self.pre_process(im) + im = self.post_process(im) + return im + + def __unicode__(self): + return self.name + + def __str__(self): + return self.__unicode__() + + def save(self): + try: + os.remove(self.sample_filename()) + except: + pass + models.Model.save(self) + self.create_sample() + for size in self.photo_sizes.all(): + size.clear_cache() + # try to clear all related subclasses of ImageModel + for prop in [prop for prop in dir(self) if prop[-8:] == '_related']: + for obj in getattr(self, prop).all(): + obj.clear_cache() + obj.pre_cache() + + def delete(self): + try: + os.remove(self.sample_filename()) + except: + pass + super(PhotoEffect, self).delete() + + +class PhotoEffect(BaseEffect): + """ A pre-defined effect to apply to photos """ + transpose_method = models.CharField(_('rotate or flip'), max_length=15, blank=True, choices=IMAGE_TRANSPOSE_CHOICES) + color = models.FloatField(_('color'), default=1.0, help_text=_("A factor of 0.0 gives a black and white image, a factor of 1.0 gives the original image.")) + brightness = models.FloatField(_('brightness'), default=1.0, help_text=_("A factor of 0.0 gives a black image, a factor of 1.0 gives the original image.")) + contrast = models.FloatField(_('contrast'), default=1.0, help_text=_("A factor of 0.0 gives a solid grey image, a factor of 1.0 gives the original image.")) + sharpness = models.FloatField(_('sharpness'), default=1.0, help_text=_("A factor of 0.0 gives a blurred image, a factor of 1.0 gives the original image.")) + filters = models.CharField(_('filters'), max_length=200, blank=True, help_text=_(IMAGE_FILTERS_HELP_TEXT)) + reflection_size = models.FloatField(_('size'), default=0, help_text=_("The height of the reflection as a percentage of the orignal image. A factor of 0.0 adds no reflection, a factor of 1.0 adds a reflection equal to the height of the orignal image.")) + reflection_strength = models.FloatField(_('strength'), default=0.6, help_text="The initial opacity of the reflection gradient.") + background_color = models.CharField(_('color'), max_length=7, default="#FFFFFF", help_text="The background color of the reflection gradient. Set this to match the background color of your page.") + + class Meta: + verbose_name = _("photo effect") + verbose_name_plural = _("photo effects") + + def pre_process(self, im): + if self.transpose_method != '': + method = getattr(Image, self.transpose_method) + im = im.transpose(method) + if im.mode != 'RGB' and im.mode != 'RGBA': + return im + for name in ['Color', 'Brightness', 'Contrast', 'Sharpness']: + factor = getattr(self, name.lower()) + if factor != 1.0: + im = getattr(ImageEnhance, name)(im).enhance(factor) + for name in self.filters.split('->'): + image_filter = getattr(ImageFilter, name.upper(), None) + if image_filter is not None: + try: + im = im.filter(image_filter) + except ValueError: + pass + return im + + def post_process(self, im): + if self.reflection_size != 0.0: + im = add_reflection(im, bgcolor=self.background_color, amount=self.reflection_size, opacity=self.reflection_strength) + return im + + +class Watermark(BaseEffect): + image = models.ImageField(_('image'), upload_to=PHOTOLOGUE_DIR+"/watermarks") + style = models.CharField(_('style'), max_length=5, choices=WATERMARK_STYLE_CHOICES, default='scale') + opacity = models.FloatField(_('opacity'), default=1, help_text=_("The opacity of the overlay.")) + + class Meta: + verbose_name = _('watermark') + verbose_name_plural = _('watermarks') + + def post_process(self, im): + mark = Image.open(self.image.path) + return apply_watermark(im, mark, self.style, self.opacity) + + +class PhotoSize(models.Model): + name = models.CharField(_('name'), max_length=20, unique=True, help_text=_('Photo size name should contain only letters, numbers and underscores. Examples: "thumbnail", "display", "small", "main_page_widget".')) + width = models.PositiveIntegerField(_('width'), default=0, help_text=_('If width is set to "0" the image will be scaled to the supplied height.')) + height = models.PositiveIntegerField(_('height'), default=0, help_text=_('If height is set to "0" the image will be scaled to the supplied width')) + quality = models.PositiveIntegerField(_('quality'), choices=JPEG_QUALITY_CHOICES, default=70, help_text=_('JPEG image quality.')) + upscale = models.BooleanField(_('upscale images?'), default=False, help_text=_('If selected the image will be scaled up if necessary to fit the supplied dimensions. Cropped sizes will be upscaled regardless of this setting.')) + crop = models.BooleanField(_('crop to fit?'), default=False, help_text=_('If selected the image will be scaled and cropped to fit the supplied dimensions.')) + pre_cache = models.BooleanField(_('pre-cache?'), default=False, help_text=_('If selected this photo size will be pre-cached as photos are added.')) + increment_count = models.BooleanField(_('increment view count?'), default=False, help_text=_('If selected the image\'s "view_count" will be incremented when this photo size is displayed.')) + effect = models.ForeignKey('PhotoEffect', null=True, blank=True, related_name='photo_sizes', verbose_name=_('photo effect')) + watermark = models.ForeignKey('Watermark', null=True, blank=True, related_name='photo_sizes', verbose_name=_('watermark image')) + + class Meta: + ordering = ['width', 'height'] + verbose_name = _('photo size') + verbose_name_plural = _('photo sizes') + + def __unicode__(self): + return self.name + + def __str__(self): + return self.__unicode__() + + def clear_cache(self): + for cls in ImageModel.__subclasses__(): + for obj in cls.objects.all(): + obj.remove_size(self) + if self.pre_cache: + obj.create_size(self) + PhotoSizeCache().reset() + + def save(self): + if self.width + self.height <= 0: + raise ValueError(_('A PhotoSize must have a positive height or width.')) + super(PhotoSize, self).save() + PhotoSizeCache().reset() + self.clear_cache() + + def delete(self): + self.clear_cache() + super(PhotoSize, self).delete() + + def _get_size(self): + return (self.width, self.height) + def _set_size(self, value): + self.width, self.height = value + size = property(_get_size, _set_size) + + +class PhotoSizeCache(object): + __state = {"sizes": {}} + + def __init__(self): + self.__dict__ = self.__state + if not len(self.sizes): + sizes = PhotoSize.objects.all() + for size in sizes: + self.sizes[size.name] = size + + def reset(self): + self.sizes = {} + + +# Set up the accessor methods +def add_methods(sender, instance, signal, *args, **kwargs): + """ Adds methods to access sized images (urls, paths) + + after the Photo model's __init__ function completes, + this method calls "add_accessor_methods" on each instance. + """ + if hasattr(instance, 'add_accessor_methods'): + instance.add_accessor_methods() + +# connect the add_accessor_methods function to the post_init signal +post_init.connect(add_methods)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mysite/photologue/templates/photologue/gallery_archive.html Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,26 @@ +{% extends "photologue/root.html" %} + +{% block title %}Latest Photo Galleries{% endblock %} + +{% block content %} + +<h1>Latest Photo Galleries</h1> + +{% if latest %} + {% for gallery in latest %} + <div class="photo-gallery"> + <h2><a href="{{ gallery.get_absolute_url }}">{{ gallery.title }}</a></h2> + {% for photo in gallery.sample|slice:sample_size %} + <div class="gallery-photo"> + <a href="{{ photo.get_absolute_url }}"><img src="{{ photo.get_thumbnail_url }}" alt="{{ photo.title }}"/></a> + </div> + {% endfor %} + </div> + {% endfor %} +{% else %} + <p>No galleries were found.</p> +{% endif %} + +<p><a href="{% url pl-gallery-list 1 %}">View all galleries.</a></p> + +{% endblock %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mysite/photologue/templates/photologue/gallery_archive_day.html Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,26 @@ +{% extends "photologue/root.html" %} + +{% block title %}Galleries for {{ day|date }}{% endblock %} + +{% block content %} + +<h1>Galleries for {{ day|date }}</h1> + +{% if object_list %} + {% for gallery in object_list %} + <div class="photo-gallery"> + <h2>{{ gallery.title }}</h2> + {% for photo in gallery.sample|slice:sample_size %} + <div class="gallery-photo"> + <a href="{{ photo.get_absolute_url }}"><img src="{{ photo.get_thumbnail_url }}" alt="{{ photo.title }}"/></a> + </div> + {% endfor %} + </div> + {% endfor %} +{% else %} + <p>No galleries were found.</p> +{% endif %} + +<p><a href="{% url pl-gallery-list 1 %}">View all galleries.</a></p> + +{% endblock %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mysite/photologue/templates/photologue/gallery_archive_month.html Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,26 @@ +{% extends "photologue/root.html" %} + +{% block title %}Galleries for {{ month|date:"F Y" }}{% endblock %} + +{% block content %} + +<h1>Galleries for {{ month|date:"F Y" }}</h1> + +{% if object_list %} + {% for gallery in object_list %} + <div class="photo-gallery"> + <h2>{{ gallery.title }}</h2> + {% for photo in gallery.sample|slice:sample_size %} + <div class="gallery-photo"> + <a href="{{ photo.get_absolute_url }}"><img src="{{ photo.get_thumbnail_url }}" alt="{{ photo.title }}"/></a> + </div> + {% endfor %} + </div> + {% endfor %} +{% else %} + <p>No galleries were found.</p> +{% endif %} + +<p><a href="{% url pl-gallery-list 1 %}">View all galleries.</a></p> + +{% endblock %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mysite/photologue/templates/photologue/gallery_archive_year.html Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,16 @@ +{% extends "photologue/root.html" %} + +{% block title %}Galleries for {{ year }}{% endblock %} + +{% block content %} + +<h1>Galleries for {{ year }}</h1> +<ul> +{% for date in date_list %} +<li><a href="{{ date|date:"M"|lower }}/">{{ date|date:"F" }}</a></li> +{% endfor %} +</ul> + +<p><a href="{% url pl-gallery-list 1 %}">View all galleries.</a></p> + +{% endblock %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mysite/photologue/templates/photologue/gallery_detail.html Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,19 @@ +{% extends "photologue/root.html" %} + +{% block title %}{{ object.title }}{% endblock %} + +{% block content %} + +<h1>{{ object.title }}</h1> +<h2>Originally published {{ object.date_added|date:"l, F jS, Y" }}</h2> +{% if object.description %}<p>{{ object.description }}</p>{% endif %} +<div class="photo-gallery"> + {% for photo in object.public %} + <div class="gallery-photo"> + <a href="{{ photo.get_absolute_url }}"><img src="{{ photo.get_thumbnail_url }}" alt="{{ photo.title }}"/></a> + </div> + {% endfor %} +</div> +<p><a href="{% url pl-gallery-list 1 %}">View all galleries</a></p> + +{% endblock %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mysite/photologue/templates/photologue/gallery_list.html Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,31 @@ +{% extends "photologue/root.html" %} + +{% block title %}All Galleries{% endblock %} + +{% block content %} + +<h1>All galleries</h1> + +{% if object_list %} + {% for gallery in object_list %} + <div class="photo-gallery"> + <h2><a href="{{ gallery.get_absolute_url }}">{{ gallery.title }}</a></h2> + {% for photo in gallery.sample|slice:sample_size %} + <div class="gallery-photo"> + <a href="{{ photo.get_absolute_url }}"><img src="{{ photo.get_thumbnail_url }}" alt="{{ photo.title }}"/></a> + </div> + {% endfor %} + </div> + {% endfor %} +{% else %} + <p>No galleries were found.</p> +{% endif %} + +{% if is_paginated %} +<p>{{ hits }} galleries total.</p> +<div id="page_controls"> + <p>{% if has_previous %}<a href="{% url pl-gallery-list previous %}">Previous</a> | {% endif %} page {{ page }} of {{ pages }} {% if has_next %}| <a href="{% url pl-gallery-list next %}">Next</a>{% endif %}</p> +</div> +{% endif %} + +{% endblock %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mysite/photologue/templates/photologue/photo_archive.html Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,20 @@ +{% extends "photologue/root.html" %} + +{% block title %}Latest Photos{% endblock %} + +{% block content %} + +<h1>Latest Photos</h1> + +{% if latest %} + {% for photo in latest %} + <div class="gallery-photo"> + <a href="{{ photo.get_absolute_url }}"><img src="{{ photo.get_thumbnail_url }}" alt="{{ photo.title }}"/></a> + </div> + {% endfor %} +{% else %} +<p>No photos were found.</p> +{% endif %} +<p><a href="{% url pl-photo-list 1 %}">View all photographs</a></p> + +{% endblock %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mysite/photologue/templates/photologue/photo_archive_day.html Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,20 @@ +{% extends "photologue/root.html" %} + +{% block title %}Photos for {{ day|date }}{% endblock %} + +{% block content %} + +<h1>Photos for {{ day|date }}</h1> + +{% if object_list %} + {% for photo in object_list %} + <div class="gallery-photo"> + <a href="{{ photo.get_absolute_url }}"><img src="{{ photo.get_thumbnail_url }}" alt="{{ photo.title }}"/></a> + </div> + {% endfor %} +{% else %} +<p>No photos were found.</p> +{% endif %} +<p><a href="{% url pl-photo-list 1 %}">View all photographs</a></p> + +{% endblock %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mysite/photologue/templates/photologue/photo_archive_month.html Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,20 @@ +{% extends "photologue/root.html" %} + +{% block title %}Photos for {{ month|date:"F Y" }}{% endblock %} + +{% block content %} + +<h1>Photos for {{ month|date:"F Y" }}</h1> + +{% if object_list %} + {% for photo in object_list %} + <div class="gallery-photo"> + <a href="{{ photo.get_absolute_url }}"><img src="{{ photo.get_thumbnail_url }}" alt="{{ photo.title }}"/></a> + </div> + {% endfor %} +{% else %} +<p>No photos were found.</p> +{% endif %} +<p><a href="{% url pl-photo-list 1 %}">View all photographs</a></p> + +{% endblock %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mysite/photologue/templates/photologue/photo_archive_year.html Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,14 @@ +{% extends "photologue/root.html" %} + +{% block title %}Galleries for {{ year }}{% endblock %} + +{% block content %} + +<h1>Photos for {{ year }}</h1> +<ul> +{% for date in date_list %} +<li><a href="{{ date|date:"M"|lower }}/">{{ date|date:"F" }}</a></li> +{% endfor %} +</ul> + +{% endblock %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mysite/photologue/templates/photologue/photo_detail.html Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,21 @@ +{% extends "photologue/root.html" %} + +{% block title %}{{ object.title }}{% endblock %} + +{% block content %} + +<h1>{{ object.title }}</h1> +<div class="gallery-photo"> + <a href="{{ object.image.url }}"><img src="{{ object.get_display_url }}" alt="{{ object.title }}"/></a> + {% if object.caption %}<p>{{ object.caption }}</p>{% endif %} +</div> +{% if object.public_galleries %} +<h2>This photo is found in the following galleries:</h2> +<ol> +{% for gallery in object.public_galleries %} + <li><a href="{{ gallery.get_absolute_url }}">{{ gallery.title }}</a></li> +{% endfor %} +</ol> +{% endif %} + +{% endblock %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mysite/photologue/templates/photologue/photo_list.html Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,26 @@ +{% extends "photologue/root.html" %} + +{% block title %}All Photos{% endblock %} + +{% block content %} + +<h1>All Photos</h1> + +{% if object_list %} + {% for photo in object_list %} + <div class="gallery-photo"> + <a href="{{ photo.get_absolute_url }}"><img src="{{ photo.get_thumbnail_url }}" alt="{{ photo.title }}"/></a> + </div> + {% endfor %} +{% else %} +<p>No photos were found.</p> +{% endif %} + +{% if is_paginated %} +<p>{{ hits }} photos total.</p> +<div id="page_controls"> + <p>{% if has_previous %}<a href="{% url pl-photo-list previous %}">Previous</a> | {% endif %} page {{ page }} of {{ pages }} {% if has_next %}| <a href="{% url pl-photo-list next %}">Next</a>{% endif %}</p> +</div> +{% endif %} + +{% endblock %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mysite/photologue/templates/photologue/root.html Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,1 @@ +{% extends "base.html" %} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mysite/photologue/tests.py Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,218 @@ +import os +import unittest +from django.conf import settings +from django.core.files.base import ContentFile +from django.test import TestCase + +from models import * + +# Path to sample image +RES_DIR = os.path.join(os.path.dirname(__file__), 'res') +LANDSCAPE_IMAGE_PATH = os.path.join(RES_DIR, 'test_landscape.jpg') +PORTRAIT_IMAGE_PATH = os.path.join(RES_DIR, 'test_portrait.jpg') +SQUARE_IMAGE_PATH = os.path.join(RES_DIR, 'test_square.jpg') + + +class TestPhoto(ImageModel): + """ Minimal ImageModel class for testing """ + name = models.CharField(max_length=30) + + +class PLTest(TestCase): + """ Base TestCase class """ + def setUp(self): + self.s = PhotoSize(name='test', width=100, height=100) + self.s.save() + self.pl = TestPhoto(name='landscape') + self.pl.image.save(os.path.basename(LANDSCAPE_IMAGE_PATH), + ContentFile(open(LANDSCAPE_IMAGE_PATH, 'rb').read())) + self.pl.save() + + def tearDown(self): + path = self.pl.image.path + self.pl.delete() + self.failIf(os.path.isfile(path)) + self.s.delete() + + +class PhotoTest(PLTest): + def test_new_photo(self): + self.assertEqual(TestPhoto.objects.count(), 1) + self.failUnless(os.path.isfile(self.pl.image.path)) + self.assertEqual(os.path.getsize(self.pl.image.path), + os.path.getsize(LANDSCAPE_IMAGE_PATH)) + + def test_exif(self): + self.assert_(len(self.pl.EXIF.keys()) > 0) + + def test_paths(self): + self.assertEqual(os.path.normpath(str(self.pl.cache_path())).lower(), + os.path.normpath(os.path.join(settings.MEDIA_ROOT, + PHOTOLOGUE_DIR, + 'photos', + 'cache')).lower()) + self.assertEqual(self.pl.cache_url(), + settings.MEDIA_URL + PHOTOLOGUE_DIR + '/photos/cache') + + def test_count(self): + for i in range(5): + self.pl.get_test_url() + self.assertEquals(self.pl.view_count, 0) + self.s.increment_count = True + self.s.save() + for i in range(5): + self.pl.get_test_url() + self.assertEquals(self.pl.view_count, 5) + + def test_precache(self): + # set the thumbnail photo size to pre-cache + self.s.pre_cache = True + self.s.save() + # make sure it created the file + self.failUnless(os.path.isfile(self.pl.get_test_filename())) + self.s.pre_cache = False + self.s.save() + # clear the cache and make sure the file's deleted + self.pl.clear_cache() + self.failIf(os.path.isfile(self.pl.get_test_filename())) + + def test_accessor_methods(self): + self.assertEquals(self.pl.get_test_photosize(), self.s) + self.assertEquals(self.pl.get_test_size(), + Image.open(self.pl.get_test_filename()).size) + self.assertEquals(self.pl.get_test_url(), + self.pl.cache_url() + '/' + \ + self.pl._get_filename_for_size(self.s)) + self.assertEquals(self.pl.get_test_filename(), + os.path.join(self.pl.cache_path(), + self.pl._get_filename_for_size(self.s))) + + +class ImageResizeTest(PLTest): + def setUp(self): + super(ImageResizeTest, self).setUp() + self.pp = TestPhoto(name='portrait') + self.pp.image.save(os.path.basename(PORTRAIT_IMAGE_PATH), + ContentFile(open(PORTRAIT_IMAGE_PATH, 'rb').read())) + self.pp.save() + self.ps = TestPhoto(name='square') + self.ps.image.save(os.path.basename(SQUARE_IMAGE_PATH), + ContentFile(open(SQUARE_IMAGE_PATH, 'rb').read())) + self.ps.save() + + def tearDown(self): + super(ImageResizeTest, self).tearDown() + self.pp.delete() + self.ps.delete() + + def test_resize_to_fit(self): + self.assertEquals(self.pl.get_test_size(), (100, 75)) + self.assertEquals(self.pp.get_test_size(), (75, 100)) + self.assertEquals(self.ps.get_test_size(), (100, 100)) + + def test_resize_to_fit_width(self): + self.s.size = (100, 0) + self.s.save() + self.assertEquals(self.pl.get_test_size(), (100, 75)) + self.assertEquals(self.pp.get_test_size(), (100, 133)) + self.assertEquals(self.ps.get_test_size(), (100, 100)) + + def test_resize_to_fit_width_enlarge(self): + self.s.size = (2000, 0) + self.s.upscale = True + self.s.save() + self.assertEquals(self.pl.get_test_size(), (2000, 1500)) + self.assertEquals(self.pp.get_test_size(), (2000, 2667)) + self.assertEquals(self.ps.get_test_size(), (2000, 2000)) + + def test_resize_to_fit_height(self): + self.s.size = (0, 100) + self.s.save() + self.assertEquals(self.pl.get_test_size(), (133, 100)) + self.assertEquals(self.pp.get_test_size(), (75, 100)) + self.assertEquals(self.ps.get_test_size(), (100, 100)) + + def test_resize_to_fit_height_enlarge(self): + self.s.size = (0, 2000) + self.s.upscale = True + self.s.save() + self.assertEquals(self.pl.get_test_size(), (2667, 2000)) + self.assertEquals(self.pp.get_test_size(), (1500, 2000)) + self.assertEquals(self.ps.get_test_size(), (2000, 2000)) + + def test_resize_and_crop(self): + self.s.crop = True + self.s.save() + self.assertEquals(self.pl.get_test_size(), self.s.size) + self.assertEquals(self.pp.get_test_size(), self.s.size) + self.assertEquals(self.ps.get_test_size(), self.s.size) + + def test_resize_rounding_to_fit(self): + self.s.size = (113, 113) + self.s.save() + self.assertEquals(self.pl.get_test_size(), (113, 85)) + self.assertEquals(self.pp.get_test_size(), (85, 113)) + self.assertEquals(self.ps.get_test_size(), (113, 113)) + + def test_resize_rounding_cropped(self): + self.s.size = (113, 113) + self.s.crop = True + self.s.save() + self.assertEquals(self.pl.get_test_size(), self.s.size) + self.assertEquals(self.pp.get_test_size(), self.s.size) + self.assertEquals(self.ps.get_test_size(), self.s.size) + + def test_resize_one_dimension_width(self): + self.s.size = (1500, 1200) + self.s.save() + self.assertEquals(self.pl.get_test_size(), (1500, 1125)) + + def test_resize_one_dimension_height(self): + self.s.size = (1600, 1100) + self.s.save() + self.assertEquals(self.pl.get_test_size(), (1467, 1100)) + + def test_resize_no_upscale(self): + self.s.size = (2000, 2000) + self.s.save() + self.assertEquals(self.pl.get_test_size(), (1600, 1200)) + + def test_resize_no_upscale_mixed_height(self): + self.s.size = (3200, 600) + self.s.save() + self.assertEquals(self.pl.get_test_size(), (800, 600)) + + def test_resize_no_upscale_mixed_width(self): + self.s.size = (800, 2400) + self.s.save() + self.assertEquals(self.pl.get_test_size(), (800, 600)) + + def test_resize_no_upscale_crop(self): + self.s.size = (2000, 2000) + self.s.crop = True + self.s.save() + self.assertEquals(self.pl.get_test_size(), (2000, 2000)) + + def test_resize_upscale(self): + self.s.size = (2000, 2000) + self.s.upscale = True + self.s.save() + self.assertEquals(self.pl.get_test_size(), (2000, 1500)) + self.assertEquals(self.pp.get_test_size(), (1500, 2000)) + self.assertEquals(self.ps.get_test_size(), (2000, 2000)) + + +class PhotoEffectTest(PLTest): + def test(self): + effect = PhotoEffect(name='test') + im = Image.open(self.pl.image.path) + self.assert_(isinstance(effect.pre_process(im), Image.Image)) + self.assert_(isinstance(effect.post_process(im), Image.Image)) + self.assert_(isinstance(effect.process(im), Image.Image)) + + +class PhotoSizeCacheTest(PLTest): + def test(self): + cache = PhotoSizeCache() + self.assertEqual(cache.sizes['test'], self.s) +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mysite/photologue/urls.py Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,36 @@ +from django.conf import settings +from django.conf.urls.defaults import * +from models import * + +# Number of random images from the gallery to display. +SAMPLE_SIZE = ":%s" % getattr(settings, 'GALLERY_SAMPLE_SIZE', 5) + +# galleries +gallery_args = {'date_field': 'date_added', 'allow_empty': True, 'queryset': Gallery.objects.filter(is_public=True), 'extra_context':{'sample_size':SAMPLE_SIZE}} +urlpatterns = patterns('django.views.generic.date_based', + url(r'^gallery/(?P<year>\d{4})/(?P<month>[a-z]{3})/(?P<day>\w{1,2})/(?P<slug>[\-\d\w]+)/$', 'object_detail', {'date_field': 'date_added', 'slug_field': 'title_slug', 'queryset': Gallery.objects.filter(is_public=True), 'extra_context':{'sample_size':SAMPLE_SIZE}}, name='pl-gallery-detail'), + url(r'^gallery/(?P<year>\d{4})/(?P<month>[a-z]{3})/(?P<day>\w{1,2})/$', 'archive_day', gallery_args, name='pl-gallery-archive-day'), + url(r'^gallery/(?P<year>\d{4})/(?P<month>[a-z]{3})/$', 'archive_month', gallery_args, name='pl-gallery-archive-month'), + url(r'^gallery/(?P<year>\d{4})/$', 'archive_year', gallery_args, name='pl-gallery-archive-year'), + url(r'^gallery/?$', 'archive_index', gallery_args, name='pl-gallery-archive'), +) +urlpatterns += patterns('django.views.generic.list_detail', + url(r'^gallery/(?P<slug>[\-\d\w]+)/$', 'object_detail', {'slug_field': 'title_slug', 'queryset': Gallery.objects.filter(is_public=True), 'extra_context':{'sample_size':SAMPLE_SIZE}}, name='pl-gallery'), + url(r'^gallery/page/(?P<page>[0-9]+)/$', 'object_list', {'queryset': Gallery.objects.filter(is_public=True), 'allow_empty': True, 'paginate_by': 5, 'extra_context':{'sample_size':SAMPLE_SIZE}}, name='pl-gallery-list'), +) + +# photographs +photo_args = {'date_field': 'date_added', 'allow_empty': True, 'queryset': Photo.objects.filter(is_public=True)} +urlpatterns += patterns('django.views.generic.date_based', + url(r'^photo/(?P<year>\d{4})/(?P<month>[a-z]{3})/(?P<day>\w{1,2})/(?P<slug>[\-\d\w]+)/$', 'object_detail', {'date_field': 'date_added', 'slug_field': 'title_slug', 'queryset': Photo.objects.filter(is_public=True)}, name='pl-photo-detail'), + url(r'^photo/(?P<year>\d{4})/(?P<month>[a-z]{3})/(?P<day>\w{1,2})/$', 'archive_day', photo_args, name='pl-photo-archive-day'), + url(r'^photo/(?P<year>\d{4})/(?P<month>[a-z]{3})/$', 'archive_month', photo_args, name='pl-photo-archive-month'), + url(r'^photo/(?P<year>\d{4})/$', 'archive_year', photo_args, name='pl-photo-archive-year'), + url(r'^photo/$', 'archive_index', photo_args, name='pl-photo-archive'), +) +urlpatterns += patterns('django.views.generic.list_detail', + url(r'^photo/(?P<slug>[\-\d\w]+)/$', 'object_detail', {'slug_field': 'title_slug', 'queryset': Photo.objects.filter(is_public=True)}, name='pl-photo'), + url(r'^photo/page/(?P<page>[0-9]+)/$', 'object_list', {'queryset': Photo.objects.filter(is_public=True), 'allow_empty': True, 'paginate_by': 20}, name='pl-photo-list'), +) + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mysite/photologue/utils/EXIF.py Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,1568 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Library to extract EXIF information from digital camera image files +# http://sourceforge.net/projects/exif-py/ +# +# VERSION 1.0.7 +# +# To use this library call with: +# f = open(path_name, 'rb') +# tags = EXIF.process_file(f) +# +# To ignore makerNote tags, pass the -q or --quick +# command line arguments, or as +# f = open(path_name, 'rb') +# tags = EXIF.process_file(f, details=False) +# +# To stop processing after a certain tag is retrieved, +# pass the -t TAG or --stop-tag TAG argument, or as +# f = open(path_name, 'rb') +# tags = EXIF.process_file(f, stop_tag='TAG') +# +# where TAG is a valid tag name, ex 'DateTimeOriginal' +# +# These are useful when you are retrieving a large list of images +# +# Returned tags will be a dictionary mapping names of EXIF tags to their +# values in the file named by path_name. You can process the tags +# as you wish. In particular, you can iterate through all the tags with: +# for tag in tags.keys(): +# if tag not in ('JPEGThumbnail', 'TIFFThumbnail', 'Filename', +# 'EXIF MakerNote'): +# print "Key: %s, value %s" % (tag, tags[tag]) +# (This code uses the if statement to avoid printing out a few of the +# tags that tend to be long or boring.) +# +# The tags dictionary will include keys for all of the usual EXIF +# tags, and will also include keys for Makernotes used by some +# cameras, for which we have a good specification. +# +# Note that the dictionary keys are the IFD name followed by the +# tag name. For example: +# 'EXIF DateTimeOriginal', 'Image Orientation', 'MakerNote FocusMode' +# +# Copyright (c) 2002-2007 Gene Cash All rights reserved +# Copyright (c) 2007 Ianaré Sévi All rights reserved +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided +# with the distribution. +# +# 3. Neither the name of the authors nor the names of its contributors +# may be used to endorse or promote products derived from this +# software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# +# ----- See 'changes.txt' file for all contributors and changes ----- # +# + + +# Don't throw an exception when given an out of range character. +def make_string(seq): + str = "" + for c in seq: + # Screen out non-printing characters + if 32 <= c and c < 256: + str += chr(c) + # If no printing chars + if not str: + return seq + return str + +# Special version to deal with the code in the first 8 bytes of a user comment. +def make_string_uc(seq): + code = seq[0:8] + seq = seq[8:] + # Of course, this is only correct if ASCII, and the standard explicitly + # allows JIS and Unicode. + return make_string(seq) + +# field type descriptions as (length, abbreviation, full name) tuples +FIELD_TYPES = ( + (0, 'X', 'Proprietary'), # no such type + (1, 'B', 'Byte'), + (1, 'A', 'ASCII'), + (2, 'S', 'Short'), + (4, 'L', 'Long'), + (8, 'R', 'Ratio'), + (1, 'SB', 'Signed Byte'), + (1, 'U', 'Undefined'), + (2, 'SS', 'Signed Short'), + (4, 'SL', 'Signed Long'), + (8, 'SR', 'Signed Ratio'), + ) + +# dictionary of main EXIF tag names +# first element of tuple is tag name, optional second element is +# another dictionary giving names to values +EXIF_TAGS = { + 0x0100: ('ImageWidth', ), + 0x0101: ('ImageLength', ), + 0x0102: ('BitsPerSample', ), + 0x0103: ('Compression', + {1: 'Uncompressed TIFF', + 6: 'JPEG Compressed'}), + 0x0106: ('PhotometricInterpretation', ), + 0x010A: ('FillOrder', ), + 0x010D: ('DocumentName', ), + 0x010E: ('ImageDescription', ), + 0x010F: ('Make', ), + 0x0110: ('Model', ), + 0x0111: ('StripOffsets', ), + 0x0112: ('Orientation', + {1: 'Horizontal (normal)', + 2: 'Mirrored horizontal', + 3: 'Rotated 180', + 4: 'Mirrored vertical', + 5: 'Mirrored horizontal then rotated 90 CCW', + 6: 'Rotated 90 CW', + 7: 'Mirrored horizontal then rotated 90 CW', + 8: 'Rotated 90 CCW'}), + 0x0115: ('SamplesPerPixel', ), + 0x0116: ('RowsPerStrip', ), + 0x0117: ('StripByteCounts', ), + 0x011A: ('XResolution', ), + 0x011B: ('YResolution', ), + 0x011C: ('PlanarConfiguration', ), + 0x0128: ('ResolutionUnit', + {1: 'Not Absolute', + 2: 'Pixels/Inch', + 3: 'Pixels/Centimeter'}), + 0x012D: ('TransferFunction', ), + 0x0131: ('Software', ), + 0x0132: ('DateTime', ), + 0x013B: ('Artist', ), + 0x013E: ('WhitePoint', ), + 0x013F: ('PrimaryChromaticities', ), + 0x0156: ('TransferRange', ), + 0x0200: ('JPEGProc', ), + 0x0201: ('JPEGInterchangeFormat', ), + 0x0202: ('JPEGInterchangeFormatLength', ), + 0x0211: ('YCbCrCoefficients', ), + 0x0212: ('YCbCrSubSampling', ), + 0x0213: ('YCbCrPositioning', ), + 0x0214: ('ReferenceBlackWhite', ), + 0x828D: ('CFARepeatPatternDim', ), + 0x828E: ('CFAPattern', ), + 0x828F: ('BatteryLevel', ), + 0x8298: ('Copyright', ), + 0x829A: ('ExposureTime', ), + 0x829D: ('FNumber', ), + 0x83BB: ('IPTC/NAA', ), + 0x8769: ('ExifOffset', ), + 0x8773: ('InterColorProfile', ), + 0x8822: ('ExposureProgram', + {0: 'Unidentified', + 1: 'Manual', + 2: 'Program Normal', + 3: 'Aperture Priority', + 4: 'Shutter Priority', + 5: 'Program Creative', + 6: 'Program Action', + 7: 'Portrait Mode', + 8: 'Landscape Mode'}), + 0x8824: ('SpectralSensitivity', ), + 0x8825: ('GPSInfo', ), + 0x8827: ('ISOSpeedRatings', ), + 0x8828: ('OECF', ), + # print as string + 0x9000: ('ExifVersion', make_string), + 0x9003: ('DateTimeOriginal', ), + 0x9004: ('DateTimeDigitized', ), + 0x9101: ('ComponentsConfiguration', + {0: '', + 1: 'Y', + 2: 'Cb', + 3: 'Cr', + 4: 'Red', + 5: 'Green', + 6: 'Blue'}), + 0x9102: ('CompressedBitsPerPixel', ), + 0x9201: ('ShutterSpeedValue', ), + 0x9202: ('ApertureValue', ), + 0x9203: ('BrightnessValue', ), + 0x9204: ('ExposureBiasValue', ), + 0x9205: ('MaxApertureValue', ), + 0x9206: ('SubjectDistance', ), + 0x9207: ('MeteringMode', + {0: 'Unidentified', + 1: 'Average', + 2: 'CenterWeightedAverage', + 3: 'Spot', + 4: 'MultiSpot'}), + 0x9208: ('LightSource', + {0: 'Unknown', + 1: 'Daylight', + 2: 'Fluorescent', + 3: 'Tungsten', + 10: 'Flash', + 17: 'Standard Light A', + 18: 'Standard Light B', + 19: 'Standard Light C', + 20: 'D55', + 21: 'D65', + 22: 'D75', + 255: 'Other'}), + 0x9209: ('Flash', {0: 'No', + 1: 'Fired', + 5: 'Fired (?)', # no return sensed + 7: 'Fired (!)', # return sensed + 9: 'Fill Fired', + 13: 'Fill Fired (?)', + 15: 'Fill Fired (!)', + 16: 'Off', + 24: 'Auto Off', + 25: 'Auto Fired', + 29: 'Auto Fired (?)', + 31: 'Auto Fired (!)', + 32: 'Not Available'}), + 0x920A: ('FocalLength', ), + 0x9214: ('SubjectArea', ), + 0x927C: ('MakerNote', ), + # print as string + 0x9286: ('UserComment', make_string_uc), # First 8 bytes gives coding system e.g. ASCII vs. JIS vs Unicode + 0x9290: ('SubSecTime', ), + 0x9291: ('SubSecTimeOriginal', ), + 0x9292: ('SubSecTimeDigitized', ), + # print as string + 0xA000: ('FlashPixVersion', make_string), + 0xA001: ('ColorSpace', ), + 0xA002: ('ExifImageWidth', ), + 0xA003: ('ExifImageLength', ), + 0xA005: ('InteroperabilityOffset', ), + 0xA20B: ('FlashEnergy', ), # 0x920B in TIFF/EP + 0xA20C: ('SpatialFrequencyResponse', ), # 0x920C - - + 0xA20E: ('FocalPlaneXResolution', ), # 0x920E - - + 0xA20F: ('FocalPlaneYResolution', ), # 0x920F - - + 0xA210: ('FocalPlaneResolutionUnit', ), # 0x9210 - - + 0xA214: ('SubjectLocation', ), # 0x9214 - - + 0xA215: ('ExposureIndex', ), # 0x9215 - - + 0xA217: ('SensingMethod', ), # 0x9217 - - + 0xA300: ('FileSource', + {3: 'Digital Camera'}), + 0xA301: ('SceneType', + {1: 'Directly Photographed'}), + 0xA302: ('CVAPattern', ), + 0xA401: ('CustomRendered', ), + 0xA402: ('ExposureMode', + {0: 'Auto Exposure', + 1: 'Manual Exposure', + 2: 'Auto Bracket'}), + 0xA403: ('WhiteBalance', + {0: 'Auto', + 1: 'Manual'}), + 0xA404: ('DigitalZoomRatio', ), + 0xA405: ('FocalLengthIn35mm', ), + 0xA406: ('SceneCaptureType', ), + 0xA407: ('GainControl', ), + 0xA408: ('Contrast', ), + 0xA409: ('Saturation', ), + 0xA40A: ('Sharpness', ), + 0xA40C: ('SubjectDistanceRange', ), + } + +# interoperability tags +INTR_TAGS = { + 0x0001: ('InteroperabilityIndex', ), + 0x0002: ('InteroperabilityVersion', ), + 0x1000: ('RelatedImageFileFormat', ), + 0x1001: ('RelatedImageWidth', ), + 0x1002: ('RelatedImageLength', ), + } + +# GPS tags (not used yet, haven't seen camera with GPS) +GPS_TAGS = { + 0x0000: ('GPSVersionID', ), + 0x0001: ('GPSLatitudeRef', ), + 0x0002: ('GPSLatitude', ), + 0x0003: ('GPSLongitudeRef', ), + 0x0004: ('GPSLongitude', ), + 0x0005: ('GPSAltitudeRef', ), + 0x0006: ('GPSAltitude', ), + 0x0007: ('GPSTimeStamp', ), + 0x0008: ('GPSSatellites', ), + 0x0009: ('GPSStatus', ), + 0x000A: ('GPSMeasureMode', ), + 0x000B: ('GPSDOP', ), + 0x000C: ('GPSSpeedRef', ), + 0x000D: ('GPSSpeed', ), + 0x000E: ('GPSTrackRef', ), + 0x000F: ('GPSTrack', ), + 0x0010: ('GPSImgDirectionRef', ), + 0x0011: ('GPSImgDirection', ), + 0x0012: ('GPSMapDatum', ), + 0x0013: ('GPSDestLatitudeRef', ), + 0x0014: ('GPSDestLatitude', ), + 0x0015: ('GPSDestLongitudeRef', ), + 0x0016: ('GPSDestLongitude', ), + 0x0017: ('GPSDestBearingRef', ), + 0x0018: ('GPSDestBearing', ), + 0x0019: ('GPSDestDistanceRef', ), + 0x001A: ('GPSDestDistance', ), + } + +# Ignore these tags when quick processing +# 0x927C is MakerNote Tags +# 0x9286 is user comment +IGNORE_TAGS=(0x9286, 0x927C) + +# http://tomtia.plala.jp/DigitalCamera/MakerNote/index.asp +def nikon_ev_bias(seq): + # First digit seems to be in steps of 1/6 EV. + # Does the third value mean the step size? It is usually 6, + # but it is 12 for the ExposureDifference. + # + if seq == [252, 1, 6, 0]: + return "-2/3 EV" + if seq == [253, 1, 6, 0]: + return "-1/2 EV" + if seq == [254, 1, 6, 0]: + return "-1/3 EV" + if seq == [0, 1, 6, 0]: + return "0 EV" + if seq == [2, 1, 6, 0]: + return "+1/3 EV" + if seq == [3, 1, 6, 0]: + return "+1/2 EV" + if seq == [4, 1, 6, 0]: + return "+2/3 EV" + # Handle combinations not in the table. + a = seq[0] + # Causes headaches for the +/- logic, so special case it. + if a == 0: + return "0 EV" + if a > 127: + a = 256 - a + ret_str = "-" + else: + ret_str = "+" + b = seq[2] # Assume third value means the step size + whole = a / b + a = a % b + if whole != 0: + ret_str = ret_str + str(whole) + " " + if a == 0: + ret_str = ret_str + "EV" + else: + r = Ratio(a, b) + ret_str = ret_str + r.__repr__() + " EV" + return ret_str + +# Nikon E99x MakerNote Tags +MAKERNOTE_NIKON_NEWER_TAGS={ + 0x0001: ('MakernoteVersion', make_string), # Sometimes binary + 0x0002: ('ISOSetting', ), + 0x0003: ('ColorMode', ), + 0x0004: ('Quality', ), + 0x0005: ('Whitebalance', ), + 0x0006: ('ImageSharpening', ), + 0x0007: ('FocusMode', ), + 0x0008: ('FlashSetting', ), + 0x0009: ('AutoFlashMode', ), + 0x000B: ('WhiteBalanceBias', ), + 0x000C: ('WhiteBalanceRBCoeff', ), + 0x000D: ('ProgramShift', nikon_ev_bias), + # Nearly the same as the other EV vals, but step size is 1/12 EV (?) + 0x000E: ('ExposureDifference', nikon_ev_bias), + 0x000F: ('ISOSelection', ), + 0x0011: ('NikonPreview', ), + 0x0012: ('FlashCompensation', nikon_ev_bias), + 0x0013: ('ISOSpeedRequested', ), + 0x0016: ('PhotoCornerCoordinates', ), + # 0x0017: Unknown, but most likely an EV value + 0x0018: ('FlashBracketCompensationApplied', nikon_ev_bias), + 0x0019: ('AEBracketCompensationApplied', ), + 0x001A: ('ImageProcessing', ), + 0x0080: ('ImageAdjustment', ), + 0x0081: ('ToneCompensation', ), + 0x0082: ('AuxiliaryLens', ), + 0x0083: ('LensType', ), + 0x0084: ('LensMinMaxFocalMaxAperture', ), + 0x0085: ('ManualFocusDistance', ), + 0x0086: ('DigitalZoomFactor', ), + 0x0087: ('FlashMode', + {0x00: 'Did Not Fire', + 0x01: 'Fired, Manual', + 0x07: 'Fired, External', + 0x08: 'Fired, Commander Mode ', + 0x09: 'Fired, TTL Mode'}), + 0x0088: ('AFFocusPosition', + {0x0000: 'Center', + 0x0100: 'Top', + 0x0200: 'Bottom', + 0x0300: 'Left', + 0x0400: 'Right'}), + 0x0089: ('BracketingMode', + {0x00: 'Single frame, no bracketing', + 0x01: 'Continuous, no bracketing', + 0x02: 'Timer, no bracketing', + 0x10: 'Single frame, exposure bracketing', + 0x11: 'Continuous, exposure bracketing', + 0x12: 'Timer, exposure bracketing', + 0x40: 'Single frame, white balance bracketing', + 0x41: 'Continuous, white balance bracketing', + 0x42: 'Timer, white balance bracketing'}), + 0x008A: ('AutoBracketRelease', ), + 0x008B: ('LensFStops', ), + 0x008C: ('NEFCurve2', ), + 0x008D: ('ColorMode', ), + 0x008F: ('SceneMode', ), + 0x0090: ('LightingType', ), + 0x0091: ('ShotInfo', ), # First 4 bytes are probably a version number in ASCII + 0x0092: ('HueAdjustment', ), + # 0x0093: ('SaturationAdjustment', ), + 0x0094: ('Saturation', # Name conflict with 0x00AA !! + {-3: 'B&W', + -2: '-2', + -1: '-1', + 0: '0', + 1: '1', + 2: '2'}), + 0x0095: ('NoiseReduction', ), + 0x0096: ('NEFCurve2', ), + 0x0097: ('ColorBalance', ), + 0x0098: ('LensData', ), # First 4 bytes are a version number in ASCII + 0x0099: ('RawImageCenter', ), + 0x009A: ('SensorPixelSize', ), + 0x009C: ('Scene Assist', ), + 0x00A0: ('SerialNumber', ), + 0x00A2: ('ImageDataSize', ), + # A4: In NEF, looks like a 4 byte ASCII version number + 0x00A5: ('ImageCount', ), + 0x00A6: ('DeletedImageCount', ), + 0x00A7: ('TotalShutterReleases', ), + # A8: ExposureMode? JPG: First 4 bytes are probably a version number in ASCII + # But in a sample NEF, its 8 zeros, then the string "NORMAL" + 0x00A9: ('ImageOptimization', ), + 0x00AA: ('Saturation', ), + 0x00AB: ('DigitalVariProgram', ), + 0x00AC: ('ImageStabilization', ), + 0x00AD: ('Responsive AF', ), # 'AFResponse' + 0x0010: ('DataDump', ), + } + +MAKERNOTE_NIKON_OLDER_TAGS = { + 0x0003: ('Quality', + {1: 'VGA Basic', + 2: 'VGA Normal', + 3: 'VGA Fine', + 4: 'SXGA Basic', + 5: 'SXGA Normal', + 6: 'SXGA Fine'}), + 0x0004: ('ColorMode', + {1: 'Color', + 2: 'Monochrome'}), + 0x0005: ('ImageAdjustment', + {0: 'Normal', + 1: 'Bright+', + 2: 'Bright-', + 3: 'Contrast+', + 4: 'Contrast-'}), + 0x0006: ('CCDSpeed', + {0: 'ISO 80', + 2: 'ISO 160', + 4: 'ISO 320', + 5: 'ISO 100'}), + 0x0007: ('WhiteBalance', + {0: 'Auto', + 1: 'Preset', + 2: 'Daylight', + 3: 'Incandescent', + 4: 'Fluorescent', + 5: 'Cloudy', + 6: 'Speed Light'}), + } + +# decode Olympus SpecialMode tag in MakerNote +def olympus_special_mode(v): + a={ + 0: 'Normal', + 1: 'Unknown', + 2: 'Fast', + 3: 'Panorama'} + b={ + 0: 'Non-panoramic', + 1: 'Left to right', + 2: 'Right to left', + 3: 'Bottom to top', + 4: 'Top to bottom'} + if v[0] not in a or v[2] not in b: + return v + return '%s - sequence %d - %s' % (a[v[0]], v[1], b[v[2]]) + +MAKERNOTE_OLYMPUS_TAGS={ + # ah HAH! those sneeeeeaky bastids! this is how they get past the fact + # that a JPEG thumbnail is not allowed in an uncompressed TIFF file + 0x0100: ('JPEGThumbnail', ), + 0x0200: ('SpecialMode', olympus_special_mode), + 0x0201: ('JPEGQual', + {1: 'SQ', + 2: 'HQ', + 3: 'SHQ'}), + 0x0202: ('Macro', + {0: 'Normal', + 1: 'Macro', + 2: 'SuperMacro'}), + 0x0203: ('BWMode', + {0: 'Off', + 1: 'On'}), + 0x0204: ('DigitalZoom', ), + 0x0205: ('FocalPlaneDiagonal', ), + 0x0206: ('LensDistortionParams', ), + 0x0207: ('SoftwareRelease', ), + 0x0208: ('PictureInfo', ), + 0x0209: ('CameraID', make_string), # print as string + 0x0F00: ('DataDump', ), + 0x0300: ('PreCaptureFrames', ), + 0x0404: ('SerialNumber', ), + 0x1000: ('ShutterSpeedValue', ), + 0x1001: ('ISOValue', ), + 0x1002: ('ApertureValue', ), + 0x1003: ('BrightnessValue', ), + 0x1004: ('FlashMode', ), + 0x1004: ('FlashMode', + {2: 'On', + 3: 'Off'}), + 0x1005: ('FlashDevice', + {0: 'None', + 1: 'Internal', + 4: 'External', + 5: 'Internal + External'}), + 0x1006: ('ExposureCompensation', ), + 0x1007: ('SensorTemperature', ), + 0x1008: ('LensTemperature', ), + 0x100b: ('FocusMode', + {0: 'Auto', + 1: 'Manual'}), + 0x1017: ('RedBalance', ), + 0x1018: ('BlueBalance', ), + 0x101a: ('SerialNumber', ), + 0x1023: ('FlashExposureComp', ), + 0x1026: ('ExternalFlashBounce', + {0: 'No', + 1: 'Yes'}), + 0x1027: ('ExternalFlashZoom', ), + 0x1028: ('ExternalFlashMode', ), + 0x1029: ('Contrast int16u', + {0: 'High', + 1: 'Normal', + 2: 'Low'}), + 0x102a: ('SharpnessFactor', ), + 0x102b: ('ColorControl', ), + 0x102c: ('ValidBits', ), + 0x102d: ('CoringFilter', ), + 0x102e: ('OlympusImageWidth', ), + 0x102f: ('OlympusImageHeight', ), + 0x1034: ('CompressionRatio', ), + 0x1035: ('PreviewImageValid', + {0: 'No', + 1: 'Yes'}), + 0x1036: ('PreviewImageStart', ), + 0x1037: ('PreviewImageLength', ), + 0x1039: ('CCDScanMode', + {0: 'Interlaced', + 1: 'Progressive'}), + 0x103a: ('NoiseReduction', + {0: 'Off', + 1: 'On'}), + 0x103b: ('InfinityLensStep', ), + 0x103c: ('NearLensStep', ), + + # TODO - these need extra definitions + # http://search.cpan.org/src/EXIFTOOL/Image-ExifTool-6.90/html/TagNames/Olympus.html + 0x2010: ('Equipment', ), + 0x2020: ('CameraSettings', ), + 0x2030: ('RawDevelopment', ), + 0x2040: ('ImageProcessing', ), + 0x2050: ('FocusInfo', ), + 0x3000: ('RawInfo ', ), + } + +# 0x2020 CameraSettings +MAKERNOTE_OLYMPUS_TAG_0x2020={ + 0x0100: ('PreviewImageValid', + {0: 'No', + 1: 'Yes'}), + 0x0101: ('PreviewImageStart', ), + 0x0102: ('PreviewImageLength', ), + 0x0200: ('ExposureMode', { + 1: 'Manual', + 2: 'Program', + 3: 'Aperture-priority AE', + 4: 'Shutter speed priority AE', + 5: 'Program-shift'}), + 0x0201: ('AELock', + {0: 'Off', + 1: 'On'}), + 0x0202: ('MeteringMode', + {2: 'Center Weighted', + 3: 'Spot', + 5: 'ESP', + 261: 'Pattern+AF', + 515: 'Spot+Highlight control', + 1027: 'Spot+Shadow control'}), + 0x0300: ('MacroMode', + {0: 'Off', + 1: 'On'}), + 0x0301: ('FocusMode', + {0: 'Single AF', + 1: 'Sequential shooting AF', + 2: 'Continuous AF', + 3: 'Multi AF', + 10: 'MF'}), + 0x0302: ('FocusProcess', + {0: 'AF Not Used', + 1: 'AF Used'}), + 0x0303: ('AFSearch', + {0: 'Not Ready', + 1: 'Ready'}), + 0x0304: ('AFAreas', ), + 0x0401: ('FlashExposureCompensation', ), + 0x0500: ('WhiteBalance2', + {0: 'Auto', + 16: '7500K (Fine Weather with Shade)', + 17: '6000K (Cloudy)', + 18: '5300K (Fine Weather)', + 20: '3000K (Tungsten light)', + 21: '3600K (Tungsten light-like)', + 33: '6600K (Daylight fluorescent)', + 34: '4500K (Neutral white fluorescent)', + 35: '4000K (Cool white fluorescent)', + 48: '3600K (Tungsten light-like)', + 256: 'Custom WB 1', + 257: 'Custom WB 2', + 258: 'Custom WB 3', + 259: 'Custom WB 4', + 512: 'Custom WB 5400K', + 513: 'Custom WB 2900K', + 514: 'Custom WB 8000K', }), + 0x0501: ('WhiteBalanceTemperature', ), + 0x0502: ('WhiteBalanceBracket', ), + 0x0503: ('CustomSaturation', ), # (3 numbers: 1. CS Value, 2. Min, 3. Max) + 0x0504: ('ModifiedSaturation', + {0: 'Off', + 1: 'CM1 (Red Enhance)', + 2: 'CM2 (Green Enhance)', + 3: 'CM3 (Blue Enhance)', + 4: 'CM4 (Skin Tones)'}), + 0x0505: ('ContrastSetting', ), # (3 numbers: 1. Contrast, 2. Min, 3. Max) + 0x0506: ('SharpnessSetting', ), # (3 numbers: 1. Sharpness, 2. Min, 3. Max) + 0x0507: ('ColorSpace', + {0: 'sRGB', + 1: 'Adobe RGB', + 2: 'Pro Photo RGB'}), + 0x0509: ('SceneMode', + {0: 'Standard', + 6: 'Auto', + 7: 'Sport', + 8: 'Portrait', + 9: 'Landscape+Portrait', + 10: 'Landscape', + 11: 'Night scene', + 13: 'Panorama', + 16: 'Landscape+Portrait', + 17: 'Night+Portrait', + 19: 'Fireworks', + 20: 'Sunset', + 22: 'Macro', + 25: 'Documents', + 26: 'Museum', + 28: 'Beach&Snow', + 30: 'Candle', + 35: 'Underwater Wide1', + 36: 'Underwater Macro', + 39: 'High Key', + 40: 'Digital Image Stabilization', + 44: 'Underwater Wide2', + 45: 'Low Key', + 46: 'Children', + 48: 'Nature Macro'}), + 0x050a: ('NoiseReduction', + {0: 'Off', + 1: 'Noise Reduction', + 2: 'Noise Filter', + 3: 'Noise Reduction + Noise Filter', + 4: 'Noise Filter (ISO Boost)', + 5: 'Noise Reduction + Noise Filter (ISO Boost)'}), + 0x050b: ('DistortionCorrection', + {0: 'Off', + 1: 'On'}), + 0x050c: ('ShadingCompensation', + {0: 'Off', + 1: 'On'}), + 0x050d: ('CompressionFactor', ), + 0x050f: ('Gradation', + {'-1 -1 1': 'Low Key', + '0 -1 1': 'Normal', + '1 -1 1': 'High Key'}), + 0x0520: ('PictureMode', + {1: 'Vivid', + 2: 'Natural', + 3: 'Muted', + 256: 'Monotone', + 512: 'Sepia'}), + 0x0521: ('PictureModeSaturation', ), + 0x0522: ('PictureModeHue?', ), + 0x0523: ('PictureModeContrast', ), + 0x0524: ('PictureModeSharpness', ), + 0x0525: ('PictureModeBWFilter', + {0: 'n/a', + 1: 'Neutral', + 2: 'Yellow', + 3: 'Orange', + 4: 'Red', + 5: 'Green'}), + 0x0526: ('PictureModeTone', + {0: 'n/a', + 1: 'Neutral', + 2: 'Sepia', + 3: 'Blue', + 4: 'Purple', + 5: 'Green'}), + 0x0600: ('Sequence', ), # 2 or 3 numbers: 1. Mode, 2. Shot number, 3. Mode bits + 0x0601: ('PanoramaMode', ), # (2 numbers: 1. Mode, 2. Shot number) + 0x0603: ('ImageQuality2', + {1: 'SQ', + 2: 'HQ', + 3: 'SHQ', + 4: 'RAW'}), + 0x0901: ('ManometerReading', ), + } + + +MAKERNOTE_CASIO_TAGS={ + 0x0001: ('RecordingMode', + {1: 'Single Shutter', + 2: 'Panorama', + 3: 'Night Scene', + 4: 'Portrait', + 5: 'Landscape'}), + 0x0002: ('Quality', + {1: 'Economy', + 2: 'Normal', + 3: 'Fine'}), + 0x0003: ('FocusingMode', + {2: 'Macro', + 3: 'Auto Focus', + 4: 'Manual Focus', + 5: 'Infinity'}), + 0x0004: ('FlashMode', + {1: 'Auto', + 2: 'On', + 3: 'Off', + 4: 'Red Eye Reduction'}), + 0x0005: ('FlashIntensity', + {11: 'Weak', + 13: 'Normal', + 15: 'Strong'}), + 0x0006: ('Object Distance', ), + 0x0007: ('WhiteBalance', + {1: 'Auto', + 2: 'Tungsten', + 3: 'Daylight', + 4: 'Fluorescent', + 5: 'Shade', + 129: 'Manual'}), + 0x000B: ('Sharpness', + {0: 'Normal', + 1: 'Soft', + 2: 'Hard'}), + 0x000C: ('Contrast', + {0: 'Normal', + 1: 'Low', + 2: 'High'}), + 0x000D: ('Saturation', + {0: 'Normal', + 1: 'Low', + 2: 'High'}), + 0x0014: ('CCDSpeed', + {64: 'Normal', + 80: 'Normal', + 100: 'High', + 125: '+1.0', + 244: '+3.0', + 250: '+2.0'}), + } + +MAKERNOTE_FUJIFILM_TAGS={ + 0x0000: ('NoteVersion', make_string), + 0x1000: ('Quality', ), + 0x1001: ('Sharpness', + {1: 'Soft', + 2: 'Soft', + 3: 'Normal', + 4: 'Hard', + 5: 'Hard'}), + 0x1002: ('WhiteBalance', + {0: 'Auto', + 256: 'Daylight', + 512: 'Cloudy', + 768: 'DaylightColor-Fluorescent', + 769: 'DaywhiteColor-Fluorescent', + 770: 'White-Fluorescent', + 1024: 'Incandescent', + 3840: 'Custom'}), + 0x1003: ('Color', + {0: 'Normal', + 256: 'High', + 512: 'Low'}), + 0x1004: ('Tone', + {0: 'Normal', + 256: 'High', + 512: 'Low'}), + 0x1010: ('FlashMode', + {0: 'Auto', + 1: 'On', + 2: 'Off', + 3: 'Red Eye Reduction'}), + 0x1011: ('FlashStrength', ), + 0x1020: ('Macro', + {0: 'Off', + 1: 'On'}), + 0x1021: ('FocusMode', + {0: 'Auto', + 1: 'Manual'}), + 0x1030: ('SlowSync', + {0: 'Off', + 1: 'On'}), + 0x1031: ('PictureMode', + {0: 'Auto', + 1: 'Portrait', + 2: 'Landscape', + 4: 'Sports', + 5: 'Night', + 6: 'Program AE', + 256: 'Aperture Priority AE', + 512: 'Shutter Priority AE', + 768: 'Manual Exposure'}), + 0x1100: ('MotorOrBracket', + {0: 'Off', + 1: 'On'}), + 0x1300: ('BlurWarning', + {0: 'Off', + 1: 'On'}), + 0x1301: ('FocusWarning', + {0: 'Off', + 1: 'On'}), + 0x1302: ('AEWarning', + {0: 'Off', + 1: 'On'}), + } + +MAKERNOTE_CANON_TAGS = { + 0x0006: ('ImageType', ), + 0x0007: ('FirmwareVersion', ), + 0x0008: ('ImageNumber', ), + 0x0009: ('OwnerName', ), + } + +# this is in element offset, name, optional value dictionary format +MAKERNOTE_CANON_TAG_0x001 = { + 1: ('Macromode', + {1: 'Macro', + 2: 'Normal'}), + 2: ('SelfTimer', ), + 3: ('Quality', + {2: 'Normal', + 3: 'Fine', + 5: 'Superfine'}), + 4: ('FlashMode', + {0: 'Flash Not Fired', + 1: 'Auto', + 2: 'On', + 3: 'Red-Eye Reduction', + 4: 'Slow Synchro', + 5: 'Auto + Red-Eye Reduction', + 6: 'On + Red-Eye Reduction', + 16: 'external flash'}), + 5: ('ContinuousDriveMode', + {0: 'Single Or Timer', + 1: 'Continuous'}), + 7: ('FocusMode', + {0: 'One-Shot', + 1: 'AI Servo', + 2: 'AI Focus', + 3: 'MF', + 4: 'Single', + 5: 'Continuous', + 6: 'MF'}), + 10: ('ImageSize', + {0: 'Large', + 1: 'Medium', + 2: 'Small'}), + 11: ('EasyShootingMode', + {0: 'Full Auto', + 1: 'Manual', + 2: 'Landscape', + 3: 'Fast Shutter', + 4: 'Slow Shutter', + 5: 'Night', + 6: 'B&W', + 7: 'Sepia', + 8: 'Portrait', + 9: 'Sports', + 10: 'Macro/Close-Up', + 11: 'Pan Focus'}), + 12: ('DigitalZoom', + {0: 'None', + 1: '2x', + 2: '4x'}), + 13: ('Contrast', + {0xFFFF: 'Low', + 0: 'Normal', + 1: 'High'}), + 14: ('Saturation', + {0xFFFF: 'Low', + 0: 'Normal', + 1: 'High'}), + 15: ('Sharpness', + {0xFFFF: 'Low', + 0: 'Normal', + 1: 'High'}), + 16: ('ISO', + {0: 'See ISOSpeedRatings Tag', + 15: 'Auto', + 16: '50', + 17: '100', + 18: '200', + 19: '400'}), + 17: ('MeteringMode', + {3: 'Evaluative', + 4: 'Partial', + 5: 'Center-weighted'}), + 18: ('FocusType', + {0: 'Manual', + 1: 'Auto', + 3: 'Close-Up (Macro)', + 8: 'Locked (Pan Mode)'}), + 19: ('AFPointSelected', + {0x3000: 'None (MF)', + 0x3001: 'Auto-Selected', + 0x3002: 'Right', + 0x3003: 'Center', + 0x3004: 'Left'}), + 20: ('ExposureMode', + {0: 'Easy Shooting', + 1: 'Program', + 2: 'Tv-priority', + 3: 'Av-priority', + 4: 'Manual', + 5: 'A-DEP'}), + 23: ('LongFocalLengthOfLensInFocalUnits', ), + 24: ('ShortFocalLengthOfLensInFocalUnits', ), + 25: ('FocalUnitsPerMM', ), + 28: ('FlashActivity', + {0: 'Did Not Fire', + 1: 'Fired'}), + 29: ('FlashDetails', + {14: 'External E-TTL', + 13: 'Internal Flash', + 11: 'FP Sync Used', + 7: '2nd("Rear")-Curtain Sync Used', + 4: 'FP Sync Enabled'}), + 32: ('FocusMode', + {0: 'Single', + 1: 'Continuous'}), + } + +MAKERNOTE_CANON_TAG_0x004 = { + 7: ('WhiteBalance', + {0: 'Auto', + 1: 'Sunny', + 2: 'Cloudy', + 3: 'Tungsten', + 4: 'Fluorescent', + 5: 'Flash', + 6: 'Custom'}), + 9: ('SequenceNumber', ), + 14: ('AFPointUsed', ), + 15: ('FlashBias', + {0XFFC0: '-2 EV', + 0XFFCC: '-1.67 EV', + 0XFFD0: '-1.50 EV', + 0XFFD4: '-1.33 EV', + 0XFFE0: '-1 EV', + 0XFFEC: '-0.67 EV', + 0XFFF0: '-0.50 EV', + 0XFFF4: '-0.33 EV', + 0X0000: '0 EV', + 0X000C: '0.33 EV', + 0X0010: '0.50 EV', + 0X0014: '0.67 EV', + 0X0020: '1 EV', + 0X002C: '1.33 EV', + 0X0030: '1.50 EV', + 0X0034: '1.67 EV', + 0X0040: '2 EV'}), + 19: ('SubjectDistance', ), + } + +# extract multibyte integer in Motorola format (little endian) +def s2n_motorola(str): + x = 0 + for c in str: + x = (x << 8) | ord(c) + return x + +# extract multibyte integer in Intel format (big endian) +def s2n_intel(str): + x = 0 + y = 0L + for c in str: + x = x | (ord(c) << y) + y = y + 8 + return x + +# ratio object that eventually will be able to reduce itself to lowest +# common denominator for printing +def gcd(a, b): + if b == 0: + return a + else: + return gcd(b, a % b) + +class Ratio: + def __init__(self, num, den): + self.num = num + self.den = den + + def __repr__(self): + self.reduce() + if self.den == 1: + return str(self.num) + return '%d/%d' % (self.num, self.den) + + def reduce(self): + div = gcd(self.num, self.den) + if div > 1: + self.num = self.num / div + self.den = self.den / div + +# for ease of dealing with tags +class IFD_Tag: + def __init__(self, printable, tag, field_type, values, field_offset, + field_length): + # printable version of data + self.printable = printable + # tag ID number + self.tag = tag + # field type as index into FIELD_TYPES + self.field_type = field_type + # offset of start of field in bytes from beginning of IFD + self.field_offset = field_offset + # length of data field in bytes + self.field_length = field_length + # either a string or array of data items + self.values = values + + def __str__(self): + return self.printable + + def __repr__(self): + return '(0x%04X) %s=%s @ %d' % (self.tag, + FIELD_TYPES[self.field_type][2], + self.printable, + self.field_offset) + +# class that handles an EXIF header +class EXIF_header: + def __init__(self, file, endian, offset, fake_exif, debug=0): + self.file = file + self.endian = endian + self.offset = offset + self.fake_exif = fake_exif + self.debug = debug + self.tags = {} + + # convert slice to integer, based on sign and endian flags + # usually this offset is assumed to be relative to the beginning of the + # start of the EXIF information. For some cameras that use relative tags, + # this offset may be relative to some other starting point. + def s2n(self, offset, length, signed=0): + self.file.seek(self.offset+offset) + slice=self.file.read(length) + if self.endian == 'I': + val=s2n_intel(slice) + else: + val=s2n_motorola(slice) + # Sign extension ? + if signed: + msb=1L << (8*length-1) + if val & msb: + val=val-(msb << 1) + return val + + # convert offset to string + def n2s(self, offset, length): + s = '' + for dummy in range(length): + if self.endian == 'I': + s = s + chr(offset & 0xFF) + else: + s = chr(offset & 0xFF) + s + offset = offset >> 8 + return s + + # return first IFD + def first_IFD(self): + return self.s2n(4, 4) + + # return pointer to next IFD + def next_IFD(self, ifd): + entries=self.s2n(ifd, 2) + return self.s2n(ifd+2+12*entries, 4) + + # return list of IFDs in header + def list_IFDs(self): + i=self.first_IFD() + a=[] + while i: + a.append(i) + i=self.next_IFD(i) + return a + + # return list of entries in this IFD + def dump_IFD(self, ifd, ifd_name, dict=EXIF_TAGS, relative=0, stop_tag='UNDEF'): + entries=self.s2n(ifd, 2) + for i in range(entries): + # entry is index of start of this IFD in the file + entry = ifd + 2 + 12 * i + tag = self.s2n(entry, 2) + + # get tag name early to avoid errors, help debug + tag_entry = dict.get(tag) + if tag_entry: + tag_name = tag_entry[0] + else: + tag_name = 'Tag 0x%04X' % tag + + # ignore certain tags for faster processing + if not (not detailed and tag in IGNORE_TAGS): + field_type = self.s2n(entry + 2, 2) + if not 0 < field_type < len(FIELD_TYPES): + # unknown field type + raise ValueError('unknown type %d in tag 0x%04X' % (field_type, tag)) + typelen = FIELD_TYPES[field_type][0] + count = self.s2n(entry + 4, 4) + offset = entry + 8 + if count * typelen > 4: + # offset is not the value; it's a pointer to the value + # if relative we set things up so s2n will seek to the right + # place when it adds self.offset. Note that this 'relative' + # is for the Nikon type 3 makernote. Other cameras may use + # other relative offsets, which would have to be computed here + # slightly differently. + if relative: + tmp_offset = self.s2n(offset, 4) + offset = tmp_offset + ifd - self.offset + 4 + if self.fake_exif: + offset = offset + 18 + else: + offset = self.s2n(offset, 4) + field_offset = offset + if field_type == 2: + # special case: null-terminated ASCII string + if count != 0: + self.file.seek(self.offset + offset) + values = self.file.read(count) + values = values.strip().replace('\x00', '') + else: + values = '' + else: + values = [] + signed = (field_type in [6, 8, 9, 10]) + for dummy in range(count): + if field_type in (5, 10): + # a ratio + value = Ratio(self.s2n(offset, 4, signed), + self.s2n(offset + 4, 4, signed)) + else: + value = self.s2n(offset, typelen, signed) + values.append(value) + offset = offset + typelen + # now "values" is either a string or an array + if count == 1 and field_type != 2: + printable=str(values[0]) + else: + printable=str(values) + # compute printable version of values + if tag_entry: + if len(tag_entry) != 1: + # optional 2nd tag element is present + if callable(tag_entry[1]): + # call mapping function + printable = tag_entry[1](values) + else: + printable = '' + for i in values: + # use lookup table for this tag + printable += tag_entry[1].get(i, repr(i)) + + self.tags[ifd_name + ' ' + tag_name] = IFD_Tag(printable, tag, + field_type, + values, field_offset, + count * typelen) + if self.debug: + print ' debug: %s: %s' % (tag_name, + repr(self.tags[ifd_name + ' ' + tag_name])) + + if tag_name == stop_tag: + break + + # extract uncompressed TIFF thumbnail (like pulling teeth) + # we take advantage of the pre-existing layout in the thumbnail IFD as + # much as possible + def extract_TIFF_thumbnail(self, thumb_ifd): + entries = self.s2n(thumb_ifd, 2) + # this is header plus offset to IFD ... + if self.endian == 'M': + tiff = 'MM\x00*\x00\x00\x00\x08' + else: + tiff = 'II*\x00\x08\x00\x00\x00' + # ... plus thumbnail IFD data plus a null "next IFD" pointer + self.file.seek(self.offset+thumb_ifd) + tiff += self.file.read(entries*12+2)+'\x00\x00\x00\x00' + + # fix up large value offset pointers into data area + for i in range(entries): + entry = thumb_ifd + 2 + 12 * i + tag = self.s2n(entry, 2) + field_type = self.s2n(entry+2, 2) + typelen = FIELD_TYPES[field_type][0] + count = self.s2n(entry+4, 4) + oldoff = self.s2n(entry+8, 4) + # start of the 4-byte pointer area in entry + ptr = i * 12 + 18 + # remember strip offsets location + if tag == 0x0111: + strip_off = ptr + strip_len = count * typelen + # is it in the data area? + if count * typelen > 4: + # update offset pointer (nasty "strings are immutable" crap) + # should be able to say "tiff[ptr:ptr+4]=newoff" + newoff = len(tiff) + tiff = tiff[:ptr] + self.n2s(newoff, 4) + tiff[ptr+4:] + # remember strip offsets location + if tag == 0x0111: + strip_off = newoff + strip_len = 4 + # get original data and store it + self.file.seek(self.offset + oldoff) + tiff += self.file.read(count * typelen) + + # add pixel strips and update strip offset info + old_offsets = self.tags['Thumbnail StripOffsets'].values + old_counts = self.tags['Thumbnail StripByteCounts'].values + for i in range(len(old_offsets)): + # update offset pointer (more nasty "strings are immutable" crap) + offset = self.n2s(len(tiff), strip_len) + tiff = tiff[:strip_off] + offset + tiff[strip_off + strip_len:] + strip_off += strip_len + # add pixel strip to end + self.file.seek(self.offset + old_offsets[i]) + tiff += self.file.read(old_counts[i]) + + self.tags['TIFFThumbnail'] = tiff + + # decode all the camera-specific MakerNote formats + + # Note is the data that comprises this MakerNote. The MakerNote will + # likely have pointers in it that point to other parts of the file. We'll + # use self.offset as the starting point for most of those pointers, since + # they are relative to the beginning of the file. + # + # If the MakerNote is in a newer format, it may use relative addressing + # within the MakerNote. In that case we'll use relative addresses for the + # pointers. + # + # As an aside: it's not just to be annoying that the manufacturers use + # relative offsets. It's so that if the makernote has to be moved by the + # picture software all of the offsets don't have to be adjusted. Overall, + # this is probably the right strategy for makernotes, though the spec is + # ambiguous. (The spec does not appear to imagine that makernotes would + # follow EXIF format internally. Once they did, it's ambiguous whether + # the offsets should be from the header at the start of all the EXIF info, + # or from the header at the start of the makernote.) + def decode_maker_note(self): + note = self.tags['EXIF MakerNote'] + make = self.tags['Image Make'].printable + # model = self.tags['Image Model'].printable # unused + + # Nikon + # The maker note usually starts with the word Nikon, followed by the + # type of the makernote (1 or 2, as a short). If the word Nikon is + # not at the start of the makernote, it's probably type 2, since some + # cameras work that way. + if make in ('NIKON', 'NIKON CORPORATION'): + if note.values[0:7] == [78, 105, 107, 111, 110, 0, 1]: + if self.debug: + print "Looks like a type 1 Nikon MakerNote." + self.dump_IFD(note.field_offset+8, 'MakerNote', + dict=MAKERNOTE_NIKON_OLDER_TAGS) + elif note.values[0:7] == [78, 105, 107, 111, 110, 0, 2]: + if self.debug: + print "Looks like a labeled type 2 Nikon MakerNote" + if note.values[12:14] != [0, 42] and note.values[12:14] != [42L, 0L]: + raise ValueError("Missing marker tag '42' in MakerNote.") + # skip the Makernote label and the TIFF header + self.dump_IFD(note.field_offset+10+8, 'MakerNote', + dict=MAKERNOTE_NIKON_NEWER_TAGS, relative=1) + else: + # E99x or D1 + if self.debug: + print "Looks like an unlabeled type 2 Nikon MakerNote" + self.dump_IFD(note.field_offset, 'MakerNote', + dict=MAKERNOTE_NIKON_NEWER_TAGS) + return + + # Olympus + if make.startswith('OLYMPUS'): + self.dump_IFD(note.field_offset+8, 'MakerNote', + dict=MAKERNOTE_OLYMPUS_TAGS) + # TODO + #for i in (('MakerNote Tag 0x2020', MAKERNOTE_OLYMPUS_TAG_0x2020),): + # self.decode_olympus_tag(self.tags[i[0]].values, i[1]) + #return + + # Casio + if make == 'Casio': + self.dump_IFD(note.field_offset, 'MakerNote', + dict=MAKERNOTE_CASIO_TAGS) + return + + # Fujifilm + if make == 'FUJIFILM': + # bug: everything else is "Motorola" endian, but the MakerNote + # is "Intel" endian + endian = self.endian + self.endian = 'I' + # bug: IFD offsets are from beginning of MakerNote, not + # beginning of file header + offset = self.offset + self.offset += note.field_offset + # process note with bogus values (note is actually at offset 12) + self.dump_IFD(12, 'MakerNote', dict=MAKERNOTE_FUJIFILM_TAGS) + # reset to correct values + self.endian = endian + self.offset = offset + return + + # Canon + if make == 'Canon': + self.dump_IFD(note.field_offset, 'MakerNote', + dict=MAKERNOTE_CANON_TAGS) + for i in (('MakerNote Tag 0x0001', MAKERNOTE_CANON_TAG_0x001), + ('MakerNote Tag 0x0004', MAKERNOTE_CANON_TAG_0x004)): + self.canon_decode_tag(self.tags[i[0]].values, i[1]) + return + + # decode Olympus MakerNote tag based on offset within tag + def olympus_decode_tag(self, value, dict): + pass + + # decode Canon MakerNote tag based on offset within tag + # see http://www.burren.cx/david/canon.html by David Burren + def canon_decode_tag(self, value, dict): + for i in range(1, len(value)): + x=dict.get(i, ('Unknown', )) + if self.debug: + print i, x + name=x[0] + if len(x) > 1: + val=x[1].get(value[i], 'Unknown') + else: + val=value[i] + # it's not a real IFD Tag but we fake one to make everybody + # happy. this will have a "proprietary" type + self.tags['MakerNote '+name]=IFD_Tag(str(val), None, 0, None, + None, None) + +# process an image file (expects an open file object) +# this is the function that has to deal with all the arbitrary nasty bits +# of the EXIF standard +def process_file(f, stop_tag='UNDEF', details=True, debug=False): + # yah it's cheesy... + global detailed + detailed = details + + # by default do not fake an EXIF beginning + fake_exif = 0 + + # determine whether it's a JPEG or TIFF + data = f.read(12) + if data[0:4] in ['II*\x00', 'MM\x00*']: + # it's a TIFF file + f.seek(0) + endian = f.read(1) + f.read(1) + offset = 0 + elif data[0:2] == '\xFF\xD8': + # it's a JPEG file + while data[2] == '\xFF' and data[6:10] in ('JFIF', 'JFXX', 'OLYM', 'Phot'): + length = ord(data[4])*256+ord(data[5]) + f.read(length-8) + # fake an EXIF beginning of file + data = '\xFF\x00'+f.read(10) + fake_exif = 1 + if data[2] == '\xFF' and data[6:10] == 'Exif': + # detected EXIF header + offset = f.tell() + endian = f.read(1) + else: + # no EXIF information + return {} + else: + # file format not recognized + return {} + + # deal with the EXIF info we found + if debug: + print {'I': 'Intel', 'M': 'Motorola'}[endian], 'format' + hdr = EXIF_header(f, endian, offset, fake_exif, debug) + ifd_list = hdr.list_IFDs() + ctr = 0 + for i in ifd_list: + if ctr == 0: + IFD_name = 'Image' + elif ctr == 1: + IFD_name = 'Thumbnail' + thumb_ifd = i + else: + IFD_name = 'IFD %d' % ctr + if debug: + print ' IFD %d (%s) at offset %d:' % (ctr, IFD_name, i) + hdr.dump_IFD(i, IFD_name, stop_tag=stop_tag) + # EXIF IFD + exif_off = hdr.tags.get(IFD_name+' ExifOffset') + if exif_off: + if debug: + print ' EXIF SubIFD at offset %d:' % exif_off.values[0] + hdr.dump_IFD(exif_off.values[0], 'EXIF', stop_tag=stop_tag) + # Interoperability IFD contained in EXIF IFD + intr_off = hdr.tags.get('EXIF SubIFD InteroperabilityOffset') + if intr_off: + if debug: + print ' EXIF Interoperability SubSubIFD at offset %d:' \ + % intr_off.values[0] + hdr.dump_IFD(intr_off.values[0], 'EXIF Interoperability', + dict=INTR_TAGS, stop_tag=stop_tag) + # GPS IFD + gps_off = hdr.tags.get(IFD_name+' GPSInfo') + if gps_off: + if debug: + print ' GPS SubIFD at offset %d:' % gps_off.values[0] + hdr.dump_IFD(gps_off.values[0], 'GPS', dict=GPS_TAGS, stop_tag=stop_tag) + ctr += 1 + + # extract uncompressed TIFF thumbnail + thumb = hdr.tags.get('Thumbnail Compression') + if thumb and thumb.printable == 'Uncompressed TIFF': + hdr.extract_TIFF_thumbnail(thumb_ifd) + + # JPEG thumbnail (thankfully the JPEG data is stored as a unit) + thumb_off = hdr.tags.get('Thumbnail JPEGInterchangeFormat') + if thumb_off: + f.seek(offset+thumb_off.values[0]) + size = hdr.tags['Thumbnail JPEGInterchangeFormatLength'].values[0] + hdr.tags['JPEGThumbnail'] = f.read(size) + + # deal with MakerNote contained in EXIF IFD + if 'EXIF MakerNote' in hdr.tags and detailed: + hdr.decode_maker_note() + + # Sometimes in a TIFF file, a JPEG thumbnail is hidden in the MakerNote + # since it's not allowed in a uncompressed TIFF IFD + if 'JPEGThumbnail' not in hdr.tags: + thumb_off=hdr.tags.get('MakerNote JPEGThumbnail') + if thumb_off: + f.seek(offset+thumb_off.values[0]) + hdr.tags['JPEGThumbnail']=file.read(thumb_off.field_length) + + return hdr.tags + + +# show command line usage +def usage(exit_status): + msg = 'Usage: EXIF.py [OPTIONS] file1 [file2 ...]\n' + msg += 'Extract EXIF information from digital camera image files.\n\nOptions:\n' + msg += '-q --quick Do not process MakerNotes.\n' + msg += '-t TAG --stop-tag TAG Stop processing when this tag is retrieved.\n' + msg += '-d --debug Run in debug mode.\n' + print msg + sys.exit(exit_status) + +# library test/debug function (dump given files) +if __name__ == '__main__': + import sys + import getopt + + # parse command line options/arguments + try: + opts, args = getopt.getopt(sys.argv[1:], "hqdt:v", ["help", "quick", "debug", "stop-tag="]) + except getopt.GetoptError: + usage(2) + if args == []: + usage(2) + detailed = True + stop_tag = 'UNDEF' + debug = False + for o, a in opts: + if o in ("-h", "--help"): + usage(0) + if o in ("-q", "--quick"): + detailed = False + if o in ("-t", "--stop-tag"): + stop_tag = a + if o in ("-d", "--debug"): + debug = True + + # output info for each file + for filename in args: + try: + file=open(filename, 'rb') + except: + print "'%s' is unreadable\n"%filename + continue + print filename + ':' + # get the tags + data = process_file(file, stop_tag=stop_tag, details=detailed, debug=debug) + if not data: + print 'No EXIF information found' + continue + + x=data.keys() + x.sort() + for i in x: + if i in ('JPEGThumbnail', 'TIFFThumbnail'): + continue + try: + print ' %s (%s): %s' % \ + (i, FIELD_TYPES[data[i].field_type][2], data[i].printable) + except: + print 'error', i, '"', data[i], '"' + if 'JPEGThumbnail' in data: + print 'File has JPEG thumbnail' + print +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mysite/photologue/utils/reflection.py Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,93 @@ +""" Function for generating web 2.0 style image reflection effects. + +Copyright (c) 2007, Justin C. Driscoll +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of reflection.py nor the names of its contributors may be used + to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +""" + +try: + import Image + import ImageColor +except ImportError: + try: + from PIL import Image + from PIL import ImageColor + except ImportError: + raise ImportError("The Python Imaging Library was not found.") + + +def add_reflection(im, bgcolor="#00000", amount=0.4, opacity=0.6): + """ Returns the supplied PIL Image (im) with a reflection effect + + bgcolor The background color of the reflection gradient + amount The height of the reflection as a percentage of the orignal image + opacity The initial opacity of the reflection gradient + + Originally written for the Photologue image management system for Django + and Based on the original concept by Bernd Schlapsi + + """ + # convert bgcolor string to rgb value + background_color = ImageColor.getrgb(bgcolor) + + # copy orignial image and flip the orientation + reflection = im.copy().transpose(Image.FLIP_TOP_BOTTOM) + + # create a new image filled with the bgcolor the same size + background = Image.new("RGB", im.size, background_color) + + # calculate our alpha mask + start = int(255 - (255 * opacity)) # The start of our gradient + steps = int(255 * amount) # the number of intermedite values + increment = (255 - start) / float(steps) + mask = Image.new('L', (1, 255)) + for y in range(255): + if y < steps: + val = int(y * increment + start) + else: + val = 255 + mask.putpixel((0, y), val) + alpha_mask = mask.resize(im.size) + + # merge the reflection onto our background color using the alpha mask + reflection = Image.composite(background, reflection, alpha_mask) + + # crop the reflection + reflection_height = int(im.size[1] * amount) + reflection = reflection.crop((0, 0, im.size[0], reflection_height)) + + # create new image sized to hold both the original image and the reflection + composite = Image.new("RGB", (im.size[0], im.size[1]+reflection_height), background_color) + + # paste the orignal image and the reflection into the composite image + composite.paste(im, (0, 0)) + composite.paste(reflection, (0, im.size[1])) + + # return the image complete with reflection effect + return composite + \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mysite/photologue/utils/watermark.py Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,64 @@ +""" Function for applying watermarks to images. + +Original found here: +http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/362879 + +""" + +try: + import Image + import ImageEnhance +except ImportError: + try: + from PIL import Image + from PIL import ImageEnhance + except ImportError: + raise ImportError("The Python Imaging Library was not found.") + +def reduce_opacity(im, opacity): + """Returns an image with reduced opacity.""" + assert opacity >= 0 and opacity <= 1 + if im.mode != 'RGBA': + im = im.convert('RGBA') + else: + im = im.copy() + alpha = im.split()[3] + alpha = ImageEnhance.Brightness(alpha).enhance(opacity) + im.putalpha(alpha) + return im + +def apply_watermark(im, mark, position, opacity=1): + """Adds a watermark to an image.""" + if opacity < 1: + mark = reduce_opacity(mark, opacity) + if im.mode != 'RGBA': + im = im.convert('RGBA') + # create a transparent layer the size of the image and draw the + # watermark in that layer. + layer = Image.new('RGBA', im.size, (0,0,0,0)) + if position == 'tile': + for y in range(0, im.size[1], mark.size[1]): + for x in range(0, im.size[0], mark.size[0]): + layer.paste(mark, (x, y)) + elif position == 'scale': + # scale, but preserve the aspect ratio + ratio = min( + float(im.size[0]) / mark.size[0], float(im.size[1]) / mark.size[1]) + w = int(mark.size[0] * ratio) + h = int(mark.size[1] * ratio) + mark = mark.resize((w, h)) + layer.paste(mark, ((im.size[0] - w) / 2, (im.size[1] - h) / 2)) + else: + layer.paste(mark, position) + # composite the watermark with the layer + return Image.composite(layer, im, layer) + +def test(): + im = Image.open('test.png') + mark = Image.open('overlay.png') + watermark(im, mark, 'tile', 0.5).show() + watermark(im, mark, 'scale', 1.0).show() + watermark(im, mark, (100, 100), 0.5).show() + +if __name__ == '__main__': + test()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mysite/pl-admin.py Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,116 @@ +import getopt, sys + +try: + import settings # Assumed to be in the same directory. + from django.core.management import setup_environ +except ImportError: + import sys + sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__) + sys.exit(1) + + +def precache(sizes=[], reset=False): + # setup django environment + setup_environ(settings) + + # import models + from photologue.models import Photo, PhotoSize, PhotoSizeCache + + cache = PhotoSizeCache() + + print 'Caching photos, this may take a while...' + + for photo in Photo.objects.all(): + if len(sizes): + for size in sizes: + photosize = cache.sizes.get(size, None) + if photosize is None: + print '\nA photosize named "%s" was not found...' % size + else: + if reset: + photo.remove_size(photosize) + photo.create_size(photosize) + else: + for size in caches.sizes.values(): + if reset: + Photo.remove_size(photosize) + photo.create_size(photosize) + + print ' Complete.' + sys.exit(2) + + +def reset(): + # setup django environment + setup_environ(settings) + + # import models + from photologue.models import Photo, PhotoSize + + print 'Reseting photo cache, this may take a while...' + + for photo in Photo.objects.all(): + photo.clear_cache() + + print ' Complete.' + sys.exit(2) + + +def usage(): + print """ + +pl-admin.py - Photologue administration script. + +Available Commands: + pl-admin.py create Resizes and caches all defined photo sizes for each image. + pl-admin.py reset Removes all cached images. + +Options: + --reset (-r) If calling create the script will clear the existing photo cache + before regenerating the specified size (or sizes) + --size (-s) The name of a photosize generate + +Usage: + pl-admin.py [options] command + +Examples: + pl-admin.py -r -s=thumbnail create + pl-admin.py -s=thumbnail -s=display create + pl-admin.py reset + +""" + +def main(): + try: + opts, args = getopt.getopt(sys.argv[1:], "hrs:", + ["help", "reset", "sizes="]) + except getopt.GetoptError, err: + print str(err) + usage() + sys.exit(2) + r = False + s = [] + for o, a in opts: + if o in ("-h", "--help"): + usage() + sys.exit(2) + if o in ("-r", "--reset"): + r = True + elif o in ("-s", "--sizes"): + s.append(a.strip('=')) + else: + usage() + sys.exit(2) + + if len(args) == 1: + command = args[0] + if command == 'create': + precache(s, r) + elif command == 'reset': + reset() + + usage() + + +if __name__ == '__main__': + main()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mysite/settings.py Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,102 @@ +# Django settings for madeira project. + +import os +import platform +import local_settings +project_path = os.path.abspath(os.path.split(__file__)[0]) + +DEBUG = local_settings.DEBUG +TEMPLATE_DEBUG = DEBUG + +ADMINS = ( + ('Brian Neal', 'admin@surfguitar101.com'), +) + +MANAGERS = ADMINS + +DATABASE_ENGINE = local_settings.DATABASE_ENGINE +DATABASE_NAME = local_settings.DATABASE_NAME +DATABASE_USER = local_settings.DATABASE_USER +DATABASE_PASSWORD = local_settings.DATABASE_PASSWORD +DATABASE_HOST = local_settings.DATABASE_HOST +DATABASE_PORT = local_settings.DATABASE_PORT + +INTERNAL_IPS = local_settings.INTERNAL_IPS + +# Local time zone for this installation. Choices can be found here: +# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name +# although not all choices may be available on all operating systems. +# If running in a Windows environment this must be set to the same as your +# system time zone. +TIME_ZONE = local_settings.TIME_ZONE + +# Language code for this installation. All choices can be found here: +# http://www.i18nguy.com/unicode/language-identifiers.html +LANGUAGE_CODE = 'en-us' + +SITE_ID = local_settings.SITE_ID + +# If you set this to False, Django will make some optimizations so as not +# to load the internationalization machinery. +USE_I18N = False + +# Absolute path to the directory that holds media. +# Example: "/home/media/media.lawrence.com/" +MEDIA_ROOT = local_settings.MEDIA_ROOT + +# URL that handles the media served from MEDIA_ROOT. Make sure to use a +# trailing slash if there is a path component (optional in other cases). +# Examples: "http://media.lawrence.com", "http://example.com/media/" +MEDIA_URL = local_settings.MEDIA_URL + +# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a +# trailing slash. +# Examples: "http://foo.com/media/", "/media/". +ADMIN_MEDIA_PREFIX = local_settings.ADMIN_MEDIA_PREFIX + +# Make this unique, and don't share it with anybody. +SECRET_KEY = local_settings.SECRET_KEY + +# List of callables that know how to import templates from various sources. +TEMPLATE_LOADERS = ( + 'django.template.loaders.filesystem.load_template_source', + 'django.template.loaders.app_directories.load_template_source', +) + +if not DEBUG: + CACHE_BACKEND = local_settings.CACHE_BACKEND + CACHE_MIDDLEWARE_SECONDS = local_settings.CACHE_MIDDLEWARE_SECONDS + CACHE_MIDDLEWARE_KEY_PREFIX = local_settings.CACHE_MIDDLEWARE_KEY_PREFIX + CACHE_MIDDLEWARE_ANONYMOUS_ONLY = local_settings.CACHE_MIDDLEWARE_ANONYMOUS_ONLY + +MIDDLEWARE_CLASSES = local_settings.MIDDLEWARE_CLASSES + +ROOT_URLCONF = 'mysite.urls' + +# Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". +# Always use forward slashes, even on Windows. +# Don't forget to use absolute paths, not relative paths. +TEMPLATE_DIRS = ( + os.path.join(project_path, 'templates'), + os.path.join(project_path, 'templates', 'band'), + os.path.join(project_path, 'photologue', 'templates'), +) + +TEMPLATE_CONTEXT_PROCESSORS = ( + 'django.core.context_processors.auth', + 'django.core.context_processors.debug', + 'django.core.context_processors.media', + 'django.core.context_processors.request' +) + +INSTALLED_APPS = ( + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.admin', + 'django.contrib.markup', + 'django.contrib.sites', + 'django.contrib.flatpages', + 'mysite.band', + 'mysite.photologue', +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mysite/sqllog.py Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,20 @@ +from django.db import connection +from django.template import Template, Context + +class SQLLogMiddleware: + + def process_response ( self, request, response ): + time = 0.0 + for q in connection.queries: + time += float(q['time']) + t = Template(''' + <p><em>Total query count:</em> {{ count }}<br/> + <em>Total execution time:</em> {{ time }}</p> + <ol class="sqllog"> + {% for sql in sqllog %} + <li>{{ sql.time }}: {{ sql.sql }}</li> + {% endfor %} + </ol> + ''') + response.content = "%s%s" % ( response.content, t.render(Context({'sqllog':connection.queries,'count':len(connection.queries),'time':time}))) + return response
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mysite/templates/404.html Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" +"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head><title>Page Not Found</title> +<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}css/theme.css" /> +<link rel="shortcut icon" type="image/vnd.microsoft.com" href="{{ MEDIA_URL }}images/favicon.ico" /> +</head> +<body> + + <h1>Not Found</h1> + + <p>The requested URL {{ request.path|escape }} was not found on this server.</p> + +</body> +</html>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mysite/templates/500.html Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" +"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head><title>Internal Server Error</title> +</head> +<body> + + <h1>Internal Server Error</h1> + + <p>We're sorry, that page is currently unavailable due to a server misconfiguration.</p> + <p>The server administrator has been notified, and we apologise for any inconvenience.</p> + +</body> +</html>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mysite/templates/admin/band/email.html Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,33 @@ +{% extends 'admin/base_site.html' %} +{% block title %}The Madeira | Mailing List Email Form{% endblock %} +{% block extrastyle %} +<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}django/css/forms.css" /> +{% endblock %} +{% block bodyclass %}change-form{% endblock %} +{% block breadcrumbs %} +<div class="breadcrumbs"> + <a href="/admin">Home</a> +</div> +{% endblock %} +{% block content %} +<h1>Madeira Mailing List Email Form</h1> +<div id="content-main"> +<p>Use this form to send an email to all subscribers of the The Madeira mailing list.</p> +<form method="post" action="{{ request.build_absolute_uri }}"> +<div> +<fieldset class="module aligned"> + {% for field in form %} + <div class="form-row {% if field.field.required %}required{% endif %} {% if field.errors %}errors{% endif %}"> + {% if field.errors %}{{ field.errors }}{% endif %} + {{ field.label_tag }} + {{ field }} + </div> + {% endfor %} +</fieldset> +<div class="submit-row"> + <input type="submit" name="Send" value="Send" class="default" /> +</div> +</div> +</form> +</div> +{% endblock %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mysite/templates/admin/band/email_sent.html Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,16 @@ +{% extends 'admin/base_site.html' %} +{% block title %}The Madeira | Mailing List Email Sent{% endblock %} +{% block extrastyle %} +<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}django/css/forms.css" /> +{% endblock %} +{% block breadcrumbs %} +<div class="breadcrumbs"> + <a href="/admin">Home</a> +</div> +{% endblock %} +{% block content %} +<h1>Madeira Mailing List Email Sent</h1> +<div id="content-main"> + <p>Your email has been sent.</p> +</div> +{% endblock %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mysite/templates/admin/base_site.html Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,13 @@ +{% extends "admin/base.html" %} +{% load i18n %} + +{% block title %}{{ title }} | {% trans 'Madeira site admin' %}{% endblock %} +{% block extrahead %} +<link rel="shortcut icon" href="/media/images/favicon.ico" type="image/vnd.microsoft.icon" /> +{% endblock %} + +{% block branding %} +<h1 id="site-name">{% trans 'Madeira site administration' %}</h1> +{% endblock %} + +{% block nav-global %}{% endblock %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mysite/templates/admin/index.html Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,129 @@ +{% extends "admin/base_site.html" %} +{% load i18n %} + +{% block extrastyle %}{{ block.super }}<link rel="stylesheet" type="text/css" href="{% load adminmedia %}{% admin_media_prefix %}css/dashboard.css" />{% endblock %} + +{% block coltype %}colMS{% endblock %} +{% block bodyclass %}dashboard{% endblock %} +{% block breadcrumbs %}{% endblock %} +{% block content %} +<div id="content-main"> + +{% if app_list %} + <div class="module"> + <table summary="Madeira custom views"> + <caption>Madeira Quick Links</caption> + <!-- ============================================================================== --> + {% if perms.band.add_news or perms.band.change_news %} + <tr> + <th>{% if perms.band.change_news %}<a href="band/news/">{% endif %}News Items{% if perms.band.change_news %}</a>{% endif %}</th> + <td class="x50">{% if perms.band.add_news %}<a href="band/news/add/" class="addlink">{% endif %}Add{% if perms.band.add_news %}</a>{% endif %}</td> + <td class="x75">{% if perms.band.change_news %}<a href="band/news/" class="changelink">{% endif %}Change{% if perms.band.change_news %}</a>{% endif %}</td> + </tr> + {% endif %} + {% if perms.band.add_member or perms.band.change_member %} + <tr> + <th>{% if perms.band.change_member %}<a href="band/member/">{% endif %}Biography & Gear{% if perms.band.change_member %}</a>{% endif %}</th> + <td class="x50">{% if perms.band.add_member %}<a href="band/member/add/" class="addlink">{% endif %}Add{% if perms.band.add_member %}</a>{% endif %}</td> + <td class="x75">{% if perms.band.change_member %}<a href="band/member/" class="changelink">{% endif %}Change{% if perms.band.change_member %}</a>{% endif %}</td> + </tr> + {% endif %} + {% if perms.band.add_gig or perms.band.change_gig %} + <tr> + <th>{% if perms.band.change_gig %}<a href="band/gig/">{% endif %}Gigs{% if perms.band.change_gig %}</a>{% endif %}</th> + <td class="x50">{% if perms.band.add_gig %}<a href="band/gig/add/" class="addlink">{% endif %}Add{% if perms.band.add_gig %}</a>{% endif %}</td> + <td class="x75">{% if perms.band.change_gig %}<a href="band/gig/" class="changelink">{% endif %}Change{% if perms.band.change_gig %}</a>{% endif %}</td> + </tr> + {% endif %} + {% if perms.band.add_article or perms.band.change_article %} + <tr> + <th>{% if perms.band.change_article %}<a href="band/article/">{% endif %}Press Items{% if perms.band.change_article %}</a>{% endif %}</th> + <td class="x50">{% if perms.band.add_article %}<a href="band/article/add/" class="addlink">{% endif %}Add{% if perms.band.add_article %}</a>{% endif %}</td> + <td class="x75">{% if perms.band.change_article %}<a href="band/article/" class="changelink">{% endif %}Change{% if perms.band.change_article %}</a>{% endif %}</td> + </tr> + {% endif %} + {% if perms.band.add_mp3_set or perms.band.change_mp3_set %} + <tr> + <th>{% if perms.band.change_mp3_set %}<a href="band/mp3_set/">{% endif %}Songs{% if perms.band.change_mp3_set %}</a>{% endif %}</th> + <td class="x50">{% if perms.band.add_mp3_set %}<a href="band/mp3_set/add/" class="addlink">{% endif %}Add{% if perms.band.add_mp3_set %}</a>{% endif %}</td> + <td class="x75">{% if perms.band.change_mp3_set %}<a href="band/mp3_set/" class="changelink">{% endif %}Change{% if perms.band.change_mp3_set %}</a>{% endif %}</td> + </tr> + {% endif %} + {% if perms.band.add_video_set or perms.band.change_video_set %} + <tr> + <th>{% if perms.band.change_video_set %}<a href="band/video_set/">{% endif %}Videos{% if perms.band.change_video_set %}</a>{% endif %}</th> + <td class="x50">{% if perms.band.add_video_set %}<a href="band/video_set/add/" class="addlink">{% endif %}Add{% if perms.band.add_video_set %}</a>{% endif %}</td> + <td class="x75">{% if perms.band.change_video_set %}<a href="band/video_set/" class="changelink">{% endif %}Change{% if perms.band.change_video_set %}</a>{% endif %}</td> + </tr> + {% endif %} + {% if perms.band.add_merchandise or perms.band.change_merchandise %} + <tr> + <th>{% if perms.band.change_merchandise %}<a href="band/merchandise/">{% endif %}Merchandise{% if perms.band.change_merchandise %}</a>{% endif %}</th> + <td class="x50">{% if perms.band.add_merchandise %}<a href="band/merchandise/add/" class="addlink">{% endif %}Add{% if perms.band.add_merchandise %}</a>{% endif %}</td> + <td class="x75">{% if perms.band.change_merchandise %}<a href="band/merchandise/" class="changelink">{% endif %}Change{% if perms.band.change_merchandise %}</a>{% endif %}</td> + </tr> + {% endif %} + + + <!-- ============================================================================== --> + <tr><th scope="row"><a href="{% url mysite.band.admin_views.email %}">Send Email to Mailing List</a></th> + <td> </td> + <td> </td> + </tr> + </table> + </div> + + + {% for app in app_list %} + <div class="module"> + <table summary="{% blocktrans with app.name as name %}Models available in the {{ name }} application.{% endblocktrans %}"> + <caption><a href="{{ app.app_url }}" class="section">{% blocktrans with app.name as name %}{{ name }}{% endblocktrans %}</a></caption> + {% for model in app.models %} + <tr> + {% if model.perms.change %} + <th scope="row"><a href="{{ model.admin_url }}">{{ model.name }}</a></th> + {% else %} + <th scope="row">{{ model.name }}</th> + {% endif %} + + {% if model.perms.add %} + <td><a href="{{ model.admin_url }}add/" class="addlink">{% trans 'Add' %}</a></td> + {% else %} + <td> </td> + {% endif %} + + {% if model.perms.change %} + <td><a href="{{ model.admin_url }}" class="changelink">{% trans 'Change' %}</a></td> + {% else %} + <td> </td> + {% endif %} + </tr> + {% endfor %} + </table> + </div> + {% endfor %} +{% else %} + <p>{% trans "You don't have permission to edit anything." %}</p> +{% endif %} +</div> +{% endblock %} + +{% block sidebar %} +<div id="content-related"> + <div class="module" id="recent-actions-module"> + <h2>{% trans 'Recent Actions' %}</h2> + <h3>{% trans 'My Actions' %}</h3> + {% load log %} + {% get_admin_log 10 as admin_log for_user user %} + {% if not admin_log %} + <p>{% trans 'None available' %}</p> + {% else %} + <ul class="actionlist"> + {% for entry in admin_log %} + <li class="{% if entry.is_addition %}addlink{% endif %}{% if entry.is_change %}changelink{% endif %}{% if entry.is_deletion %}deletelink{% endif %}">{% if not entry.is_deletion %}<a href="{{ entry.get_admin_url }}">{% endif %}{{ entry.object_repr|escape }}{% if not entry.is_deletion %}</a>{% endif %}<br /><span class="mini quiet">{% filter capfirst %}{% trans entry.content_type.name %}{% endfilter %}</span></li> + {% endfor %} + </ul> + {% endif %} + </div> +</div> +{% endblock %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mysite/templates/band/base.html Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,51 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" +"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head><title>{% block title %}{% endblock %}</title> +<meta http-equiv="Content-Type" content="text/html" /> +<meta http-equiv="Content-Language" content="en-US" /> +<meta name="robots" content="all" /> +<meta name="Author" content="Brian Neal" /> +<meta name="copyright" content="© 2007-2008 Brian Neal" /> +<meta name="keywords" lang="en-us" content="instrumental surf, surf, guitar, musician, instro, surf music, Dick Dale, Atlantics,Surf Coasters, Fender, Strat, Stratocaster, Destination Earth, Destination: Earth!,Space Cossacks, Troubadours, reverb" /> +<meta name="description" lang="en-us" content="Home page for the instrumental surf band The Madeira. The Madeira combine high energy performances reminiscent of The Atlantics and Dick Dale with exotic melodies and an unusually high level of musicianship. This page contains show dates, photos, videos, and news about the band." /> +{% block custom_css %}{% endblock %} +<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}css/theme.css" /> +{% block custom_js %}{% endblock %} +<link rel="shortcut icon" type="image/vnd.microsoft.com" href="{{ MEDIA_URL }}images/favicon.ico" /> +</head> +<body> + +<div class="header"> + <img src="{{ MEDIA_URL }}images/madeira-p10-logo.jpg" border="0" alt="Madeira Logo" /> +</div> + +<div id="navleft"> + <ul> + <li><a href="{% url mysite.band.views.index %}">Home</a></li> + <li><a href="{% url mysite.band.views.news %}">News</a></li> + <li><a href="{% url mysite.band.views.bio %}">Biography</a></li> + <li><a href="{% url mysite.band.views.gigs %}">Shows</a></li> + <li><a href="{% url mysite.band.views.press_index %}">Press</a></li> + <li><a href="{% url mysite.band.views.songs %}">Songs</a></li> + <li><a href="{% url mysite.band.views.photos_index %}">Photos</a></li> + <li><a href="{% url mysite.band.views.videos_index %}">Videos</a></li> + <li><a href="{% url mysite.band.views.flyers %}">Flyers</a></li> + <li><a href="{% url mysite.band.views.buy %}">Buy</a></li> + <li><a href="{% url mysite.band.views.contact %}">Contact</a></li> + <li><a href="{% url mysite.band.views.mail %}">Mailing List</a></li> + </ul> +</div> + +<div class="content"> + {% block content %} + {% endblock %} + <div id="footer"> + Website © 2008 by The Madeira <br /> + Visit <a href="http://myspace.com/themadeira">The Madeira on Myspace</a> <br /> + </div> +</div> + +</body> +</html>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mysite/templates/band/bio.html Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,24 @@ +{% extends 'band/base.html' %} +{% block title %}The Madeira | Biography{% endblock %} +{% block content %} +<h1>Band Biography</h1> +{% if members %} + {% for member in members %} + <h2>{{ member.name }} - {{ member.instrument }}</h2> + {% if member.photo %} + <img class="floatLeftBox" src="{{ member.photo.url }}" border="0" alt="{{ member.name }}" title="{{ member.name }}" /> + {{ member.bio|linebreaks }} + {% if member.gear_set.all %} + <p>Gear:</p> + <ul> + {% for item in member.gear_set.all %} + <li>{{ item.item }}</li> + {% endfor %} + </ul> + {% endif %} + {% endif %} + {% endfor %} +{% else %} +The band has no members. +{% endif %} +{% endblock %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mysite/templates/band/buy.html Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,52 @@ +{% extends 'band/base.html' %} +{% block title %}The Madeira | Merchandise{% endblock %} +{% block content %} +<h1>Madeira Merchandise</h1> +{% for album in albums %} + <h2>{{ album.title }} </h2> + <div style="float: right;"> + <img src="{{ album.photo.image.url }}" alt="{{ album.title }}" title="{{ album.title }}" /> + </div> + {% if album.label_release_set %} + <ul> + {% for release in album.label_release_set.all %} + <li><a href="{{ release.record_label.url }}">{{ release.record_label.name }}</a> + {{ release.catalog_number }}, {{ release.release_date|date:"F d, Y" }}</li> + {% endfor %} + </ul> + {% endif %} + {{ album.desc|safe|linebreaks }} + <p>Track listing:</p> + <ol> + {% for track in album.album_track_set.all %} + <li>{{ track.track_name }}</li> + {% endfor %} + </ol> + {% if album.album_merchant_set %} + <p>Buy {{ album.title }} at:</p> + <ul> + {% for merchant in album.album_merchant_set.all %} + <li><a href="{{ merchant.url }}">{{ merchant.name }}</a></li> + {% endfor %} + </ul> + {% endif %} + <br clear="all" /> +{% endfor %} +{% if merchandise %} + <hr /> + {{ config.ordering_info|safe|linebreaks }} +{% endif %} +{% for item in merchandise %} + <h2>{{ item.name }}</h2> + <div style="float: right;"> + <img src="{{ item.photo.image.url }}" alt="{{ item.name }}" title="{{ item.name }}" /> + </div> + {{ item.desc|safe|linebreaks }} + {% if item.in_stock %} + <p>Price: ${{ item.price }}</p> + {% else %} + <p><strike>Price: ${{ item.price }}</strike> <strong>SOLD OUT!</strong></p> + {% endif %} + <br clear="all" /> +{% endfor %} +{% endblock %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mysite/templates/band/contact.html Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,14 @@ +{% extends 'band/base.html' %} +{% block title %}The Madeira | Contact{% endblock %} +{% block content %} +<h1>Madeira Contact Info</h1> +<p>For general band inquiries, send email to: <a href="mailto:{{ config.contact_email }}">{{ config.contact_email }}</a>.</p> +<p>To contact individual band members:</p> +<ul> +{% for member in band %} + {% if member.email %} + <li>{{ member.name }}: <a href="mailto:{{ member.email }}">{{ member.email }}</a></li> + {% endif %} +{% endfor %} +</ul> +{% endblock %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mysite/templates/band/email_subscribe.txt Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,19 @@ +Hello, + +We have received a request for this email address to join the mailing list +for {{ band }}. In order for us to process this subscription, we need confirmation from you. + +If you did not request to join this mailing list, you may ignore this message. + +To subscribe to the mailing list, go to the following confirmation URL: + +{{ url }} + +This should take you directly to an email confirmation page. If it does not, +please copy and paste the full URL into your web browser's address box and +hit the "Enter" key on your keyboard. + +Thanks, + +{{ band }} +{{ band_url }}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mysite/templates/band/email_unsubscribe.txt Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,19 @@ +Hello, + +We have received a request for this email address to unsubscribe from the mailing list +for {{ band }}. In order for us to process this request, we need confirmation from you. + +If you did not request to unsubscribe from this mailing list, you may ignore this message. + +To unsubscribe from the mailing list, go to the following confirmation URL: + +{{ url }} + +This should take you directly to an email confirmation page. If it does not, +please copy and paste the full URL into your web browser's address box and +hit the "Enter" key on your keyboard. + +Thanks, + +{{ band }} +{{ band_url }}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mysite/templates/band/flyers.html Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,20 @@ +{% extends 'band/base.html' %} +{% block title %}The Madeira | Flyer Gallery{% endblock %} +{% block content %} +<h1>Show Flyer Gallery</h1> +{% if gigs %} + <center> + {% for gig in gigs %} + <p> + {% if gig.title %} + <img src="{{ gig.flyer.image.url }}" alt="{{ gig.title }}" title="{{ gig.title }} : {{ gig.date|date:"F d, Y" }}" /> + {% else %} + <img src="{{ gig.flyer.image.url }}" alt="{{ gig.date|date:"F d, Y" }}" title="{{ gig.date|date:"F d, Y" }}" /> + {% endif %} + </p> + {% endfor %} + </center> +{% else %} +No flyers available at this time. +{% endif %} +{% endblock %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mysite/templates/band/gigs.html Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,152 @@ +{% extends 'band/base.html' %} +{% block title %}The Madeira | Shows{% endblock %} +{% block custom_css %} +<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}css/thickbox.css" /> +{% endblock %} +{% block custom_js %} +<script type="text/javascript" src="{{ MEDIA_URL }}js/jquery.js"></script> +<script type="text/javascript" src="{{ MEDIA_URL }}js/thickbox.js"></script> +{% endblock %} +{% block content %} +<h1>Show Dates</h1> + +<h2>Upcoming Shows</h2> +{% if upcoming %} + {% for show in upcoming %} + <p style="clear:both"> + {% if show.flyer %} + <a href="{{ show.flyer.image.url }}" class="thickbox" rel="madeira-gallery"> + <img style="float:left; margin-right:5px; margin-bottom:1em" src="{{ show.flyer.get_thumbnail_url }}" + alt="{{ show.flyer.caption }}" title="{{ show.flyer.caption }}" /></a> + {% endif %} + <strong>{{ show.date|date:"F d, Y" }}</strong> + {% if show.time %}{{ show.time|time:"h:i A" }}{% endif %}<br /> + + {% if show.title and show.url %} + <a href="{{ show.url }}" target="_blank">{{ show.title }}</a><br /> + {% else %} + {% if show.title %} + {{ show.title }}<br /> + {% endif %} + {% endif %} + + {% if show.venue %} + {% if show.venue.url %} + <a href="{{ show.venue.url }}" target="_blank">{{ show.venue.name }}</a>, + {% else %} + {{ show.venue }}, + {% endif %} + {% if show.venue.address %} + {{ show.venue.address }}, + {% endif %} + {% if show.venue.city.state %} + {{ show.venue.city.name }}, {{ show.venue.city.state.name }} + {% else %} + {{ show.venue.city.name }} + {% endif %} + <br /> + {% if show.venue.phone %} + {{ show.venue.phone }} + <br /> + {% endif %} + {% endif %} + + {% if show.bands.all %} + With: + {% for band in show.bands.all %} + {% if band.url %} + <a href="{{ band.url }}" target="_blank">{{ band.name }}</a> + {% else %} + {{ band.name }} + {% endif %} + {% if not forloop.last %} + • + {% endif %} + {% endfor %} + <br /> + {% endif %} + + {% if show.notes %} + {{ show.notes|safe }} + {% endif %} + </p> + {% endfor %} +{% else %} +None at this time. +{% endif %} +<br clear="all" /> + +{% if flyerGigs %} +<div class="thumb-box"> + <h2>Flyers</h2> + <table align="center" border="0"> + <tr><td> + {% for gig in flyerGigs %} + <table class="image-table"> + <caption>{{ gig.venue.name}}, {{ gig.date|date:"F 'y" }}</caption> + <tr><td> + <a href="{{ gig.flyer.image.url }}" class="thickbox" rel="madeira-gallery"> + <img src="{{ gig.flyer.get_thumbnail_url }}" alt="{{ gig.date|date:"F d, Y" }}" title="{{ gig.date|date:"F d, Y" }}" /></a> + </td></tr> + </table> + {% endfor %} + </td></tr> + </table> + <div clear="all"></div> + <center><p>To see all our flyers in full size, check out our <a href="{% url band.views.flyers %}">show flyer gallery</a>.</p></center> +</div> +{% endif %} + +{% if previous %} + <h2>Previous Shows</h2> + <center> + <table border="0" cellpadding="3" cellspacing="3" width="95%"> + <tr><th width="20%" align="center">Date</th><th width="40%" align="center">Venue</th><th width="40%" align="center">Bands</th></tr> + {% for show in previous %} + <tr> + <td width="20%">{{ show.date|date:"M d, Y" }}</td> + <td width="40%"> + {% if show.title and show.url %} + <a href="{{ show.url }}" target="_blank">{{ show.title }}</a>, + {% else %} + {% if show.title %} + {{ show.title }}, + {% endif %} + {% endif %} + {% if show.venue.url %} + <a href="{{ show.venue.url }}" target="_blank">{{ show.venue.name }}</a>, + {% else %} + {{ show.venue.name }}, + {% endif %} + {{ show.venue.city.name }}, {{ show.venue.city.state.abbrev }} + </td> + <td width="40%"> + {% for band in show.bands.all %} + {% if band.url %} + <a href="{{ band.url }}" target="_blank">{{ band.name }}</a> + {% else %} + {{ band.name }} + {% endif %} + {% if not forloop.last %} + • + {% endif %} + {% endfor %} + </td> + </tr> + {% endfor %} + </table> + </center> +{% endif %} + +{% if stats %} +<h2>Past Show Statistics</h2> +<table border="0" cellpadding="3" cellspacing="3"> + <tr><th align="left">Number of shows:</th><td>{{ stats.count }}</td></tr> + <tr><th align="left">Number of unique venues:</th><td>{{ stats.venues }}</td></tr> + <tr><th align="left">Number of unique cities:</th><td>{{ stats.cities }}</td></tr> + <tr><th align="left">Number of unique states:</th><td>{{ stats.states }}</td></tr> + <tr><th align="left">Number of unique bands:</th><td>{{ stats.bands }}</td></tr> +</table> +{% endif %} + +{% endblock %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mysite/templates/band/index.html Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,63 @@ +{% extends 'band/base.html' %} +{% block title %}The Madeira{% endblock %} +{% block custom_css %} +<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}css/thickbox.css" /> +{% endblock %} +{% block custom_js %} +<script type="text/javascript" src="{{ MEDIA_URL }}js/jquery.js"></script> +<script type="text/javascript" src="{{ MEDIA_URL }}js/thickbox.js"></script> +{% endblock %} +{% load markup %} +{% block content %} +<h1>The Madeira</h1> +<img class="floatLeftBox" src="{{ config.intro_photo.image.url }}" + alt="{{ config.intro_photo.title }}" title="{{config.intro_photo.title}}" border="0" /> +{{ config.intro_text|textile }} +<br /> + +{% if upcomingDates %} +<div class="center-block"> +<h2>Upcoming Shows...</h2> + +<center><table border="0" cellspacing="10" cellpadding="3"><tr> +{% for gig in upcomingDates %} + {% if gig.flyer %} + <td> + <a href="{{ gig.flyer.image.url }}" class="thickbox" rel="madeira-gallery"> + <img src="{{ gig.flyer.get_thumbnail_url }}" alt="{{ gig.flyer.caption }}" title="{{ gig.flyer.caption }}" /></a> + <br /><center>{{ gig.flyer.caption }}</center> + </td> + {% endif %} +{% endfor %} +</tr></table></center> + +<ul> +{% for show in upcomingDates %} +<li><strong>{{ show.date|date:"l, F d" }}</strong>: {{ show.venue.name }}, {{ show.venue.city.name }}{% if show.venue.city.state %}, {{ show.venue.city.state.name }} +{% endif %}</li> +{% endfor %} +</ul> +<center><a href="{% url mysite.band.views.gigs %}">See all upcoming shows...</a></center> +</div> +{% endif %} + +<div class="newsflash"> + <table border="0" cellspacing="2" cellpadding="2" width="100%"> + <tr><td colspan="2"><center><h1>The Madeira Releases:</h1></center></td></tr> + + <tr><td colspan="2"><center> + <img src="{{ carpe.image.url }}" alt="Carpe Noctem Cover" title="Carpe Noctem" border="0" /> + </center></td></tr> + <tr><td colspan="2"><center><strong>Available Now: Carpe Noctem!</strong><br /> + </center></td></tr> + <tr><td><center><a href="http://www.dblcrown.com"><img src="{{ sandstorm.image.url }}" alt="Sandstorm CD Cover" + title="Sandstorm" border="0" /></a></center></td> + + <td><center><a href="http://www.dblcrown.com"><img src="{{ ruins.image.url }}" alt="Ruins EP Cover" title="Ruins" + border="0" /></a></center></td></tr> + <tr><td><center><a href="http://www.dblcrown.com">Sandstorm</a></center></td> + <td><center><a href="http://www.dblcrown.com">Ruins</a></center></td></tr> + </table> +</div> + +{% endblock %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mysite/templates/band/mail.html Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,21 @@ +{% extends 'band/base.html' %} +{% block title %}The Madeira | Mailing List{% endblock %} +{% block content %} +<h1>Madeira Mailing List</h1> +<p>Get on the Madeira mailing list to receive updates about upcoming shows, releases, and website updates. +This is a low volume list. We do not share your email address with anyone.</p> +<fieldset><legend>Mailing List</legend> + <form method="post" action="{{ request.build_absolute_uri }}"> + <table border="0" class="input-form"> + {% for field in form %} + <tr> + <th>{{ field.label_tag }}{% if field.field.required %}*{% endif %}:</th> + <td>{{ field }} + {% if field.errors %}{{ field.errors }}{% endif %}</td> + </tr> + {% endfor %} + <tr><td><input type="submit" name="Submit" value="Submit" class="submit-button" /></td></tr> + </table> + </form> +</fieldset> +{% endblock %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mysite/templates/band/mail_confirm.html Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,6 @@ +{% extends 'band/base.html' %} +{% block title %}The Madeira | Mailing List Confirmation{% endblock %} +{% block content %} +<h1>Madeira Mailing List Confirmation</h1> +<p>Your email address, {{ email }}, has been successfully {{ action }}.</p> +{% endblock %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mysite/templates/band/mail_not_found.html Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,7 @@ +{% extends 'band/base.html' %} +{% block title %}The Madeira | Mailing List Confirmation{% endblock %} +{% block content %} +<h1>Madeira Mailing List</h1> +<p>Sorry, we did not find that email address in our database.</p> +<p>Back to <a href="{% url mysite.band.views.contact %}">contact page</a>.</p> +{% endblock %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mysite/templates/band/mail_thanks.html Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,9 @@ +{% extends 'band/base.html' %} +{% block title %}The Madeira | Mailing List Confirmation{% endblock %} +{% block content %} +<h1>Madeira Mailing List</h1> +<p>Thanks for subscribing to our email list! You should shortly receive a confirmation email +with instructions on how to complete the subscription process.</p> +<p><strong>Please check your spam folders for this email</strong>. Sometimes it ends up in there. +Thanks.</p> +{% endblock %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mysite/templates/band/mail_unsubscribe.html Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,8 @@ +{% extends 'band/base.html' %} +{% block title %}The Madeira | Mailing List Confirmation{% endblock %} +{% block content %} +<h1>Madeira Mailing List</h1> +<p>We're sorry to see you unsubscribing from our email list! You should shortly receive a confirmation email +with instructions on how to complete the removal process. <strong>Please check your spam folders for +this email</strong>.</p> +{% endblock %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mysite/templates/band/news.html Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,31 @@ +{% extends 'band/base.html' %} +{% load markup %} +{% block title %}The Madeira | News{% endblock %} +{% block content %} +<h1>News</h1> +{% if news %} + {% for story in news %} + <h2>{{ story.date|date:"F d, Y" }} + {% if story.title %} + • {{ story.title }} + {% endif %} + </h2> + <div> + {% if story.photo %} + <img src="{{ story.photo.url }}" class="floatLeftBox" + alt="{{ story.photo_caption }}" title="{{ story.photo_caption }}" border="0" /> + {% endif %} + {% if story.markup_enabled %} + {{ story.text|textile }} + {% else %} + {{ story.text|safe|linebreaks }} + {% endif %} + {% if story.author %} + <p><em>-- {{ story.author }}</em></p> + {% endif %} + </div> + {% endfor %} +{% else %} +No news at this time. +{% endif %} +{% endblock %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mysite/templates/band/photo_detail.html Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,23 @@ +{% extends 'band/base.html' %} +{% load markup %} +{% block title %}The Madeira | Photos: {{ gallery.title }}{% endblock %} +{% block custom_css %} +<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}css/thickbox.css" /> +{% endblock %} +{% block custom_js %} +<script type="text/javascript" src="{{ MEDIA_URL }}js/jquery.js"></script> +<script type="text/javascript" src="{{ MEDIA_URL }}js/thickbox.js"></script> +{% endblock %} +{% block content %} +<h1>Madeira Photos: {{ gallery.title }}</h1> +{{ gallery.description|textile }} + +<div class="madeira-photo-list"> +{% for photo in gallery.photos.all %} + <a href="{{ photo.image.url }}" class="thickbox" rel="madeira-gallery"> + <img src="{{ photo.get_thumbnail_url }}" alt="{{ photo.caption }}" title="{{ photo.caption }}" /></a> +{% endfor %} +</div> +<center><a href="{% url mysite.band.views.photos_index %}">Photo gallery index</a></center> + +{% endblock %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mysite/templates/band/photos.html Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,30 @@ +{% extends 'band/base.html' %} +{% block title %}The Madeira | Photo Galleries{% endblock %} +{% block custom_css %} +<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}css/thickbox.css" /> +{% endblock %} +{% block custom_js %} +<script type="text/javascript" src="{{ MEDIA_URL }}js/jquery.js"></script> +<script type="text/javascript" src="{{ MEDIA_URL }}js/thickbox.js"></script> +{% endblock %} +{% block content %} +<h1>Madeira Photo Galleries</h1> +{% if galleries %} + <ul> + {% for gallery in galleries %} + <li><a href="{% url mysite.band.views.photo_detail gallery.id %}">{{ gallery.title }}</a></li> + {% endfor %} + </ul> +{% else %} +No photo galleries available at this time. +{% endif %} +{% if randomPhotos %} + <div class="madeira-photo-list"> + <h2>Random Photos:</h2> + {% for photo in randomPhotos %} + <a href="{{ photo.image.url }}" class="thickbox" rel="madeira-gallery"> + <img src="{{ photo.get_thumbnail_url }}" alt="{{ photo.caption }}" title="{{ photo.caption }}" /></a> + {% endfor %} + </div> +{% endif %} +{% endblock %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mysite/templates/band/press.html Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,44 @@ +{% extends 'band/base.html' %} +{% load markup %} +{% block title %}The Madeira | Press{% endblock %} +{% block content %} +<h1>Madeira Press, Articles, & Reviews</h1> +{% if articles %} + <a name="Contents"> </a> + <h2>Contents</h2> + <ul> + {% for article in articles %} + <li><a href="#article_{{ article.id }}">{{ article.title }}</a></li> + {% endfor %} + </ul> + + {% for article in articles %} + <a name="article_{{ article.id }}"> </a> + <h2>{{ article.title }}</h2> + {% if article.markup_enabled %} + {{ article.text|textile }} + {% else %} + {{ article.text|safe|linebreaks }} + {% endif %} + <div class="article-source"> + {{ article.source|safe|linebreaks }} + </div> + {% if article.url %} + <a href="{{ article.url }}" target="_blank">Original article</a> + {% endif %} + {% if article.pdf and article.url %} + | + {% endif %} + {% if article.pdf %} + <a href="{{ article.get_pdf_url }}" target="_blank">Original article as PDF</a> + <a href="http://www.adobe.com/products/acrobat/readstep2.html"> + <img src="{{ MEDIA_URL }}images/get_adobe_reader.gif" alt="Adobe Reader" title="Get Adobe Reader" border="0" + align="middle" /></a> + {% endif %} + <p><a class="intLink" href="#Contents">Top</a></p> + {% endfor %} + +{% else %} +No articles at this time. +{% endif %} +{% endblock %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mysite/templates/band/press_detail.html Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,28 @@ +{% extends 'band/base.html' %} +{% load markup %} +{% block title %}The Madeira | Press{% endblock %} +{% block content %} +<h1>Madeira Press, Articles, & Reviews</h1> +<h2>{{ article.title }}</h2> +{% if article.markup_enabled %} + {{ article.text|textile }} +{% else %} + {{ article.text|safe|linebreaks }} +{% endif %} +<div class="article-source"> +{{ article.source|safe|linebreaks }} +</div> +{% if article.url %} +<a href="{{ article.url }}" target="_blank">Original article</a> +{% endif %} +{% if article.pdf and article.url %} +| +{% endif %} +{% if article.pdf %} +<a href="{{ article.get_pdf_url }}" target="_blank">Original article as PDF</a> +<a href="http://www.adobe.com/products/acrobat/readstep2.html"> + <img src="{{ MEDIA_URL }}images/get_adobe_reader.gif" alt="Adobe Reader" title="Get Adobe Reader" border="0" + align="middle" /></a> +{% endif %} +<p><a href="{% url mysite.band.views.press_index %}">Press index</a></p> +{% endblock %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mysite/templates/band/songs.html Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,20 @@ +{% extends 'band/base.html' %} +{% block title %}The Madeira | Songs{% endblock %} +{% block content %} +<h1>Madeira Songs</h1> +{% if mp3Sets %} + <p>Check out some Madeira MP3 downloads!</p> + {% for set in mp3Sets %} + <h2>{{ set.title }}</h2> + {{ set.text|safe|linebreaks }} + <ul> + {% for mp3 in set.mp3_set.all %} + <li><a href="{{ mp3.file.url }}">{{ mp3.title }}</a> + ({{ mp3.file.size|filesizeformat }}){% if mp3.desc %} - {{ mp3.desc }}{% endif %}</li> + {% endfor %} + </ul> + {% endfor %} +{% else %} +No downloads available at this time. +{% endif %} +{% endblock %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mysite/templates/band/video_detail.html Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,17 @@ +{% extends 'band/base.html' %} +{% block title %}The Madeira | Videos: {{ vidset.title }}{% endblock %} +{% block content %} +<h1>Madeira Videos: {{ vidset.title }}</h1> +{{ vidset.text|safe|linebreaks }} + +<div align="center"> +<table cellspacing="3" cellpadding="2" border="0"> +{% for video in vidset.video_set.all %} + <tr><th>{{ video.title }}</th><td>{{ video.embed_code|safe }}</td></tr> +{% endfor %} +</table> +</div> +<br /> +<center><a href="{% url mysite.band.views.videos_index %}">Videos index</a></center> + +{% endblock %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mysite/templates/band/videos.html Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,14 @@ +{% extends 'band/base.html' %} +{% block title %}The Madeira | Videos{% endblock %} +{% block content %} +<h1>Madeira Videos</h1> +{% if vidsets %} + <ul> + {% for set in vidsets %} + <li><a href="{% url mysite.band.views.video_detail set.id %}">{{ set.title }}</a></li> + {% endfor %} + </ul> +{% else %} +No videos available at this time. +{% endif %} +{% endblock %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mysite/templates/flatpages/default.html Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,5 @@ +{% extends 'band/base.html' %} +{% block title %}The Madeira | {{ flatpage.title }}{% endblock %} +{% block content %} +{{ flatpage.content }} +{% endblock %}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mysite/urls.py Mon Apr 06 03:10:59 2009 +0000 @@ -0,0 +1,16 @@ +from django.conf.urls.defaults import * +from django.contrib import admin +from mysite import settings + +admin.autodiscover() + +urlpatterns = patterns('', + (r'^', include('mysite.band.urls')), + (r'^admin/', include(admin.site.urls)), + (r'^photologue/', include('mysite.photologue.urls')), +) + +if settings.DEBUG: + urlpatterns += patterns('', + (r'^static/(?P<path>.*)$', 'django.views.static.serve', {'document_root': settings.MEDIA_ROOT}), + )