From 1b06243dabb7d88a2a89c0f37bd6fa69272a5bfa Mon Sep 17 00:00:00 2001 From: substantialnoninfringinguser Date: Wed, 13 May 2009 06:22:07 +0100 Subject: [PATCH 001/449] [svn] Improve registration system. Add jquery fade effects and quick search. Copied from http://cucc@cucc.survex.com/svn/trunk/expoweb/troggle/, rev. 8334 by cucc @ 5/10/2009 5:23 AM --- expo/views_caves.py | 4 +- media/css/main3.css | 20 +- media/css/nav.css | 13 +- media/js/base.js | 106 +++--- media/js/jquery.quicksearch.js | 328 ++++++++++++++++++ media/surveyHover.gif | Bin 0 -> 39482 bytes registration/views.py | 12 +- templates/base.html | 79 +---- templates/cave.html | 2 +- templates/caveindex.html | 8 +- templates/frontpage.html | 6 +- templates/personindex.html | 5 +- templates/registration/activate.html | 7 +- templates/registration/activation_email.txt | 4 +- .../registration/registration_complete.html | 8 +- templates/survey.html | 2 - 16 files changed, 466 insertions(+), 138 deletions(-) create mode 100644 media/js/jquery.quicksearch.js create mode 100644 media/surveyHover.gif diff --git a/expo/views_caves.py b/expo/views_caves.py index 02994b6..1e11602 100644 --- a/expo/views_caves.py +++ b/expo/views_caves.py @@ -77,12 +77,12 @@ def caveSearch(request): def surveyindex(request): surveys=Survey.objects.all() - expeditions=Expedition.objects.all() + expeditions=Expedition.objects.order_by("-year") return render_response(request,'survey.html',locals()) def survey(request,year,wallet_number): surveys=Survey.objects.all() - expeditions=Expedition.objects.all() + expeditions=Expedition.objects.order_by("-year") current_expedition=Expedition.objects.filter(year=year)[0] if wallet_number!='': diff --git a/media/css/main3.css b/media/css/main3.css index 467de9d..4dbac32 100644 --- a/media/css/main3.css +++ b/media/css/main3.css @@ -16,7 +16,7 @@ table, caption, tbody, tfoot, thead, tr, th, td } html, body { - height: 100% + height: 100%; } @@ -207,10 +207,6 @@ td { } -#nav { - -} - .redtext{ color:#F00; } @@ -292,7 +288,9 @@ div#content { margin-top: 50px; margin-left: 120px; margin-right: 120px; - padding: 5em; + padding-top: 10px; + padding-left: 5em; + padding-right: 5em; background:#CCC; } @@ -361,6 +359,16 @@ h1 { left:auto; } +#surveyHover { + width:auto; + right:auto; + left:auto; +} + #col1 { width:60% +} + +#quicksearch { + margin-left:40px; } \ No newline at end of file diff --git a/media/css/nav.css b/media/css/nav.css index 4fc7b3d..1c97c51 100644 --- a/media/css/nav.css +++ b/media/css/nav.css @@ -1,9 +1,12 @@ div#nav { + position:fixed; width: 12em; - float: left; background: rgb(153, 153, 153); - padding: 15px 0; - height:100% + margin-top: 0px; + margin-left: 120px; + border-top: thin black solid; } - -div#content { margin-left:13em } \ No newline at end of file + +div#content { + padding-left:240px; +} \ No newline at end of file diff --git a/media/js/base.js b/media/js/base.js index 17a664e..0dc562b 100644 --- a/media/js/base.js +++ b/media/js/base.js @@ -1,46 +1,74 @@ - function showDiv(collapsed,expanded){ - document.getElementById(collapsed).style.display = 'none'; - document.getElementById(expanded).style.display = 'block'; - } - - function hideDiv(collapsed,expanded){ - document.getElementById(collapsed).style.display = 'block'; - document.getElementById(expanded).style.display = 'none'; - } +$(document).ready(function() { - function makeDivTransparent(div){ - document.getElementById(div).style.backgroundColor = 'transparent'; - } +$('.searchable li').quicksearch({ + position: 'before', + attached: 'ul.searchable', + labelText: '', + loaderText: '', + delay: 100 +}) + +$('table.searchable tr').quicksearch({ + position: 'before', + attached: 'table.searchable:first', +}); + +$(".toggleEyeCandy").click(function () { + $(".leftMargin,.rightMargin").toggle("fade"); + $(".toggleEyeCandy").toggle(); + }); + +$(".nav").css('opacity','7') +$(".footer").hide(); +$(".fadeIn").hide(); +setTimeout("$('.leftMargin.fadeIn').fadeIn(3000);",1000); +setTimeout("$('.rightMargin.fadeIn').fadeIn(3000);",2000); + + +/*$("#footerLinks").hover( + function() {$(".footer").fadeIn("slow")}, + function() {$(".footer").fadeOut("slow")} +);*/ + +function linkHover(hoverLink,image){ + +$(hoverLink).hover( + function() { + $(image).fadeIn("slow"); + $(hoverLink).css("background","gray"); + }, + function() { + $(image).fadeOut("slow"); + $(hoverLink).css("background","black"); + } +); -hex=0 // Initial color value. -leftPos=25 -year=1976 -currentDate= new Date() -currentYear = currentDate.getFullYear() -function fadeText(){ -if(hex<153) { //If color is not black yet - hex=hex+10; // increase color darkness - leftPos-=1; - document.getElementById("expoHeader").style.color="rgb("+0+","+hex+","+0+")"; -// document.getElementById("expoFinalDate").style.color="rgb("+0+","+hex+","+0+")"; - document.getElementById("expoHeader").style.left=leftPos; - setTimeout("fadeText()",50) - setTimeout("countUpYear()",1000) -} -else { - hex=0; - leftPos=25; -} +}; + +linkHover("#expoWebsiteLink","#richardBanner"); +linkHover("#cuccLink","#timeMachine"); +linkHover("#surveyBinderLink","#surveyHover"); +linkHover("#troggle","#timeMachine"); + + +}); + +function contentHeight(){ +setMaxHeight($(".rightMargin,#content,.leftMargin,#col2"),$("#content")); +}; + +function setMaxHeight(group, target) { + tallest = 0; + group.each(function() { + thisHeight = $(this).height(); + if(thisHeight > tallest) { + tallest = thisHeight; + } + }); + target.height(tallest); } -function countUpYear(){ - if (year" - setTimeout("countUpYear()",1000) - } -} \ No newline at end of file + diff --git a/media/js/jquery.quicksearch.js b/media/js/jquery.quicksearch.js new file mode 100644 index 0000000..fec6967 --- /dev/null +++ b/media/js/jquery.quicksearch.js @@ -0,0 +1,328 @@ +jQuery(function ($) { + $.fn.quicksearch = function (opt) { + + function is_empty(i) + { + return (i === null || i === undefined || i === false) ? true: false; + } + + function strip_html(input) + { + var regexp = new RegExp(/\<[^\<]+\>/g); + var output = input.replace(regexp, ""); + output = $.trim(output.toLowerCase().replace(/\n/, '').replace(/\s{2,}/, ' ')); + return output; + } + + function get_key() + { + var input = strip_html($('input[rel="' + options.randomElement + '"]').val()); + + if (input.indexOf(' ') === -1) + { + return input; + } + else + { + return input.split(" "); + } + } + + function test_key(k, value, type) + { + if (type === "string") + { + return test_key_string(k, value); + } + else + { + return test_key_arr(k, value); + } + } + + function test_key_string(k, value) + { + return (value.indexOf(k) > -1); + } + + function test_key_arr(k, value) + { + for (var i = 0; i < k.length; i++) { + var test = value.indexOf(k[i]); + if (test === -1) { + return false; + } + } + return true; + } + + function select_element(el) + { + if (options.hideElement === "grandparent") + { + return $(el).parent().parent(); + } + else if (options.hideElement === "parent") + { + return $(el).parent(); + } + else + { + return $(el); + } + } + + function stripe(el) + { + if (doStripe) + { + var i = 0; + select_element(el).filter(':visible').each(function () { + + for (var j = 0; j < stripeRowLength; j++) + { + if (i === j) + { + $(this).addClass(options.stripeRowClass[i]); + + } + else + { + $(this).removeClass(options.stripeRowClass[j]); + } + } + i = (i + 1) % stripeRowLength; + }); + } + } + + function fix_widths(el) + { + $(el).find('td').each(function () { + $(this).attr('width', parseInt($(this).css('width'))); + }); + } + + function loader(o) { + if (options.loaderId) + { + var l = $('input[rel="' + options.randomElement + '"]').parent().find('.loader'); + if (o === 'hide') + { + l.hide(); + } + else + { + l.show(); + } + } + } + + function place_form() { + var formPosition = options.position; + var formAttached = options.attached; + + if (formPosition === 'before') { + $(formAttached).before(make_form()); + } else if (formPosition === 'prepend') { + $(formAttached).prepend(make_form()); + } else if (formPosition === 'append') { + $(formAttached).append(make_form()); + } else { + $(formAttached).after(make_form()); + } + } + + function make_form_label() + { + if (!is_empty(options.labelText)) { + return ' '; + } + return ''; + } + + function make_form_input() + { + var val = (!is_empty(options.inputText)) ? options.inputText : "" + return ' '; + } + + function make_form_loader() + { + if (!is_empty(options.loaderImg)) { + return 'Loading'; + } else { + return '' + options.loaderText + ''; + } + } + + function make_form() + { + var f = (!options.isFieldset) ? 'form' : 'fieldset'; + return '<' + f + ' action="#" ' + 'id="'+ options.formId + '" ' + 'class="quicksearch">' + + make_form_label() + make_form_input() + make_form_loader() + + ''; + } + + function focus_on_load() + { + $('input[rel="' + options.randomElement + '"]').get(0).focus(); + } + + function toggle_text() { + $('input[rel="' + options.randomElement + '"]').focus(function () { + if ($(this).val() === options.inputText) { + $(this).val(''); + } + }); + $('input[rel="' + options.randomElement + '"]').blur(function () { + if ($(this).val() === "") { + $(this).val(options.inputText); + } + }); + } + + function get_cache(el) + { + return $(el).map(function(){ + return strip_html(this.innerHTML); + }); + } + + function init() + { + place_form(); + if (options.fixWidths) fix_widths(el); + if (options.focusOnLoad) focus_on_load(); + if (options.inputText != "" && options.inputText != null) toggle_text(); + + cache = get_cache(el); + + stripe(el); + loader('hide'); + } + + function qs() + { + clearTimeout(timeout); + timeout = setTimeout(function () { + + loader('show'); + + setTimeout(function () { + options.onBefore(); + + var k = get_key(); + var k_type = (typeof k); + var i = 0; + + k = options.filter(k); + + if (k != "") + { + if (typeof score[k] === "undefined") + { + score[k] = new Array(); + cache.each(function (i) { + if (test_key(k, cache[i], k_type)) + { + score[k][i] = true; + } + }); + } + + if (score[k].length === 0) + { + select_element(el).hide(); + } + else + { + $(el).each(function (i) { + if (score[k][i]) + { + select_element(this).show(); + } + else + { + select_element(this).hide(); + } + }); + + } + } + else + { + select_element(el).show(); + } + + stripe(el); + }, options.delay/2); + + setTimeout( function () { + loader('hide'); + }, options.delay/2); + + options.onAfter(); + + }, options.delay/2); + } + + var options = $.extend({ + position: 'prepend', + attached: 'body', + formId: 'quicksearch', + labelText: 'Quick Search', + labelClass: 'qs_label', + inputText: null, + inputClass: 'qs_input', + loaderId: 'loader', + loaderClass: 'loader', + loaderImg: null, + loaderText: 'Loading...', + stripeRowClass: null, + hideElement: null, + delay: 500, + focusOnLoad: false, + onBefore: function () { }, + onAfter: function () { }, + filter: function (i) { + return i; + }, + randomElement: 'qs' + Math.floor(Math.random() * 1000000), + isFieldset: false, + fixWidths: false + }, opt); + + var timeout; + var score = {}; + var stripeRowLength = (!is_empty(options.stripeRowClass)) ? options.stripeRowClass.length : 0; + var doStripe = (stripeRowLength > 0) ? true : false; + var el = this; + var cache; + var selector = $(this).selector; + + $.fn.extend({ + reset_cache: function () { + el = $(selector); + cache = get_cache(el); + } + }); + + init(); + + $('input[rel="' + options.randomElement + '"]').keydown(function (e) { + var keycode = e.keyCode; + if (!(keycode === 9 || keycode === 13 || keycode === 16 || keycode === 17 || keycode === 18 || keycode === 38 || keycode === 40 || keycode === 224)) + { + qs(); + } + }); + + $('form.quicksearch, fieldset.quicksearch').submit( function () { return false; }); + + return this; + }; +}); \ No newline at end of file diff --git a/media/surveyHover.gif b/media/surveyHover.gif new file mode 100644 index 0000000000000000000000000000000000000000..353872803a321b0656f2a68d7972e65e707b9f14 GIT binary patch literal 39482 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 -{% block title %}THE TITLE{% endblock %} +{% block title %}Troggle{% endblock %} + + - - - - {% block head %}{% endblock %} @@ -71,8 +17,11 @@ $('#q').liveUpdate('posts').focus();

CUCC Expeditions to Austria: 1976 - 2009

@@ -35,6 +34,9 @@ Everyone is gearing up for the 2009 expedition; please see the link below for th Troggle is still under development, and there is much work to do.

+{% endblock content %} + +{% block margins %} -{% endblock content %} \ No newline at end of file +{% endblock margins %} \ No newline at end of file diff --git a/templates/personindex.html b/templates/personindex.html index e684ad8..ea97f43 100644 --- a/templates/personindex.html +++ b/templates/personindex.html @@ -6,7 +6,7 @@ {% block content %}

Notable expoers

- +
{% for person in notablepersons %} @@ -20,10 +20,11 @@

All expoers

-
PersonFirstLastNotability
+
{% for persons in personss %} + + + + + + +
+ {% for person in persons %} diff --git a/templates/registration/activate.html b/templates/registration/activate.html index eb8ec61..ca50e6c 100644 --- a/templates/registration/activate.html +++ b/templates/registration/activate.html @@ -10,11 +10,16 @@ New troggle account registered {% block content %} +{% if account %}

-Hello, {{user}}! Your account is now activated. You've also been logged in automatically for your convenience. Use the links in the upper right to control this in the future. +Hello, {{ account }}! Your account is now activated. Now you can log in with the password you chose. Use the links in the upper right to control this in the future.

If you have been on the expedition in the past, you already have a profile in the system; click here to find it and link it to your account. Otherwise, please create yourself a new profile.

+{% endif %} + +The activation key you entered has already been used or was invalid. + {% endblock %} \ No newline at end of file diff --git a/templates/registration/activation_email.txt b/templates/registration/activation_email.txt index 163e313..20aad5f 100644 --- a/templates/registration/activation_email.txt +++ b/templates/registration/activation_email.txt @@ -1,10 +1,10 @@ -Hello, +Hello {{ form.user }}, Glad you're joining the CUCC EXPO team! Please go to {{ site }}{% url registration_activate activation_key %} -to activate your account. Do this within {{ expiration_days }}, or else you'll have to sign up again. +to activate your account. Do this within {{ expiration_days }} days, or else you'll have to sign up again. Yours, The magical troggle \ No newline at end of file diff --git a/templates/registration/registration_complete.html b/templates/registration/registration_complete.html index 552fa04..78684fe 100644 --- a/templates/registration/registration_complete.html +++ b/templates/registration/registration_complete.html @@ -1,13 +1,13 @@ {% extends "base.html" %} {% block title %} -registration_complete.html | {{ block.super }} +{{ block.super }}: registration complete {% endblock %} -{% block header %} -

registration_complete.html

+{% block contentheader %} +

Registration Complete

{% endblock %} {% block content %} -Thank you for signing up. An email with the activation code has been sent to your inbox. +

Thank you for signing up. An email with the activation code has been sent to your inbox.

{% endblock %} \ No newline at end of file diff --git a/templates/survey.html b/templates/survey.html index 6fce15f..fcdf65e 100644 --- a/templates/survey.html +++ b/templates/survey.html @@ -75,7 +75,6 @@ {% block nav %} - - {% endblock %} {% block content %} From 21204f1bc87f3f9d1f3972abcc5cf41bcb8d888b Mon Sep 17 00:00:00 2001 From: substantialnoninfringinguser Date: Wed, 13 May 2009 06:22:53 +0100 Subject: [PATCH 002/449] [svn] Fixes to deal with reorganization of expo surveys repository. Now that survey scans and Surveys.csv are in different directories, we have two settings variables, settings.SURVEYS for the root of the survey repo, and settings.SURVEY_SCANS for the surveyscans directory. Fixed tab / indent muck in surveys parser. Commented out some "file abstraction" stuff for the time being. Copied from http://cucc@cucc.survex.com/svn/trunk/expoweb/troggle/, rev. 8335 by cucc @ 5/10/2009 7:26 AM --- expo/admin.py | 1 + expo/models.py | 2 +- parsers/surveys.py | 95 +++++++++++++++++++++++++--------------------- urls.py | 2 +- 4 files changed, 54 insertions(+), 46 deletions(-) diff --git a/expo/admin.py b/expo/admin.py index 4416dda..f2717d9 100644 --- a/expo/admin.py +++ b/expo/admin.py @@ -24,6 +24,7 @@ class ScannedImageInline(admin.TabularInline): class SurveyAdmin(TroggleModelAdmin): inlines = (ScannedImageInline,) + search_fields = ('expedition__year','wallet_number') class QMInline(admin.TabularInline): model=QM diff --git a/expo/models.py b/expo/models.py index 639e8b5..b7ad2d7 100644 --- a/expo/models.py +++ b/expo/models.py @@ -538,7 +538,7 @@ class Photo(TroggleModel): def __str__(self): return self.caption -scansFileStorage = FileSystemStorage(location=settings.SURVEYS, base_url=settings.SURVEYS_URL) +scansFileStorage = FileSystemStorage(location=settings.SURVEY_SCANS, base_url=settings.SURVEYS_URL) def get_scan_path(instance, filename): year=instance.survey.expedition.year print "WN: ", type(instance.survey.wallet_number), instance.survey.wallet_number diff --git a/parsers/surveys.py b/parsers/surveys.py index ca2f153..c7c666d 100644 --- a/parsers/surveys.py +++ b/parsers/surveys.py @@ -5,7 +5,7 @@ sys.path.append('C:\\Expo\\expoweb') from troggle import * os.environ['DJANGO_SETTINGS_MODULE']='troggle.settings' import troggle.settings as settings -import troggle.expo.models as models +from troggle.expo.models import * #import settings #import expo.models as models @@ -13,40 +13,41 @@ import csv import re import datetime -try: - surveytab = open(os.path.join(settings.SURVEYS, "Surveys.csv")) -except IOError: - import cStringIO, urllib - surveytab = cStringIO.StringIO(urllib.urlopen(settings.SURVEYS + "download/Surveys.csv").read()) -dialect=csv.Sniffer().sniff(surveytab.read()) -surveytab.seek(0,0) -surveyreader = csv.reader(surveytab,dialect=dialect) -headers = surveyreader.next() -header = dict(zip(headers, range(len(headers)))) #set up a dictionary where the indexes are header names and the values are column numbers +def readSurveysFromCSV(): + try: + surveytab = open(os.path.join(settings.SURVEYS, "Surveys.csv")) + except IOError: + import cStringIO, urllib + surveytab = cStringIO.StringIO(urllib.urlopen(settings.SURVEYS + "download/Surveys.csv").read()) + dialect=csv.Sniffer().sniff(surveytab.read()) + surveytab.seek(0,0) + surveyreader = csv.reader(surveytab,dialect=dialect) + headers = surveyreader.next() + header = dict(zip(headers, range(len(headers)))) #set up a dictionary where the indexes are header names and the values are column numbers -# test if the expeditions have been added yet -if len(models.Expedition.objects.all())==0: - print "There are no expeditions in the database. Please run the logbook parser." - sys.exit() -models.ScannedImage.objects.all().delete() -models.Survey.objects.all().delete() -for survey in surveyreader: - walletNumberLetter = re.match(r'(?P\d*)(?P[a-zA-Z]*)',survey[header['Survey Number']]) #I hate this, but some surveys have a letter eg 2000#34a. This line deals with that. -# print walletNumberLetter.groups() + # test if the expeditions have been added yet + if Expedition.objects.count()==0: + print "There are no expeditions in the database. Please run the logbook parser." + sys.exit() + ScannedImage.objects.all().delete() + Survey.objects.all().delete() + for survey in surveyreader: + walletNumberLetter = re.match(r'(?P\d*)(?P[a-zA-Z]*)',survey[header['Survey Number']]) #I hate this, but some surveys have a letter eg 2000#34a. This line deals with that. + # print walletNumberLetter.groups() - surveyobj = models.Survey( - expedition = models.Expedition.objects.filter(year=survey[header['Year']])[0], - wallet_number = walletNumberLetter.group('number'), + surveyobj = Survey( + expedition = Expedition.objects.filter(year=survey[header['Year']])[0], + wallet_number = walletNumberLetter.group('number'), - comments = survey[header['Comments']], - location = survey[header['Location']] - ) - surveyobj.wallet_letter = walletNumberLetter.group('letter') - if survey[header['Finished']]=='Yes': - #try and find the sketch_scan - pass - surveyobj.save() - print "added survey " + survey[header['Year']] + "#" + surveyobj.wallet_number + "\r", + comments = survey[header['Comments']], + location = survey[header['Location']] + ) + surveyobj.wallet_letter = walletNumberLetter.group('letter') + if survey[header['Finished']]=='Yes': + #try and find the sketch_scan + pass + surveyobj.save() + print "added survey " + survey[header['Year']] + "#" + surveyobj.wallet_number + "\r", def listdir(*directories): try: @@ -59,11 +60,15 @@ def listdir(*directories): # add survey scans def parseSurveyScans(year): - yearFileList = listdir(year.year) +# yearFileList = listdir(year.year) + yearPath=os.path.join(settings.SURVEY_SCANS, year.year) + yearFileList=os.listdir(yearPath) + print yearFileList for surveyFolder in yearFileList: try: surveyNumber=re.match(r'\d\d\d\d#0*(\d+)',surveyFolder).groups() - scanList = listdir(year.year, surveyFolder) +# scanList = listdir(year.year, surveyFolder) + scanList=os.listdir(os.path.join(yearPath,surveyFolder)) except AttributeError: print surveyFolder + " ignored", continue @@ -73,10 +78,10 @@ def parseSurveyScans(year): scanChopped=re.match(r'(?i).*(notes|elev|plan|elevation|extend)(\d*)\.(png|jpg|jpeg)',scan).groups() scanType,scanNumber,scanFormat=scanChopped except AttributeError: - print "Adding scans: " + scan + " ignored \r", + print scan + " ignored \r", continue - if scanType == 'elev' or scanType == 'extend': - scanType = 'elevation' + if scanType == 'elev' or scanType == 'extend': + scanType = 'elevation' if scanNumber=='': scanNumber=1 @@ -84,11 +89,11 @@ def parseSurveyScans(year): if type(surveyNumber)==types.TupleType: surveyNumber=surveyNumber[0] try: - survey=models.Survey.objects.get_or_create(wallet_number=surveyNumber, expedition=year)[0] - except models.Survey.MultipleObjectsReturned: - survey=models.Survey.objects.filter(wallet_number=surveyNumber, expedition=year)[0] + survey=Survey.objects.get_or_create(wallet_number=surveyNumber, expedition=year)[0] + except Survey.MultipleObjectsReturned: + survey=Survey.objects.filter(wallet_number=surveyNumber, expedition=year)[0] - scanObj = models.ScannedImage( + scanObj = ScannedImage( file=os.path.join(year.year, surveyFolder, scan), contents=scanType, number_in_wallet=scanNumber, @@ -96,6 +101,8 @@ def parseSurveyScans(year): ) #print "Added scanned image at " + str(scanObj) scanObj.save() - -for year in models.Expedition.objects.filter(year__gte=2000): #expos since 2000, because paths and filenames were nonstandard before then - parseSurveyScans(year) + +def parseSurveys(): + readSurveysFromCSV() + for year in Expedition.objects.filter(year__gte=2000): #expos since 2000, because paths and filenames were nonstandard before then + parseSurveyScans(year) diff --git a/urls.py b/urls.py index 9494694..4d39f84 100644 --- a/urls.py +++ b/urls.py @@ -76,7 +76,7 @@ urlpatterns = patterns('', #(r'^survey_files/upload/(?P.*)$', view_surveys.upload), (r'^survey_scans/(?P.*)$', 'django.views.static.serve', - {'document_root': settings.SURVEYS, 'show_indexes':True}), + {'document_root': settings.SURVEY_SCANS, 'show_indexes':True}), (r'^photos/(?P.*)$', 'django.views.static.serve', {'document_root': settings.PHOTOS_ROOT, 'show_indexes':True}), From 8c68a8a0d749327eed5431a04f03f5ec4472969a Mon Sep 17 00:00:00 2001 From: substantialnoninfringinguser Date: Wed, 13 May 2009 06:23:57 +0100 Subject: [PATCH 003/449] [svn] Dynamic thumbnail generation for photos and survey scans using imagekit, further improving registration system, other misc. Copied from http://cucc@cucc.survex.com/svn/trunk/expoweb/troggle/, rev. 8336 by cucc @ 5/10/2009 11:05 PM --- expo/imagekit_specs.py | 23 +++++++++++++++++ expo/models.py | 27 +++++++++++++++++--- localsettingswindows.py | 50 +++++++++++++++++++++++++++++-------- media/css/main3.css | 4 +-- parsers/surveys.py | 44 ++++++++++++++++++++++++-------- settings.py | 7 +++--- templates/base.html | 2 +- templates/controlPanel.html | 2 +- templates/person.html | 4 +-- templates/survey.html | 8 +++--- urls.py | 2 ++ 11 files changed, 134 insertions(+), 39 deletions(-) create mode 100644 expo/imagekit_specs.py diff --git a/expo/imagekit_specs.py b/expo/imagekit_specs.py new file mode 100644 index 0000000..243cb9f --- /dev/null +++ b/expo/imagekit_specs.py @@ -0,0 +1,23 @@ +from imagekit.specs import ImageSpec +from imagekit import processors + +class ResizeThumb(processors.Resize): + width = 100 + height = 75 + crop = True + +class ResizeDisplay(processors.Resize): + width = 600 + +class EnhanceThumb(processors.Adjustment): + contrast = 1.2 + sharpness = 1.1 + +class Thumbnail(ImageSpec): + access_as = 'thumbnail_image' + pre_cache = True + processors = [ResizeThumb, EnhanceThumb] + +class Display(ImageSpec): + increment_count = True + processors = [ResizeDisplay] diff --git a/expo/models.py b/expo/models.py index b7ad2d7..a3953d9 100644 --- a/expo/models.py +++ b/expo/models.py @@ -11,6 +11,7 @@ from django.conf import settings import datetime from decimal import Decimal, getcontext from django.core.urlresolvers import reverse +from imagekit.models import ImageModel getcontext().prec=2 #use 2 significant figures for decimal calculations from models_survex import * @@ -25,6 +26,15 @@ class TroggleModel(models.Model): class Meta: abstract = True +class TroggleImageModel(ImageModel): + new_since_parsing = models.BooleanField(default=False, editable=False) + + def get_admin_url(self): + return settings.URL_ROOT + "/admin/expo/" + self._meta.object_name.lower() + "/" + str(self.pk) + + class Meta: + abstract = True + class Expedition(TroggleModel): year = models.CharField(max_length=20, unique=True) name = models.CharField(max_length=100) @@ -517,7 +527,7 @@ class QM(TroggleModel): return res photoFileStorage = FileSystemStorage(location=settings.PHOTOS_ROOT, base_url=settings.PHOTOS_URL) -class Photo(TroggleModel): +class Photo(TroggleImageModel): caption = models.CharField(max_length=1000,blank=True,null=True) contains_logbookentry = models.ForeignKey(LogbookEntry,blank=True,null=True) contains_person = models.ManyToManyField(Person,blank=True,null=True) @@ -527,10 +537,14 @@ class Photo(TroggleModel): contains_entrance = models.ForeignKey(Entrance, related_name="photo_file",blank=True,null=True) nearest_survey_point = models.ForeignKey(SurveyStation,blank=True,null=True) nearest_QM = models.ForeignKey(QM,blank=True,null=True) - - lon_utm = models.FloatField(blank=True,null=True) lat_utm = models.FloatField(blank=True,null=True) + + class IKOptions: + spec_module = 'expo.imagekit_specs' + cache_dir = 'thumbs' + image_field = 'file' + #content_type = models.ForeignKey(ContentType) #object_id = models.PositiveIntegerField() #location = generic.GenericForeignKey('content_type', 'object_id') @@ -545,7 +559,7 @@ def get_scan_path(instance, filename): number="%02d" % instance.survey.wallet_number + str(instance.survey.wallet_letter) #using %02d string formatting because convention was 2009#01 return os.path.join('./',year,year+r'#'+number,instance.contents+str(instance.number_in_wallet)+r'.jpg') -class ScannedImage(TroggleModel): +class ScannedImage(TroggleImageModel): file = models.ImageField(storage=scansFileStorage, upload_to=get_scan_path) scanned_by = models.ForeignKey(Person,blank=True, null=True) scanned_on = models.DateField(null=True) @@ -554,6 +568,11 @@ class ScannedImage(TroggleModel): number_in_wallet = models.IntegerField(null=True) lon_utm = models.FloatField(blank=True,null=True) lat_utm = models.FloatField(blank=True,null=True) + + class IKOptions: + spec_module = 'expo.imagekit_specs' + cache_dir = 'thumbs' + image_field = 'file' #content_type = models.ForeignKey(ContentType) #object_id = models.PositiveIntegerField() #location = generic.GenericForeignKey('content_type', 'object_id') diff --git a/localsettingswindows.py b/localsettingswindows.py index a264e8d..53225b2 100644 --- a/localsettingswindows.py +++ b/localsettingswindows.py @@ -1,20 +1,48 @@ -DATABASE_ENGINE = 'mysql' # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'. -DATABASE_NAME = 'troggle' # Or path to database file if using sqlite3. -DATABASE_USER = 'troggle' # Not used with sqlite3. -DATABASE_PASSWORD = 'troggle' # Not used with sqlite3. -DATABASE_HOST = 'localhost' # Set to empty string for localhost. Not used with sqlite3. -DATABASE_PORT = '3306' # Set to empty string for default. Not used with sqlite3. +DATABASE_ENGINE = '' # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'. +DATABASE_NAME = '' # Or path to database file if using sqlite3. +DATABASE_USER = '' # Not used with sqlite3. +DATABASE_PASSWORD = '' # Not used with sqlite3. +DATABASE_HOST = '' # Set to empty string for localhost. Not used with sqlite3. +DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3. -SURVEX_DATA = 'c:\\loser\\' -CAVERN = '"C:\\Program Files\\Survex\\cavern"' -EXPOWEB = 'C:\\expoweb\\' +SURVEX_DATA = 'c:\\Expo\\loser\\' +CAVERN = 'cavern' +EXPOWEB = 'C:\\Expo\\expoweb\\' +SURVEYS = 'E:\\surveys\\' +SURVEY_SCANS = 'E:\\surveys\\surveyscans' + +LOGFILE = open(EXPOWEB+'troggle\\parsing_log.txt',"a+b") + +PHOTOS = 'C:\\Expo\\expoweb\\photos' + +URL_ROOT = 'http://127.0.0.1:8000' PYTHON_PATH = 'C:\\expoweb\\troggle\\' -URL_ROOT = "/" +MEDIA_ROOT = 'C:/Expo/expoweb/troggle/media/' + +#FILES = "http://framos.lawoftheland.co.uk/troggle/survey_files/" + +EMAIL_HOST = "smtp.gmail.com" + +EMAIL_HOST_USER = "cuccexpo@gmail.com" + +EMAIL_HOST_PASSWORD = "" + +EMAIL_PORT=587 + +EMAIL_USE_TLS = True + +# 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/" + + + TEMPLATE_DIRS = ( - "c:/expoweb/troggle/templates", + "C:/Expo/expoweb/troggle/templates", + # 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. diff --git a/media/css/main3.css b/media/css/main3.css index 4dbac32..d875da9 100644 --- a/media/css/main3.css +++ b/media/css/main3.css @@ -180,7 +180,7 @@ table.trad td, table.trad th { margin: 0pt; border: 1px solid #aaa; /* You are not expected to understand this. It is necessary. */ -/* The above is the most fucktarded comment I have ever read. AC, 24 APR 2009 */ +/* The above is the most fucktarded comment I have ever read :-) AC, 24 APR 2009 */ table.centre { margin-left: auto; margin-right: auto; } table.centre td { text-align: left; } @@ -235,7 +235,7 @@ a.redtext:link { } div.figure { width: 20%; - border: thin silver solid; + border: thin white solid; margin: 0.5em; padding: 0.5em; display: inline; diff --git a/parsers/surveys.py b/parsers/surveys.py index c7c666d..e2a502d 100644 --- a/parsers/surveys.py +++ b/parsers/surveys.py @@ -1,19 +1,19 @@ import sys import os import types -sys.path.append('C:\\Expo\\expoweb') -from troggle import * -os.environ['DJANGO_SETTINGS_MODULE']='troggle.settings' +#sys.path.append('C:\\Expo\\expoweb') +#from troggle import * +#os.environ['DJANGO_SETTINGS_MODULE']='troggle.settings' import troggle.settings as settings from troggle.expo.models import * - +from PIL import Image #import settings #import expo.models as models import csv import re import datetime -def readSurveysFromCSV(): +def readSurveysFromCSV(logfile=None): try: surveytab = open(os.path.join(settings.SURVEYS, "Surveys.csv")) except IOError: @@ -29,8 +29,18 @@ def readSurveysFromCSV(): if Expedition.objects.count()==0: print "There are no expeditions in the database. Please run the logbook parser." sys.exit() + + if logfile: + logfile.write("Deleting all scanned images") ScannedImage.objects.all().delete() + + if logfile: + logfile.write("Deleting all survey objects") Survey.objects.all().delete() + + if logfile: + logfile.write("Beginning to import surveys from "+str(os.path.join(settings.SURVEYS, "Surveys.csv"))+"\n"+"-"*60+"\n") + for survey in surveyreader: walletNumberLetter = re.match(r'(?P\d*)(?P[a-zA-Z]*)',survey[header['Survey Number']]) #I hate this, but some surveys have a letter eg 2000#34a. This line deals with that. # print walletNumberLetter.groups() @@ -47,7 +57,9 @@ def readSurveysFromCSV(): #try and find the sketch_scan pass surveyobj.save() - print "added survey " + survey[header['Year']] + "#" + surveyobj.wallet_number + "\r", + + if logfile: + logfile.write("added survey " + survey[header['Year']] + "#" + surveyobj.wallet_number + "\r") def listdir(*directories): try: @@ -59,7 +71,7 @@ def listdir(*directories): return [folder.rstrip(r"/") for folder in folders] # add survey scans -def parseSurveyScans(year): +def parseSurveyScans(year, logfile=None): # yearFileList = listdir(year.year) yearPath=os.path.join(settings.SURVEY_SCANS, year.year) yearFileList=os.listdir(yearPath) @@ -92,17 +104,27 @@ def parseSurveyScans(year): survey=Survey.objects.get_or_create(wallet_number=surveyNumber, expedition=year)[0] except Survey.MultipleObjectsReturned: survey=Survey.objects.filter(wallet_number=surveyNumber, expedition=year)[0] - + file=os.path.join(year.year, surveyFolder, scan) scanObj = ScannedImage( - file=os.path.join(year.year, surveyFolder, scan), + file=file, contents=scanType, number_in_wallet=scanNumber, - survey=survey + survey=survey, + new_since_parsing=False, ) #print "Added scanned image at " + str(scanObj) + if scanFormat=="png": + if isInterlacedPNG(os.path.join(settings.SURVEY_SCANS,file)): + print file + "is an interlaced PNG. No can do." + continue scanObj.save() -def parseSurveys(): +def parseSurveys(logfile=None): readSurveysFromCSV() for year in Expedition.objects.filter(year__gte=2000): #expos since 2000, because paths and filenames were nonstandard before then parseSurveyScans(year) + +def isInterlacedPNG(filePath): #We need to check for interlaced PNGs because the thumbnail engine can't handle them (uses PIL) + file=Image.open(filePath) + return file.info['interlace'] + \ No newline at end of file diff --git a/settings.py b/settings.py index 11b1f2f..1e578db 100644 --- a/settings.py +++ b/settings.py @@ -78,10 +78,11 @@ INSTALLED_APPS = ( 'django.contrib.sessions', 'django.contrib.sites', 'django.contrib.redirects', - #'photologue', + #'troggle.photologue', #'troggle.reversion', - #'django_evolution', + 'django_evolution', 'troggle.registration', 'troggle.profiles', - 'troggle.expo' + 'troggle.expo', + ) diff --git a/templates/base.html b/templates/base.html index c44212f..2a8e758 100644 --- a/templates/base.html +++ b/templates/base.html @@ -19,7 +19,7 @@ {% if user.username %} You are logged in as {{ user.username }} {% if user.person %}({{ user.person }}) - {% else %} sort your profile + {% else %} sort your profile {% endif %}. | Log out {% else %} Sign up | Log in {% endif %} {% endblock%} diff --git a/templates/controlPanel.html b/templates/controlPanel.html index 3fad8e6..d60a327 100644 --- a/templates/controlPanel.html +++ b/templates/controlPanel.html @@ -13,7 +13,7 @@
PersonFirstLast
- + diff --git a/templates/person.html b/templates/person.html index d28f82a..adc6aba 100644 --- a/templates/person.html +++ b/templates/person.html @@ -11,13 +11,13 @@ {% block content %} {% if person.blurb %} -{{person.blurb}} +{{person.blurb|safe}} {% endif %} {% for pic in person.photo_set.all %} {% if pic.is_mugshot %}
-

+

{{ pic.caption }}

diff --git a/templates/survey.html b/templates/survey.html index fcdf65e..a0395b6 100644 --- a/templates/survey.html +++ b/templates/survey.html @@ -179,11 +179,11 @@
[ There are no surveys in the database for this year. Put link in to add one. ]
{% endif %}
-
+

Scanned notes for {{ current_survey }}.

{% for noteItem in notes %}
-

+

File at: {{ noteItem.file.name }}
Scanned by: {{ noteItem.scanned_by }}
On: {{ noteItem.scanned_on }}
@@ -201,8 +201,8 @@

Scanned plan sketch files for {{ current_survey }}.

{% for sketchItem in planSketches %}
-

-

File at: {{ sketchItem.file.name }}
+

+

File at: {{ sketchItem.file.name }}
Scanned by: {{ sketchItem.scanned_by }}
On: {{ sketchItem.scanned_on }}

diff --git a/urls.py b/urls.py index 4d39f84..780fdb0 100644 --- a/urls.py +++ b/urls.py @@ -68,6 +68,8 @@ urlpatterns = patterns('', # (r'^personform/(.*)$', personForm), + (r'^photologue/', include('photologue.urls')), + (r'^site_media/(?P.*)$', 'django.views.static.serve', {'document_root': settings.MEDIA_ROOT, 'show_indexes': True}), From b5093905759f246176ca9f8992fb99cbb4aa9739 Mon Sep 17 00:00:00 2001 From: substantialnoninfringinguser Date: Wed, 13 May 2009 06:24:52 +0100 Subject: [PATCH 004/449] [svn] Switch from photologue to imagekit. Less bloat. Copied from http://cucc@cucc.survex.com/svn/trunk/expoweb/troggle/, rev. 8338 by cucc @ 5/11/2009 3:08 AM --- imagekit/__init__.py | 13 +++ imagekit/defaults.py | 21 ++++ imagekit/lib.py | 17 +++ imagekit/management/__init__.py | 1 + imagekit/management/commands/__init__.py | 1 + imagekit/management/commands/ikflush.py | 38 +++++++ imagekit/models.py | 136 +++++++++++++++++++++++ imagekit/options.py | 23 ++++ imagekit/processors.py | 134 ++++++++++++++++++++++ imagekit/specs.py | 119 ++++++++++++++++++++ imagekit/tests.py | 86 ++++++++++++++ imagekit/utils.py | 15 +++ 12 files changed, 604 insertions(+) create mode 100644 imagekit/__init__.py create mode 100644 imagekit/defaults.py create mode 100644 imagekit/lib.py create mode 100644 imagekit/management/__init__.py create mode 100644 imagekit/management/commands/__init__.py create mode 100644 imagekit/management/commands/ikflush.py create mode 100644 imagekit/models.py create mode 100644 imagekit/options.py create mode 100644 imagekit/processors.py create mode 100644 imagekit/specs.py create mode 100644 imagekit/tests.py create mode 100644 imagekit/utils.py diff --git a/imagekit/__init__.py b/imagekit/__init__.py new file mode 100644 index 0000000..2965bbd --- /dev/null +++ b/imagekit/__init__.py @@ -0,0 +1,13 @@ +""" + +Django ImageKit + +Author: Justin Driscoll +Version: 0.2 + +""" +VERSION = "0.2" + + + + \ No newline at end of file diff --git a/imagekit/defaults.py b/imagekit/defaults.py new file mode 100644 index 0000000..e1a05f6 --- /dev/null +++ b/imagekit/defaults.py @@ -0,0 +1,21 @@ +""" Default ImageKit configuration """ + +from imagekit.specs import ImageSpec +from imagekit import processors + +class ResizeThumbnail(processors.Resize): + width = 100 + height = 50 + crop = True + +class EnhanceSmall(processors.Adjustment): + contrast = 1.2 + sharpness = 1.1 + +class SampleReflection(processors.Reflection): + size = 0.5 + background_color = "#000000" + +class DjangoAdminThumbnail(ImageSpec): + access_as = 'admin_thumbnail' + processors = [ResizeThumbnail, EnhanceSmall, SampleReflection] diff --git a/imagekit/lib.py b/imagekit/lib.py new file mode 100644 index 0000000..65646a4 --- /dev/null +++ b/imagekit/lib.py @@ -0,0 +1,17 @@ +# 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 + import ImageColor +except ImportError: + try: + from PIL import Image + from PIL import ImageFile + from PIL import ImageFilter + from PIL import ImageEnhance + from PIL import ImageColor + except ImportError: + raise ImportError('ImageKit was unable to import the Python Imaging Library. Please confirm it`s installed and available on your current Python path.') \ No newline at end of file diff --git a/imagekit/management/__init__.py b/imagekit/management/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/imagekit/management/__init__.py @@ -0,0 +1 @@ + diff --git a/imagekit/management/commands/__init__.py b/imagekit/management/commands/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/imagekit/management/commands/__init__.py @@ -0,0 +1 @@ + diff --git a/imagekit/management/commands/ikflush.py b/imagekit/management/commands/ikflush.py new file mode 100644 index 0000000..c03440f --- /dev/null +++ b/imagekit/management/commands/ikflush.py @@ -0,0 +1,38 @@ +from django.db.models.loading import cache +from django.core.management.base import BaseCommand, CommandError +from optparse import make_option +from imagekit.models import ImageModel +from imagekit.specs import ImageSpec + + +class Command(BaseCommand): + help = ('Clears all ImageKit cached files.') + args = '[apps]' + requires_model_validation = True + can_import_settings = True + + def handle(self, *args, **options): + return flush_cache(args, options) + +def flush_cache(apps, options): + """ Clears the image cache + + """ + apps = [a.strip(',') for a in apps] + if apps: + print 'Flushing cache for %s...' % ', '.join(apps) + else: + print 'Flushing caches...' + + for app_label in apps: + app = cache.get_app(app_label) + models = [m for m in cache.get_models(app) if issubclass(m, ImageModel)] + + for model in models: + for obj in model.objects.all(): + for spec in model._ik.specs: + prop = getattr(obj, spec.name(), None) + if prop is not None: + prop._delete() + if spec.pre_cache: + prop._create() diff --git a/imagekit/models.py b/imagekit/models.py new file mode 100644 index 0000000..140715e --- /dev/null +++ b/imagekit/models.py @@ -0,0 +1,136 @@ +import os +from datetime import datetime +from django.conf import settings +from django.core.files.base import ContentFile +from django.db import models +from django.db.models.base import ModelBase +from django.utils.translation import ugettext_lazy as _ + +from imagekit import specs +from imagekit.lib import * +from imagekit.options import Options +from imagekit.utils import img_to_fobj + +# Modify image file buffer size. +ImageFile.MAXBLOCK = getattr(settings, 'PIL_IMAGEFILE_MAXBLOCK', 256 * 2 ** 10) + +# Choice tuples for specifying the crop origin. +# These are provided for convenience. +CROP_HORZ_CHOICES = ( + (0, _('left')), + (1, _('center')), + (2, _('right')), +) + +CROP_VERT_CHOICES = ( + (0, _('top')), + (1, _('center')), + (2, _('bottom')), +) + + +class ImageModelBase(ModelBase): + """ ImageModel metaclass + + This metaclass parses IKOptions and loads the specified specification + module. + + """ + def __init__(cls, name, bases, attrs): + parents = [b for b in bases if isinstance(b, ImageModelBase)] + if not parents: + return + user_opts = getattr(cls, 'IKOptions', None) + opts = Options(user_opts) + try: + module = __import__(opts.spec_module, {}, {}, ['']) + except ImportError: + raise ImportError('Unable to load imagekit config module: %s' % \ + opts.spec_module) + for spec in [spec for spec in module.__dict__.values() \ + if isinstance(spec, type) \ + and issubclass(spec, specs.ImageSpec) \ + and spec != specs.ImageSpec]: + setattr(cls, spec.name(), specs.Descriptor(spec)) + opts.specs.append(spec) + setattr(cls, '_ik', opts) + + +class ImageModel(models.Model): + """ Abstract base class implementing all core ImageKit functionality + + Subclasses of ImageModel are augmented with accessors for each defined + image specification and can override the inner IKOptions class to customize + storage locations and other options. + + """ + __metaclass__ = ImageModelBase + + class Meta: + abstract = True + + class IKOptions: + pass + + def admin_thumbnail_view(self): + if not self._imgfield: + return None + prop = getattr(self, self._ik.admin_thumbnail_spec, None) + if prop is None: + return 'An "%s" image spec has not been defined.' % \ + self._ik.admin_thumbnail_spec + else: + if hasattr(self, 'get_absolute_url'): + return u'' % \ + (self.get_absolute_url(), prop.url) + else: + return u'' % \ + (self._imgfield.url, prop.url) + admin_thumbnail_view.short_description = _('Thumbnail') + admin_thumbnail_view.allow_tags = True + + @property + def _imgfield(self): + return getattr(self, self._ik.image_field) + + def _clear_cache(self): + for spec in self._ik.specs: + prop = getattr(self, spec.name()) + prop._delete() + + def _pre_cache(self): + for spec in self._ik.specs: + if spec.pre_cache: + prop = getattr(self, spec.name()) + prop._create() + + def save(self, clear_cache=True, *args, **kwargs): + is_new_object = self._get_pk_val is None + super(ImageModel, self).save(*args, **kwargs) + if is_new_object: + clear_cache = False + spec = self._ik.preprocessor_spec + if spec is not None: + newfile = self._imgfield.storage.open(str(self._imgfield)) + img = Image.open(newfile) + img = spec.process(img, None) + format = img.format or 'JPEG' + if format != 'JPEG': + imgfile = img_to_fobj(img, format) + else: + imgfile = img_to_fobj(img, format, + quality=int(spec.quality), + optimize=True) + content = ContentFile(imgfile.read()) + newfile.close() + name = str(self._imgfield) + self._imgfield.storage.delete(name) + self._imgfield.storage.save(name, content) + if clear_cache and self._imgfield != '': + self._clear_cache() + self._pre_cache() + + def delete(self): + assert self._get_pk_val() is not None, "%s object can't be deleted because its %s attribute is set to None." % (self._meta.object_name, self._meta.pk.attname) + self._clear_cache() + models.Model.delete(self) diff --git a/imagekit/options.py b/imagekit/options.py new file mode 100644 index 0000000..022cc9e --- /dev/null +++ b/imagekit/options.py @@ -0,0 +1,23 @@ +# Imagekit options +from imagekit import processors +from imagekit.specs import ImageSpec + + +class Options(object): + """ Class handling per-model imagekit options + + """ + image_field = 'image' + crop_horz_field = 'crop_horz' + crop_vert_field = 'crop_vert' + preprocessor_spec = None + cache_dir = 'cache' + save_count_as = None + cache_filename_format = "%(filename)s_%(specname)s.%(extension)s" + admin_thumbnail_spec = 'admin_thumbnail' + spec_module = 'imagekit.defaults' + + def __init__(self, opts): + for key, value in opts.__dict__.iteritems(): + setattr(self, key, value) + self.specs = [] \ No newline at end of file diff --git a/imagekit/processors.py b/imagekit/processors.py new file mode 100644 index 0000000..6f6b480 --- /dev/null +++ b/imagekit/processors.py @@ -0,0 +1,134 @@ +""" Imagekit Image "ImageProcessors" + +A processor defines a set of class variables (optional) and a +class method named "process" which processes the supplied image using +the class properties as settings. The process method can be overridden as well allowing user to define their +own effects/processes entirely. + +""" +from imagekit.lib import * + +class ImageProcessor(object): + """ Base image processor class """ + @classmethod + def process(cls, image, obj=None): + return image + + +class Adjustment(ImageProcessor): + color = 1.0 + brightness = 1.0 + contrast = 1.0 + sharpness = 1.0 + + @classmethod + def process(cls, image, obj=None): + for name in ['Color', 'Brightness', 'Contrast', 'Sharpness']: + factor = getattr(cls, name.lower()) + if factor != 1.0: + image = getattr(ImageEnhance, name)(image).enhance(factor) + return image + + +class Reflection(ImageProcessor): + background_color = '#FFFFFF' + size = 0.0 + opacity = 0.6 + + @classmethod + def process(cls, image, obj=None): + # convert bgcolor string to rgb value + background_color = ImageColor.getrgb(cls.background_color) + # copy orignial image and flip the orientation + reflection = image.copy().transpose(Image.FLIP_TOP_BOTTOM) + # create a new image filled with the bgcolor the same size + background = Image.new("RGB", image.size, background_color) + # calculate our alpha mask + start = int(255 - (255 * cls.opacity)) # The start of our gradient + steps = int(255 * cls.size) # 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(image.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(image.size[1] * cls.size) + reflection = reflection.crop((0, 0, image.size[0], reflection_height)) + # create new image sized to hold both the original image and the reflection + composite = Image.new("RGB", (image.size[0], image.size[1]+reflection_height), background_color) + # paste the orignal image and the reflection into the composite image + composite.paste(image, (0, 0)) + composite.paste(reflection, (0, image.size[1])) + # return the image complete with reflection effect + return composite + + +class Resize(ImageProcessor): + width = None + height = None + crop = False + upscale = False + + @classmethod + def process(cls, image, obj=None): + cur_width, cur_height = image.size + if cls.crop: + crop_horz = getattr(obj, obj._ik.crop_horz_field, 1) + crop_vert = getattr(obj, obj._ik.crop_vert_field, 1) + ratio = max(float(cls.width)/cur_width, float(cls.height)/cur_height) + resize_x, resize_y = ((cur_width * ratio), (cur_height * ratio)) + crop_x, crop_y = (abs(cls.width - resize_x), abs(cls.height - resize_y)) + x_diff, y_diff = (int(crop_x / 2), int(crop_y / 2)) + box_left, box_right = { + 0: (0, cls.width), + 1: (int(x_diff), int(x_diff + cls.width)), + 2: (int(crop_x), int(resize_x)), + }[crop_horz] + box_upper, box_lower = { + 0: (0, cls.height), + 1: (int(y_diff), int(y_diff + cls.height)), + 2: (int(crop_y), int(resize_y)), + }[crop_vert] + box = (box_left, box_upper, box_right, box_lower) + image = image.resize((int(resize_x), int(resize_y)), Image.ANTIALIAS).crop(box) + else: + if not cls.width is None and not cls.height is None: + ratio = min(float(cls.width)/cur_width, + float(cls.height)/cur_height) + else: + if cls.width is None: + ratio = float(cls.height)/cur_height + else: + ratio = float(cls.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 cls.upscale: + return image + image = image.resize(new_dimensions, Image.ANTIALIAS) + return image + + +class Transpose(ImageProcessor): + """ Rotates or flips the image + + Method should be one of the following strings: + - FLIP_LEFT RIGHT + - FLIP_TOP_BOTTOM + - ROTATE_90 + - ROTATE_270 + - ROTATE_180 + + """ + method = 'FLIP_LEFT_RIGHT' + + @classmethod + def process(cls, image, obj=None): + return image.transpose(getattr(Image, cls.method)) diff --git a/imagekit/specs.py b/imagekit/specs.py new file mode 100644 index 0000000..a6832ba --- /dev/null +++ b/imagekit/specs.py @@ -0,0 +1,119 @@ +""" ImageKit image specifications + +All imagekit specifications must inherit from the ImageSpec class. Models +inheriting from ImageModel will be modified with a descriptor/accessor for each +spec found. + +""" +import os +from StringIO import StringIO +from imagekit.lib import * +from imagekit.utils import img_to_fobj +from django.core.files.base import ContentFile + +class ImageSpec(object): + pre_cache = False + quality = 70 + increment_count = False + processors = [] + + @classmethod + def name(cls): + return getattr(cls, 'access_as', cls.__name__.lower()) + + @classmethod + def process(cls, image, obj): + processed_image = image.copy() + for proc in cls.processors: + processed_image = proc.process(processed_image, obj) + return processed_image + + +class Accessor(object): + def __init__(self, obj, spec): + self._img = None + self._obj = obj + self.spec = spec + + def _get_imgfile(self): + format = self._img.format or 'JPEG' + if format != 'JPEG': + imgfile = img_to_fobj(self._img, format) + else: + imgfile = img_to_fobj(self._img, format, + quality=int(self.spec.quality), + optimize=True) + return imgfile + + def _create(self): + if self._exists(): + return + # process the original image file + fp = self._obj._imgfield.storage.open(self._obj._imgfield.name) + fp.seek(0) + fp = StringIO(fp.read()) + try: + self._img = self.spec.process(Image.open(fp), self._obj) + # save the new image to the cache + content = ContentFile(self._get_imgfile().read()) + self._obj._imgfield.storage.save(self.name, content) + except IOError: + pass + + def _delete(self): + self._obj._imgfield.storage.delete(self.name) + + def _exists(self): + return self._obj._imgfield.storage.exists(self.name) + + def _basename(self): + filename, extension = \ + os.path.splitext(os.path.basename(self._obj._imgfield.name)) + return self._obj._ik.cache_filename_format % \ + {'filename': filename, + 'specname': self.spec.name(), + 'extension': extension.lstrip('.')} + + @property + def name(self): + return os.path.join(self._obj._ik.cache_dir, self._basename()) + + @property + def url(self): + self._create() + if self.spec.increment_count: + fieldname = self._obj._ik.save_count_as + if fieldname is not None: + current_count = getattr(self._obj, fieldname) + setattr(self._obj, fieldname, current_count + 1) + self._obj.save(clear_cache=False) + return self._obj._imgfield.storage.url(self.name) + + @property + def file(self): + self._create() + return self._obj._imgfield.storage.open(self.name) + + @property + def image(self): + if self._img is None: + self._create() + if self._img is None: + self._img = Image.open(self.file) + return self._img + + @property + def width(self): + return self.image.size[0] + + @property + def height(self): + return self.image.size[1] + + +class Descriptor(object): + def __init__(self, spec): + self._spec = spec + + def __get__(self, obj, type=None): + return Accessor(obj, self._spec) diff --git a/imagekit/tests.py b/imagekit/tests.py new file mode 100644 index 0000000..8c2eb5e --- /dev/null +++ b/imagekit/tests.py @@ -0,0 +1,86 @@ +import os +import tempfile +import unittest +from django.conf import settings +from django.core.files.base import ContentFile +from django.db import models +from django.test import TestCase + +from imagekit import processors +from imagekit.models import ImageModel +from imagekit.specs import ImageSpec +from imagekit.lib import Image + + +class ResizeToWidth(processors.Resize): + width = 100 + +class ResizeToHeight(processors.Resize): + height = 100 + +class ResizeToFit(processors.Resize): + width = 100 + height = 100 + +class ResizeCropped(ResizeToFit): + crop = ('center', 'center') + +class TestResizeToWidth(ImageSpec): + access_as = 'to_width' + processors = [ResizeToWidth] + +class TestResizeToHeight(ImageSpec): + access_as = 'to_height' + processors = [ResizeToHeight] + +class TestResizeCropped(ImageSpec): + access_as = 'cropped' + processors = [ResizeCropped] + +class TestPhoto(ImageModel): + """ Minimal ImageModel class for testing """ + image = models.ImageField(upload_to='images') + + class IKOptions: + spec_module = 'imagekit.tests' + + +class IKTest(TestCase): + """ Base TestCase class """ + def setUp(self): + # create a test image using tempfile and PIL + self.tmp = tempfile.TemporaryFile() + Image.new('RGB', (800, 600)).save(self.tmp, 'JPEG') + self.tmp.seek(0) + self.p = TestPhoto() + self.p.image.save(os.path.basename('test.jpg'), + ContentFile(self.tmp.read())) + self.p.save() + # destroy temp file + self.tmp.close() + + def test_setup(self): + self.assertEqual(self.p.image.width, 800) + self.assertEqual(self.p.image.height, 600) + + def test_to_width(self): + self.assertEqual(self.p.to_width.width, 100) + self.assertEqual(self.p.to_width.height, 75) + + def test_to_height(self): + self.assertEqual(self.p.to_height.width, 133) + self.assertEqual(self.p.to_height.height, 100) + + def test_crop(self): + self.assertEqual(self.p.cropped.width, 100) + self.assertEqual(self.p.cropped.height, 100) + + def test_url(self): + tup = (settings.MEDIA_URL, self.p._ik.cache_dir, 'test_to_width.jpg') + self.assertEqual(self.p.to_width.url, "%s%s/%s" % tup) + + def tearDown(self): + # make sure image file is deleted + path = self.p.image.path + self.p.delete() + self.failIf(os.path.isfile(path)) diff --git a/imagekit/utils.py b/imagekit/utils.py new file mode 100644 index 0000000..352d40f --- /dev/null +++ b/imagekit/utils.py @@ -0,0 +1,15 @@ +""" ImageKit utility functions """ + +import tempfile + +def img_to_fobj(img, format, **kwargs): + tmp = tempfile.TemporaryFile() + if format != 'JPEG': + try: + img.save(tmp, format, **kwargs) + return + except KeyError: + pass + img.save(tmp, format, **kwargs) + tmp.seek(0) + return tmp From ee1d4d8501d55bc504c58de9764c7665ed4f89b5 Mon Sep 17 00:00:00 2001 From: substantialnoninfringinguser Date: Wed, 13 May 2009 06:26:17 +0100 Subject: [PATCH 005/449] [svn] django-evolution is optional so shouldn't be in main settings Copied from http://cucc@cucc.survex.com/svn/trunk/expoweb/troggle/, rev. 8340 by cucc @ 5/11/2009 3:18 AM --- settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings.py b/settings.py index 1e578db..5f89347 100644 --- a/settings.py +++ b/settings.py @@ -80,7 +80,7 @@ INSTALLED_APPS = ( 'django.contrib.redirects', #'troggle.photologue', #'troggle.reversion', - 'django_evolution', + #'django_evolution', 'troggle.registration', 'troggle.profiles', 'troggle.expo', From 736d13310a72b5734fcab11fff36f0023f192278 Mon Sep 17 00:00:00 2001 From: substantialnoninfringinguser Date: Wed, 13 May 2009 06:27:00 +0100 Subject: [PATCH 006/449] [svn] Copied from http://cucc@cucc.survex.com/svn/trunk/expoweb/troggle/, rev. 8341 by cucc @ 5/11/2009 3:21 AM --- expo/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/expo/models.py b/expo/models.py index a3953d9..6331c0c 100644 --- a/expo/models.py +++ b/expo/models.py @@ -11,7 +11,7 @@ from django.conf import settings import datetime from decimal import Decimal, getcontext from django.core.urlresolvers import reverse -from imagekit.models import ImageModel +from troggle.imagekit.models import ImageModel getcontext().prec=2 #use 2 significant figures for decimal calculations from models_survex import * From 987bd56edd333a5f4b1eab407f75e9bd179abe19 Mon Sep 17 00:00:00 2001 From: substantialnoninfringinguser Date: Wed, 13 May 2009 06:27:45 +0100 Subject: [PATCH 007/449] [svn] Copied from http://cucc@cucc.survex.com/svn/trunk/expoweb/troggle/, rev. 8342 by cucc @ 5/11/2009 3:23 AM --- settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings.py b/settings.py index 5f89347..777a0a9 100644 --- a/settings.py +++ b/settings.py @@ -84,5 +84,5 @@ INSTALLED_APPS = ( 'troggle.registration', 'troggle.profiles', 'troggle.expo', - + 'troggle.imagekit', ) From 268d69214d0fd61236da0b9df307cd4081f2ab58 Mon Sep 17 00:00:00 2001 From: substantialnoninfringinguser Date: Wed, 13 May 2009 06:28:36 +0100 Subject: [PATCH 008/449] [svn] Made the subcaves work! Now we just have to figure out how to parse them... Copied from http://cucc@cucc.survex.com/svn/trunk/expoweb/troggle/, rev. 8343 by cucc @ 5/11/2009 6:36 AM --- expo/models.py | 22 ++++++++-------- expo/views_caves.py | 11 ++++---- settings.py | 2 +- templates/subcave.html | 45 ++++++++++++++++++++++++++++++++ templates/todo.html | 59 ++++++++++++++++++++++++++++++++++++++++++ urls.py | 2 +- 6 files changed, 123 insertions(+), 18 deletions(-) create mode 100644 templates/subcave.html create mode 100644 templates/todo.html diff --git a/expo/models.py b/expo/models.py index 6331c0c..3472a01 100644 --- a/expo/models.py +++ b/expo/models.py @@ -1,17 +1,14 @@ -import urllib -import string +import urllib, urlparse, string, os, datetime from django.forms import ModelForm from django.db import models from django.contrib import admin from django.core.files.storage import FileSystemStorage from django.contrib.auth.models import User from django.contrib.contenttypes.models import ContentType -import os from django.conf import settings -import datetime from decimal import Decimal, getcontext -from django.core.urlresolvers import reverse -from troggle.imagekit.models import ImageModel +from django.core.urlresolvers import reverse +from imagekit.models import ImageModel getcontext().prec=2 #use 2 significant figures for decimal calculations from models_survex import * @@ -462,7 +459,7 @@ class Subcave(TroggleModel): description = models.TextField() name = models.CharField(max_length=200, ) cave = models.ForeignKey('Cave', blank=True, null=True, help_text="Only the top-level subcave should be linked to a cave") - parent= models.ForeignKey('Subcave', blank=True, null=True,) + parent= models.ForeignKey('Subcave', blank=True, null=True, related_name='children') adjoining = models.ManyToManyField('Subcave',blank=True, null=True,) survex_file = models.CharField(max_length=200, blank=True, null=True,) @@ -473,14 +470,17 @@ class Subcave(TroggleModel): urlString=self.name if self.parent: parent=self.parent - while parent.parent: + while parent: #recursively walk up the tree, adding parents to the left of the URL urlString=parent.name+'/'+urlString + if parent.cave: + cave=parent.cave parent=parent.parent - urlString=unicode(parent.cave.kataster_number)+urlString + urlString='cave/'+unicode(cave.kataster_number)+'/'+urlString else: - urlString=unicode(self.cave.kataster_number)+urlString + urlString='cave/'+unicode(self.cave.kataster_number)+'/'+urlString - return settings.URL_ROOT + urlString + + return urlparse.urljoin(settings.URL_ROOT, urlString) class QM(TroggleModel): #based on qm.csv in trunk/expoweb/smkridge/204 which has the fields: diff --git a/expo/views_caves.py b/expo/views_caves.py index 1e11602..691cdcb 100644 --- a/expo/views_caves.py +++ b/expo/views_caves.py @@ -54,15 +54,16 @@ def survexblock(request, survexpath): def subcave(request, cave_id, subcave): print subcave - subcaveSeq=re.findall('([a-zA-Z]*)(?:/)',subcave) + subcaveSeq=re.findall('(?:/)([^/]*)',subcave) print subcaveSeq - cave=models.Cave.objects.filter(kataster_number = cave_id)[0] + cave=models.Cave.objects.get(kataster_number = cave_id) subcave=models.Subcave.objects.get(name=subcaveSeq[0], cave=cave) if len(subcaveSeq)>1: - for singleSubcave in subcaveSeq[1:]: - subcave=subcave.subcave_set.get(name=singleSubcave) + for subcaveUrlSegment in subcaveSeq[1:]: + if subcaveUrlSegment: + subcave=subcave.children.get(name=subcaveUrlSegment) print subcave - return render_response(request,'subcave.html', {'subcave': subcave,}) + return render_response(request,'subcave.html', {'subcave': subcave,'cave':cave}) def caveSearch(request): query_string = '' diff --git a/settings.py b/settings.py index 777a0a9..449faf4 100644 --- a/settings.py +++ b/settings.py @@ -84,5 +84,5 @@ INSTALLED_APPS = ( 'troggle.registration', 'troggle.profiles', 'troggle.expo', - 'troggle.imagekit', + 'troggle.imagekit', ) diff --git a/templates/subcave.html b/templates/subcave.html new file mode 100644 index 0000000..c635833 --- /dev/null +++ b/templates/subcave.html @@ -0,0 +1,45 @@ +{% extends "cavebase.html" %} +{% load wiki_markup %} +{% block title %} Subcave {{subcave}} {% endblock title %} +{% block editLink %}Edit subcave {{subcave|wiki_to_html_short}}{% endblock %} +{% block content %} + +

{{subcave}}

+

+ {{subcave.description}} +

+ +
+ +

Related places

+ +

Parent

+ + + +

Connected subareas

+ +
    + {% for sibling in subcave.adjoining.all%} +
  • {{silbling}}
  • + {% endfor %} +
+ +

Children

+ +
    + {% for child in subcave.children.all %} +
  • {{child}}
  • + {% endfor %} +
+ + +
+ +{% endblock content %} \ No newline at end of file diff --git a/templates/todo.html b/templates/todo.html new file mode 100644 index 0000000..1c9fd3b --- /dev/null +++ b/templates/todo.html @@ -0,0 +1,59 @@ +{% extends "base.html" %} +{% load wiki_markup %} + +{% block title %}Cambridge Expeditions to Austria{% endblock %} + +{% block content %} + +

The unfinished front page

+ + +

Further work

+ +

Julian's work: +

parse 1992-1976 logbooks; (esp top 161)

+

detect T/U on log entries;

+

name matching and spelling in survex files;

+

Improve logbook wikihtml text

+ +

Other work:

+

surf through the tunnel sketches and images

+

bugs with all.svx block (double dot) +

render bitmap view of every survex block as a thumbnail

+

upload tunnel images and tunnel sketches

+

where are the subcaves;

+

cave section entrance match for logbook entries

+

simplify the survex parsing code (if necessary);

+

wiki survex stop linegap between comment lins

+

links between logbooks and survex blocks to cave things;

+

mini-tree of survexblocks;

+

connect sketches to caves to survey blocks and render thumbnailwise;

+

all images to start appearing in pages; and so on

+ +

{{message}}

+ +
+ + + + +
    +{% for expedition in expeditions %} +
  • + {{expedition.name}} + - {{expedition.logbookentry_set.count}} logbook entries +
  • +{% endfor %} +
+ +{% endblock %} diff --git a/urls.py b/urls.py index 780fdb0..ee4dc62 100644 --- a/urls.py +++ b/urls.py @@ -36,7 +36,7 @@ urlpatterns = patterns('', url(r'^cave/(?P[^/]+)/?(?P[^/])$', ent), #(r'^cave/(?P[^/]+)/edit/$', edit_cave), #(r'^cavesearch', caveSearch), - url(r'^cave/(?P[^/]+)/(?P[a-zA-Z/]+)/?$', subcave, name="subcave"), + url(r'^cave/(?P[^/]+)(?P/.*)/?$', subcave, name="subcave"), url(r'^survex/(.*?)\.index$', views_survex.index, name="survexindex"), From c454fecfc1dc1e63812cb230fd5e7d9d90b20c33 Mon Sep 17 00:00:00 2001 From: substantialnoninfringinguser Date: Wed, 13 May 2009 07:01:45 +0100 Subject: [PATCH 009/449] [svn] Add link to google code issue tracker --- templates/todo.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/templates/todo.html b/templates/todo.html index 1c9fd3b..0aafadb 100644 --- a/templates/todo.html +++ b/templates/todo.html @@ -20,6 +20,8 @@

Further work

+

Please see the issues tracker on google code. You can contribute new feature requests and bug reports.

+

Julian's work:

parse 1992-1976 logbooks; (esp top 161)

detect T/U on log entries;

From 57a972fae79e77752c9b2bd18889eb0a61c13d41 Mon Sep 17 00:00:00 2001 From: substantialnoninfringinguser Date: Thu, 14 May 2009 06:19:46 +0100 Subject: [PATCH 010/449] [svn] Fixed broken buttons on controlpanel, added CAVETAB2.CSV export and download buttons and made them work too. Changed ordering on PersonExpeditions so that it is based on their expedition. That way, even if we don't have date info on when a user was on expo exactly, pages like personindex work correctly. --- databaseReset.py | 18 +++++++++++++++++- expo/models.py | 3 ++- expo/views_other.py | 24 +++++++++++++++++------- export/tocavetab.py | 3 +-- export/toqms.py | 3 +-- parsers/cavetab.py | 2 -- urls.py | 2 +- 7 files changed, 39 insertions(+), 16 deletions(-) diff --git a/databaseReset.py b/databaseReset.py index f2c3b51..5503104 100644 --- a/databaseReset.py +++ b/databaseReset.py @@ -6,6 +6,7 @@ os.environ['DJANGO_SETTINGS_MODULE'] = 'settings' from django.core import management from django.db import connection from django.contrib.auth.models import User +from django.http import HttpResponse def reload_db(): cursor = connection.cursor() @@ -55,4 +56,19 @@ def reset(): import_logbooks() import_survex() import_QMs() - import_surveys() \ No newline at end of file + import_surveys() + +def export_cavetab(): + from export import tocavetab + outfile=file(os.path.join(settings.EXPOWEB, "noinfo", "CAVETAB2.CSV"),'w') + tocavetab.writeCaveTab(outfile) + outfile.close() + +def export_qms(): #finish this. need cave chooser + from export import toqms + outfile=file(os.path.join(settings.EXPOWEB, "noinfo", "CAVETAB2.CSV"),'w') + outfile.close() + + + + \ No newline at end of file diff --git a/expo/models.py b/expo/models.py index 3472a01..4c8332b 100644 --- a/expo/models.py +++ b/expo/models.py @@ -162,7 +162,8 @@ class PersonExpedition(TroggleModel): class Meta: ordering = ('expedition',) - get_latest_by = 'date_from' + #order_with_respect_to = 'expedition' + get_latest_by = 'expedition' def GetPersonChronology(self): res = { } diff --git a/expo/views_other.py b/expo/views_other.py index 0f8cb79..1b3b64f 100644 --- a/expo/views_other.py +++ b/expo/views_other.py @@ -5,8 +5,7 @@ from django.db.models import Q import databaseReset import re import randSent -from django.http import HttpResponse - +from django.http import HttpResponse, HttpResponseRedirect from django.core.urlresolvers import reverse from troggle.alwaysUseRequestContext import render_response # see views_logbooks for explanation on this. @@ -62,9 +61,20 @@ def calendar(request,year): def controlPanel(request): message = "no test message" #reverse('personn', kwargs={"name":"hkjhjh"}) if request.method=='POST': - for item in request.POST: - if request.user.is_superuser and item!='item': - print "running"+ " databaseReset."+item+"()" - exec "databaseReset."+item+"()" + if request.user.is_superuser: + for item in request.POST: + if item!='item': + print "running"+ " databaseReset."+item+"()" + exec "databaseReset."+item+"()" + else: + return HttpResponseRedirect(reverse('auth_login')) - return render_response(request,'controlPanel.html', ) \ No newline at end of file + return render_response(request,'controlPanel.html', ) + +def downloadCavetab(request): + from export import tocavetab + response = HttpResponse(mimetype='text/csv') + response['Content-Disposition'] = 'attachment; filename=CAVEETAB2.CSV' + tocavetab.writeCaveTab(response) + return response + \ No newline at end of file diff --git a/export/tocavetab.py b/export/tocavetab.py index 121c36e..ab7a2ee 100644 --- a/export/tocavetab.py +++ b/export/tocavetab.py @@ -42,8 +42,7 @@ def cavetabRow(cave): caveRow[headers.index(column)]=modelField.replace(u'\xd7','x').replace(u'\u201c','').replace(u'\u2013','').replace(u'\xbd','') return caveRow -def writeCaveTab(path): - outfile=file(path,'w') +def writeCaveTab(outfile): cavewriter=csv.writer(outfile,lineterminator='\r') cavewriter.writerow(headers) for cave in models.Cave.objects.all(): diff --git a/export/toqms.py b/export/toqms.py index 0597da6..dd58fca 100644 --- a/export/toqms.py +++ b/export/toqms.py @@ -28,8 +28,7 @@ def qmRow(qm): qmRow[headers.index(column)]=modelField.replace(u'\xd7','x').replace(u'\u201c','').replace(u'\u2013','').replace(u'\xbd','') return qmRow -def writeQmTable(path,cave): - outfile=file(path,'w') +def writeQmTable(outfile,cave): cavewriter=csv.writer(outfile,lineterminator='\r') cavewriter.writerow(headers) for qm in cave.get_QMs(): diff --git a/parsers/cavetab.py b/parsers/cavetab.py index fde5333..2dc071c 100644 --- a/parsers/cavetab.py +++ b/parsers/cavetab.py @@ -1,6 +1,4 @@ # -*- coding: utf-8 -*- -import sys -sys.path.append("/home/mjg/expoweb/troggle") import troggle.expo.models as models from django.conf import settings import csv diff --git a/urls.py b/urls.py index ee4dc62..f5decdb 100644 --- a/urls.py +++ b/urls.py @@ -58,6 +58,7 @@ urlpatterns = patterns('', url(r'^survey/(?P\d\d\d\d)\#(?P\d*)$', survey, name="survey"), url(r'^controlpanel/?$', views_other.controlPanel, name="controlpanel"), + url(r'^cavetab/?$', views_other.downloadCavetab, name="downloadcavetab"), (r'^admin/doc/?', include('django.contrib.admindocs.urls')), (r'^admin/(.*)', admin.site.root), @@ -68,7 +69,6 @@ urlpatterns = patterns('', # (r'^personform/(.*)$', personForm), - (r'^photologue/', include('photologue.urls')), (r'^site_media/(?P.*)$', 'django.views.static.serve', {'document_root': settings.MEDIA_ROOT, 'show_indexes': True}), From b1cc5ce6b561019b7b5a1039587e3794e094e5a4 Mon Sep 17 00:00:00 2001 From: substantialnoninfringinguser Date: Thu, 14 May 2009 06:32:58 +0100 Subject: [PATCH 011/449] [svn] --- settings.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/settings.py b/settings.py index 449faf4..4a6fa34 100644 --- a/settings.py +++ b/settings.py @@ -35,10 +35,12 @@ USE_I18N = True # Examples: "http://foo.com/media/", "/media/". ADMIN_MEDIA_PREFIX = '/troggle/media-admin/' PHOTOS_ROOT = os.path.join(EXPOWEB, 'photos') -MEDIA_URL = urlparse.urljoin(URL_ROOT , '/site_media/') -SURVEYS_URL = urlparse.urljoin(URL_ROOT , '/survey_scans/') -PHOTOS_URL = urlparse.urljoin(URL_ROOT , '/photos/') -SVX_URL = urlparse.urljoin(URL_ROOT , '/survex/') + +if URL_ROOT: + MEDIA_URL = urlparse.urljoin(URL_ROOT , '/site_media/') + SURVEYS_URL = urlparse.urljoin(URL_ROOT , '/survey_scans/') + PHOTOS_URL = urlparse.urljoin(URL_ROOT , '/photos/') + SVX_URL = urlparse.urljoin(URL_ROOT , '/survex/') APPEND_SLASH = False SMART_APPEND_SLASH = True From c4ff04c3576971f255f909ddfaa4beb5bd1bdc39 Mon Sep 17 00:00:00 2001 From: substantialnoninfringinguser Date: Thu, 14 May 2009 06:39:36 +0100 Subject: [PATCH 012/449] [svn] localsettings should override settings, so the import should be at the bottom of the file, unless someone has a better way of doing this --- settings.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/settings.py b/settings.py index 4a6fa34..6b878ee 100644 --- a/settings.py +++ b/settings.py @@ -1,4 +1,3 @@ -from localsettings import * import os import urlparse # Django settings for troggle project. @@ -88,3 +87,5 @@ INSTALLED_APPS = ( 'troggle.expo', 'troggle.imagekit', ) + +from localsettings import * \ No newline at end of file From 8538ef27a16de8c5f78a7fe352c51fe507729354 Mon Sep 17 00:00:00 2001 From: substantialnoninfringinguser Date: Thu, 14 May 2009 14:24:46 +0100 Subject: [PATCH 013/449] [svn] Forgot to upload with earlier commit --- templates/base.html | 6 ++++-- templates/controlPanel.html | 32 +++++++++++++++++++++++++++----- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/templates/base.html b/templates/base.html index 2a8e758..09e3017 100644 --- a/templates/base.html +++ b/templates/base.html @@ -7,11 +7,13 @@ + {% block head %}{% endblock %} - +
- + - +
caves from cavetab2.csv using parsers\cavetab.py
logbook entries using parsers\logbooks.py
people from folk.csv using parsers\people.py
people from folk.csv using parsers\people.py
QMs using parsers\QMs.py
survey scans using parsers\surveys.py
survex data using parsers\survex.py
caves from cavetab2.csv using parsers\cavetab.py
logbook entries using parsers\logbooks.py
people from folk.csv using parsers\people.py
QMs using parsers\QMs.py
QMs using parsers\QMs.py
survey scans using parsers\surveys.py
survex data using parsers\survex.py
survex data using parsers\survex.py
@@ -28,10 +28,32 @@

+

Export to csv:

- - + + + + + +
caves to cavetab2.csv
+ caves to cavetab2.csv + +
+

Overwrite the existing CAVETAB2.CSV file with one generated by Troggle.

+ +
+
+
+

Download a CAVETAB2.CSV file which is dynamically generated by Troggle.

+ +
+
qms to qms.csv + + +
+ + {% endblock %} \ No newline at end of file From 5f9330149395ed88f320759aeee294bbde190e31 Mon Sep 17 00:00:00 2001 From: substantialnoninfringinguser Date: Fri, 15 May 2009 03:29:19 +0100 Subject: [PATCH 014/449] [svn] Add: new generic object list template object_list.html, and convenience filter named "link" for making links from objects, and make expeditions list page using those two. Also, fixed survey parsing in databaseReset.py --- databaseReset.py | 1 + expo/imagekit_specs.py | 11 +++++------ expo/models.py | 12 +++++++++--- settings.py | 14 ++++++-------- templates/object_list.html | 15 +++++++++++++++ urls.py | 6 ++++-- 6 files changed, 40 insertions(+), 19 deletions(-) create mode 100644 templates/object_list.html diff --git a/databaseReset.py b/databaseReset.py index 5503104..77a7b8a 100644 --- a/databaseReset.py +++ b/databaseReset.py @@ -47,6 +47,7 @@ def import_QMs(): def import_surveys(): import parsers.surveys + parsers.surveys.parseSurveys(logfile=settings.LOGFILE) def reset(): reload_db() diff --git a/expo/imagekit_specs.py b/expo/imagekit_specs.py index 243cb9f..fd2e0a1 100644 --- a/expo/imagekit_specs.py +++ b/expo/imagekit_specs.py @@ -3,20 +3,19 @@ from imagekit import processors class ResizeThumb(processors.Resize): width = 100 - height = 75 - crop = True + crop = False class ResizeDisplay(processors.Resize): width = 600 -class EnhanceThumb(processors.Adjustment): - contrast = 1.2 - sharpness = 1.1 +#class EnhanceThumb(processors.Adjustment): + #contrast = 1.2 + #sharpness = 2 class Thumbnail(ImageSpec): access_as = 'thumbnail_image' pre_cache = True - processors = [ResizeThumb, EnhanceThumb] + processors = [ResizeThumb] class Display(ImageSpec): increment_count = True diff --git a/expo/models.py b/expo/models.py index 4c8332b..74a26d4 100644 --- a/expo/models.py +++ b/expo/models.py @@ -17,8 +17,11 @@ from models_survex import * class TroggleModel(models.Model): new_since_parsing = models.BooleanField(default=False, editable=False) + def object_name(self): + return self._meta.object_name + def get_admin_url(self): - return settings.URL_ROOT + "/admin/expo/" + self._meta.object_name.lower() + "/" + str(self.pk) + return settings.URL_ROOT + "/admin/expo/" + self.object_name.lower() + "/" + str(self.pk) class Meta: abstract = True @@ -26,8 +29,11 @@ class TroggleModel(models.Model): class TroggleImageModel(ImageModel): new_since_parsing = models.BooleanField(default=False, editable=False) + def object_name(self): + return self._meta.object_name + def get_admin_url(self): - return settings.URL_ROOT + "/admin/expo/" + self._meta.object_name.lower() + "/" + str(self.pk) + return settings.URL_ROOT + "/admin/expo/" + self.object_name.lower() + "/" + str(self.pk) class Meta: abstract = True @@ -42,7 +48,7 @@ class Expedition(TroggleModel): return self.year class Meta: - ordering = ('year',) + ordering = ('-year',) get_latest_by = 'date_from' def get_absolute_url(self): diff --git a/settings.py b/settings.py index 6b878ee..e99d199 100644 --- a/settings.py +++ b/settings.py @@ -1,3 +1,4 @@ +from localsettings import * import os import urlparse # Django settings for troggle project. @@ -35,11 +36,10 @@ USE_I18N = True ADMIN_MEDIA_PREFIX = '/troggle/media-admin/' PHOTOS_ROOT = os.path.join(EXPOWEB, 'photos') -if URL_ROOT: - MEDIA_URL = urlparse.urljoin(URL_ROOT , '/site_media/') - SURVEYS_URL = urlparse.urljoin(URL_ROOT , '/survey_scans/') - PHOTOS_URL = urlparse.urljoin(URL_ROOT , '/photos/') - SVX_URL = urlparse.urljoin(URL_ROOT , '/survex/') +MEDIA_URL = urlparse.urljoin(URL_ROOT , '/site_media/') +SURVEYS_URL = urlparse.urljoin(URL_ROOT , '/survey_scans/') +PHOTOS_URL = urlparse.urljoin(URL_ROOT , '/photos/') +SVX_URL = urlparse.urljoin(URL_ROOT , '/survex/') APPEND_SLASH = False SMART_APPEND_SLASH = True @@ -86,6 +86,4 @@ INSTALLED_APPS = ( 'troggle.profiles', 'troggle.expo', 'troggle.imagekit', -) - -from localsettings import * \ No newline at end of file +) \ No newline at end of file diff --git a/templates/object_list.html b/templates/object_list.html new file mode 100644 index 0000000..4021ad2 --- /dev/null +++ b/templates/object_list.html @@ -0,0 +1,15 @@ +{% extends "base.html" %} +{% load link %} +{% block title %}Troggle: all {{object_list.1.meta.object_name}} objects{%endblock%} + +{% block contentheader %} +

All {{object_list.0.object_name}} objects

+{% endblock contentheader %} + +{% block content %} +
    + {% for object in object_list %} + | {{ object|link }} + {% endfor %} | +
+{% endblock content %} \ No newline at end of file diff --git a/urls.py b/urls.py index f5decdb..fdead4d 100644 --- a/urls.py +++ b/urls.py @@ -7,6 +7,7 @@ from expo.views_survex import * from expo.models import * from django.views.generic.create_update import create_object from django.contrib import admin +from django.views.generic.list_detail import object_list admin.autodiscover() urlpatterns = patterns('', @@ -14,8 +15,8 @@ urlpatterns = patterns('', url(r'^$', views_other.frontpage, name="frontpage"), url(r'^todo/$', views_other.todo, name="todo"), - url(r'^caveindex/?$', views_caves.caveindex, name="caveindex"), - url(r'^personindex$', views_logbooks.personindex, name="personindex"), + url(r'^caves/?$', views_caves.caveindex, name="caveindex"), + url(r'^people/?$', views_logbooks.personindex, name="personindex"), #(r'^person/(?P\d*)/?$', views_logbooks.person), @@ -23,6 +24,7 @@ urlpatterns = patterns('', #url(r'^person/(\w+_\w+)$', views_logbooks.person, name="person"), url(r'^expedition/(\d+)$', views_logbooks.expedition, name="expedition"), + url(r'^expeditions/?$', object_list, {'queryset':Expedition.objects.all(),'template_name':'expeditions.html'},name="expeditionlist"), url(r'^personexpedition/(?P[A-Z]*[a-z]*)[^a-zA-Z]*(?P[A-Z]*[a-z]*)/(?P\d+)/?$', views_logbooks.personexpedition, name="personexpedition"), url(r'^logbookentry/(?P.*)/(?P.*)/?$', views_logbooks.logbookentry,name="logbookentry"), From b4c1d0cbb92d6bbc165ffb3eb6f74d3fdaa8d806 Mon Sep 17 00:00:00 2001 From: substantialnoninfringinguser Date: Fri, 15 May 2009 03:38:11 +0100 Subject: [PATCH 015/449] [svn] semi ugly hack... --- settings.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/settings.py b/settings.py index e99d199..c69d03f 100644 --- a/settings.py +++ b/settings.py @@ -1,4 +1,4 @@ -from localsettings import * +from localsettings import * #inital localsettings call so that urljoins work import os import urlparse # Django settings for troggle project. @@ -86,4 +86,6 @@ INSTALLED_APPS = ( 'troggle.profiles', 'troggle.expo', 'troggle.imagekit', -) \ No newline at end of file +) + +from localsettings import * #localsettings needs to take precedence. Call it to override any existing vars. \ No newline at end of file From a4212632b2e6f71b97d64785a20d276fa41af602 Mon Sep 17 00:00:00 2001 From: substantialnoninfringinguser Date: Fri, 15 May 2009 03:56:11 +0100 Subject: [PATCH 016/449] [svn] Make the workaround to avoid parsing interlaced pngs actually work (see issue # 14) --- parsers/surveys.py | 7 +++++-- templates/base.html | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/parsers/surveys.py b/parsers/surveys.py index e2a502d..351e292 100644 --- a/parsers/surveys.py +++ b/parsers/surveys.py @@ -126,5 +126,8 @@ def parseSurveys(logfile=None): def isInterlacedPNG(filePath): #We need to check for interlaced PNGs because the thumbnail engine can't handle them (uses PIL) file=Image.open(filePath) - return file.info['interlace'] - \ No newline at end of file + print filePath + if 'interlace' in file.info: + return file.info['interlace'] + else: + return False \ No newline at end of file diff --git a/templates/base.html b/templates/base.html index 09e3017..5615a8e 100644 --- a/templates/base.html +++ b/templates/base.html @@ -13,7 +13,7 @@ {% block head %}{% endblock %} - +
-
+

Download a CAVETAB2.CSV file which is dynamically generated by Troggle.

+ surveys to Surveys.csv + +
+

Overwrite the existing Surveys.csv file with one generated by Troggle.

+ +
+
+
+

Download a Surveys.csv file which is dynamically generated by Troggle.

+ +
+
qms to qms.csv - - +
+ + + + Choose a cave. + + + +
-{% endblock %} \ No newline at end of file +{% endblock %} \ No newline at end of file diff --git a/templates/survey.html b/templates/survey.html index a0395b6..9d677e5 100644 --- a/templates/survey.html +++ b/templates/survey.html @@ -11,57 +11,7 @@ blankColor = "rgb(153, 153, 153)" highlightedColor = "rgb(125, 125, 125)" chosenColor = "rgb(255, 255, 255)" - mnuItmLst=document.getElementsByClassName("menuBarItem") - function highlight(div){ - for (var i = 0, divIter; divIter = mnuItmLst[i]; i++) { - /*loop though all menuitems. blank them except ones that are toggled on*/ - if (divIter.style.backgroundColor!=chosenColor){ - divIter.style.backgroundColor=blankColor; - } - } - /*highlight the mouseovered div unless it is toggled on*/ - if (div.style.backgroundColor!=chosenColor){ - div.style.backgroundColor=highlightedColor; - } - } - - function unhighlight(div){ - /*highlight the mouseovered div unless it is the chosen one*/ - if (div.style.backgroundColor!=chosenColor){ - div.style.backgroundColor=blankColor; - } - } - - function toggle(div){ - if (document.getElementById(div.id+"Content").style.display="none"){ - document.getElementById(div.id+"Content").style.display="block"; - div.style.backgroundColor=chosenColor; - } - else { - document.getElementById(div.id+"Content").style.display="none"; - div.style.backgroundColor=blankColor; - } - } - -/* function choose(div){ - for (var i = 0, divIter; divIter = mnuItmLst[i]; i++) { - document.getElementById(divIter.id+"Content").style.display="none"; - } - document.getElementById(div.id+"Content").style.display="block"; - for (var i = 0, divIter; divIter = mnuItmLst[i]; i++) { - document.getElementById(divIter.id).style.backgroundColor=blankColor; - } - div.style.backgroundColor=chosenColor; - document.getElementById(progressTableContent).style.display="none"; - }*/ - - function redirectSurvey(){ - window.location = "{% url survey %}" + '/' + document.getElementById("expeditionChooser").value + "%23" + document.getElementById("surveyChooser").value; - } - - function redirectYear(){ - window.location = "{% url survey %}" + '/' + document.getElementById("expeditionChooser").value + "%23"; - } + {% endblock %} @@ -95,7 +45,7 @@
- @@ -120,16 +70,16 @@
-
survex file editor, keeping file in original structure
diff --git a/urls.py b/urls.py index fdead4d..8ae9ca7 100644 --- a/urls.py +++ b/urls.py @@ -38,7 +38,6 @@ urlpatterns = patterns('', url(r'^cave/(?P[^/]+)/?(?P[^/])$', ent), #(r'^cave/(?P[^/]+)/edit/$', edit_cave), #(r'^cavesearch', caveSearch), - url(r'^cave/(?P[^/]+)(?P/.*)/?$', subcave, name="subcave"), url(r'^survex/(.*?)\.index$', views_survex.index, name="survexindex"), @@ -60,7 +59,11 @@ urlpatterns = patterns('', url(r'^survey/(?P\d\d\d\d)\#(?P\d*)$', survey, name="survey"), url(r'^controlpanel/?$', views_other.controlPanel, name="controlpanel"), - url(r'^cavetab/?$', views_other.downloadCavetab, name="downloadcavetab"), + url(r'^CAVETAB2\.CSV/?$', views_other.downloadCavetab, name="downloadcavetab"), + url(r'^Surveys\.csv/?$', views_other.downloadSurveys, name="downloadsurveys"), + url(r'^cave/(?P[^/]+)/qm\.csv/?$', views_other.downloadQMs, name="downloadqms"), + (r'^downloadqms$', views_other.downloadQMs), + url(r'^cave/(?P[^/]+)(?P/.*)/?$', subcave, name="subcave"), (r'^admin/doc/?', include('django.contrib.admindocs.urls')), (r'^admin/(.*)', admin.site.root), From d99f44650cd3130e9a4ecc4c4bf66e79fbd4200a Mon Sep 17 00:00:00 2001 From: substantialnoninfringinguser Date: Mon, 18 May 2009 04:25:42 +0100 Subject: [PATCH 018/449] [svn] Turn main menu into dropdown (well actually, drop up) menu. --- media/css/main3.css | 31 +++-------------------- media/js/base.js | 61 ++++++++++++++++++++++++++++++--------------- templates/base.html | 30 ++++++++++++++++++++-- 3 files changed, 73 insertions(+), 49 deletions(-) diff --git a/media/css/main3.css b/media/css/main3.css index 384d9b3..77657a0 100644 --- a/media/css/main3.css +++ b/media/css/main3.css @@ -1,20 +1,3 @@ -html, body, div, span, applet, object, iframe, -h1, h2, h3, h4, h5, h6, p, blockquote, pre, -a, abbr, acronym, address, big, cite, code, -del, dfn, em, font, img, ins, kbd, q, s, samp, -small, strike, strong, sub, sup, tt, var, -dl, dt, dd, ol, ul, li, -fieldset, form, label, legend, -table, caption, tbody, tfoot, thead, tr, th, td - { - - font-weight: inherit; - font-style: inherit; - font-size: 100%; - font-family: inherit; - vertical-align: baseline; - } - html, body { height: 100%; } @@ -343,16 +326,10 @@ h1 { #footerLinks{ position:fixed; - text-align: center; - bottom:0; - left:0; - width:100%; - background-color:#000; - color:#999 -} - -#footerLinks a{ - color:#FFF + bottom:0px; + padding: 0; + margin-left:130px; + margin-right:130px; } /*.fadeIn { diff --git a/media/js/base.js b/media/js/base.js index 0dc562b..7a7ed5e 100644 --- a/media/js/base.js +++ b/media/js/base.js @@ -1,4 +1,22 @@ +/* The following serves to stretch the content div to the bottom of the margin images, or vice versa*/ +function contentHeight(){ +setMaxHeight($(".rightMargin,#content,.leftMargin,#col2"),$("#content")); +}; + +function setMaxHeight(group, target) { + tallest = 0; + group.each(function() { + thisHeight = $(this).height(); + if(thisHeight > tallest) { + tallest = thisHeight; + } + }); + target.height(tallest); +} + + +/*This is the jquery comment stuff */ $(document).ready(function() { $('.searchable li').quicksearch({ @@ -19,6 +37,11 @@ $(".toggleEyeCandy").click(function () { $(".toggleEyeCandy").toggle(); }); +$(".toggleMenu").click(function () { + $("ul.dropdown li:not(.toggleMenu)").toggle(); + $(".toggleMenu").toggle(); + }); + $(".nav").css('opacity','7') $(".footer").hide(); $(".fadeIn").hide(); @@ -36,11 +59,11 @@ function linkHover(hoverLink,image){ $(hoverLink).hover( function() { $(image).fadeIn("slow"); - $(hoverLink).css("background","gray"); +/* $(hoverLink).css("background","gray");*/ }, function() { $(image).fadeOut("slow"); - $(hoverLink).css("background","black"); +/* $(hoverLink).css("background","black");*/ } ); @@ -48,27 +71,25 @@ $(hoverLink).hover( }; -linkHover("#expoWebsiteLink","#richardBanner"); -linkHover("#cuccLink","#timeMachine"); +linkHover("#cavesLink","#richardBanner"); +linkHover("#caversLink","#timeMachine"); linkHover("#surveyBinderLink","#surveyHover"); linkHover("#troggle","#timeMachine"); +/*dropdown (well, up actually) menu code from http://css-tricks.com/simple-jquery-dropdowns/*/ +$("ul.dropdown li").hover( + function(){ + $(this).addClass("hover"); + $('ul:first',this).css('visibility','visible') + }, + + function(){ + $(this).removeClass("hover"); + $('ul:first',this).css('visibility', 'hidden'); + }); + $("ul.dropdown li ul li:has(ul)").find("a:first").append(" » "); +/* end dropdown menu code */ + }); -function contentHeight(){ -setMaxHeight($(".rightMargin,#content,.leftMargin,#col2"),$("#content")); -}; - -function setMaxHeight(group, target) { - tallest = 0; - group.each(function() { - thisHeight = $(this).height(); - if(thisHeight > tallest) { - tallest = thisHeight; - } - }); - target.height(tallest); -} - - diff --git a/templates/base.html b/templates/base.html index de1f73c..ff37cd0 100644 --- a/templates/base.html +++ b/templates/base.html @@ -3,10 +3,12 @@ + {% block title %}Troggle{% endblock %} + @@ -59,7 +61,31 @@ window.onload = contentHeight; {% endblock margins %} - +
+ + + + + + +
+{% endblock %} + diff --git a/feincms/templates/admin/feincms/tree_editor.html b/feincms/templates/admin/feincms/tree_editor.html new file mode 100644 index 0000000..bbde798 --- /dev/null +++ b/feincms/templates/admin/feincms/tree_editor.html @@ -0,0 +1,145 @@ +{% extends "admin/change_list.html" %} +{% load i18n admin_modify adminmedia mptt_tags %} + +{% block title %}{{ block.super }}{% endblock %} + +{% block extrahead %}{{ block.super }} + + + + + + + + + + + + + + + + + +{% endblock %} + +{% block content %} + +
+ {% block object-tools %} + {% if has_add_permission %} + + {% endif %} + {% endblock %} +
+ + + +
+ + + + + + + + + + + + +
{% trans "Page" %}{% trans "active" %}{% trans "in navi" %}{% trans "delete" %}
+
+ +{% endblock %} + diff --git a/feincms/templates/content/rss/content.html b/feincms/templates/content/rss/content.html new file mode 100644 index 0000000..c1eaaf9 --- /dev/null +++ b/feincms/templates/content/rss/content.html @@ -0,0 +1,8 @@ +

{{ feed_title }}

+ + + diff --git a/feincms/templatetags/__init__.py b/feincms/templatetags/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/feincms/templatetags/feincms_tags.py b/feincms/templatetags/feincms_tags.py new file mode 100644 index 0000000..da03d9f --- /dev/null +++ b/feincms/templatetags/feincms_tags.py @@ -0,0 +1,75 @@ +from django import template +from feincms.module.page.models import Page + +register = template.Library() + + +@register.simple_tag +def feincms_render_region(page, region, request): + """ + {% feincms_render_region feincms_page "main" request %} + """ + + contents = getattr(page.content, region) + + return u''.join(content.render(request=request) for content in contents) + + +@register.simple_tag +def feincms_render_content(content, request): + """ + {% feincms_render_content pagecontent request %} + """ + + return content.render(request=request) + + +class NaviLevelNode(template.Node): + """ Gets navigation based on current page OR request, dependant on choice of second parameter (of vs. from). + + Top navigation level is 1. + If navigation level + 1 > page.level, the ouput is none, because there is no well-defined sub-sub-navigation for a page. + + Example usage: + 1) {% feincms_get_navi_level 1 of page as pages %} + 2) {% feincms_get_navi_level 1 from request as pages %} + + Side-note: If not using mptt to retrieve pages, the ordering cannot be dertermined by 'id'. + Instead, as a "hack", we can sort by field 'lft', because we understand how mptt works :-) + """ + def __init__(self, level, switch, obj, dummy, varname): + self.level = long(int(level) - 1) + self.obj = template.Variable(obj) + self.varname = varname + self.switch = switch + + def render(self, context): + if self.switch == 'of': + # obj is a Page + page = self.obj.resolve(context) + else: # self.switch == 'from' + # obj is a request + page = Page.objects.from_request(self.obj.resolve(context)) + + if int(self.level) == 0: + # top level + pages = Page.objects.filter(in_navigation=True, level=long(0)).order_by('lft') + elif self.level <= page.level: + ancestor = page.get_ancestors()[int(self.level) - 1] + pages = Page.objects.filter(in_navigation=True, parent__pk=ancestor.pk).order_by('lft') + elif self.level == page.level + 1: + pages = Page.objects.filter(in_navigation=True, parent__pk=page.pk).order_by('lft') + else: + pages = [] + + context[self.varname] = pages + return '' + +@register.tag +def feincms_get_navi_level(parser, token): + try: + tag_name, level, switch, obj, dummy, varname = token.split_contents() + except ValueError: + raise template.TemplateSyntaxError, "%r tag requires exactly five arguments" % token.contents.split()[0] + return NaviLevelNode(level, switch, obj, dummy, varname) + diff --git a/feincms/templatetags/utils.py b/feincms/templatetags/utils.py new file mode 100644 index 0000000..5f3fc9d --- /dev/null +++ b/feincms/templatetags/utils.py @@ -0,0 +1,150 @@ +''' +I really hate repeating myself. These are helpers that avoid typing the +whole thing over and over when implementing additional template tags + +They help implementing tags of the form + +{% tag as var_name %} (SimpleAssignmentNode) +and +{% tag of template_var as var_name %} (SimpleAssignmentNodeWithVar) +''' + +from django import template + +def _parse_args(argstr): + try: + args = {} + for token in argstr.split(','): + k, v = token.split('=') + args[k] = v + + return args + + except TypeError: + raise template.TemplateSyntaxError('Malformed arguments') + +def do_simple_node_with_var_and_args_helper(cls): + def _func(parser, token): + try: + tag_name, of_, in_var_name, args = token.contents.split() + except ValueError: + raise template.TemplateSyntaxError + + return cls(tag_name, in_var_name, args) + + return _func + +class SimpleNodeWithVarAndArgs(template.Node): + def __init__(self, tag_name, in_var_name, args): + self.tag_name = tag_name + self.in_var = template.Variable(in_var_name) + self.args = args + + def render(self, context): + try: + instance = self.in_var.resolve(context) + except template.VariableDoesNotExist: + return '' + + return self.what(instance, _parse_args(self.args)) + +def do_simple_node_with_var_helper(cls): + def _func(parser, token): + try: + tag_name, of_, in_var_name = token.contents.split() + except ValueError: + raise template.TemplateSyntaxError + + return cls(tag_name, in_var_name) + + return _func + +class SimpleNodeWithVar(template.Node): + def __init__(self, tag_name, in_var_name): + self.tag_name = tag_name + self.in_var = template.Variable(in_var_name) + + def render(self, context): + try: + instance = self.in_var.resolve(context) + except template.VariableDoesNotExist: + return '' + + return self.what(instance) + +def do_simple_assignment_node_helper(cls): + def _func(parser, token): + try: + tag_name, as_, var_name = token.contents.split() + except ValueError: + raise template.TemplateSyntaxError + + return cls(tag_name, var_name) + + return _func + +class SimpleAssignmentNode(template.Node): + def __init__(self, tag_name, var_name): + self.tag_name = tag_name + self.var_name = var_name + + def render(self, context): + context[self.var_name] = self.what() + return '' + +def do_simple_assignment_node_with_var_helper(cls): + def _func(parser, token): + try: + tag_name, of_, in_var_name, as_, var_name = token.contents.split() + except ValueError: + raise template.TemplateSyntaxError + + return cls(tag_name, in_var_name, var_name) + + return _func + +class SimpleAssignmentNodeWithVar(template.Node): + def __init__(self, tag_name, in_var_name, var_name): + self.tag_name = tag_name + self.in_var = template.Variable(in_var_name) + self.var_name = var_name + + def render(self, context): + try: + instance = self.in_var.resolve(context) + except template.VariableDoesNotExist: + context[self.var_name] = [] + return '' + + context[self.var_name] = self.what(instance) + return '' + +def do_simple_assignment_node_with_var_and_args_helper(cls): + def _func(parser, token): + try: + tag_name, of_, in_var_name, as_, var_name, args = token.contents.split() + except ValueError: + raise template.TemplateSyntaxError + + return cls(tag_name, in_var_name, var_name, args) + + return _func + +class SimpleAssignmentNodeWithVarAndArgs(template.Node): + def __init__(self, tag_name, in_var_name, var_name, args): + self.tag_name = tag_name + self.in_var = template.Variable(in_var_name) + self.var_name = var_name + self.args = args + + def render(self, context): + try: + instance = self.in_var.resolve(context) + except template.VariableDoesNotExist: + context[self.var_name] = [] + return '' + + context[self.var_name] = self.what(instance, _parse_args(self.args)) + + return '' + diff --git a/feincms/views/__init__.py b/feincms/views/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/feincms/views/base.py b/feincms/views/base.py new file mode 100644 index 0000000..47ca887 --- /dev/null +++ b/feincms/views/base.py @@ -0,0 +1,23 @@ +from django.http import HttpResponseRedirect +from django.shortcuts import render_to_response +from django.template import RequestContext +from django.utils import translation + +from feincms.module.page.models import Page + + +def handler(request, path=None): + if path is None: + path = request.path + + page = Page.objects.page_for_path_or_404(path) + + if page.redirect_to: + return HttpResponseRedirect(page.redirect_to) + + page.setup_request(request) + + return render_to_response(page.template.path, { + 'feincms_page': page, + }, context_instance=RequestContext(request)) + diff --git a/feincms/views/decorators.py b/feincms/views/decorators.py new file mode 100644 index 0000000..e31bcdd --- /dev/null +++ b/feincms/views/decorators.py @@ -0,0 +1,16 @@ +try: + from functools import wraps +except ImportError: + from django.utils.functional import wraps + +from feincms.module.page.models import Page + + +def add_page_to_extra_context(view_func): + def inner(request, *args, **kwargs): + kwargs.setdefault('extra_context', {}) + kwargs['extra_context']['feincms_page'] = Page.objects.best_match_for_request(request) + + return view_func(request, *args, **kwargs) + return wraps(view_func)(inner) + diff --git a/feincms/views/generic/__init__.py b/feincms/views/generic/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/feincms/views/generic/create_update.py b/feincms/views/generic/create_update.py new file mode 100644 index 0000000..8ffe4c7 --- /dev/null +++ b/feincms/views/generic/create_update.py @@ -0,0 +1,8 @@ +from django.views.generic import create_update +from feincms.views.decorators import add_page_to_extra_context + + +create_object = add_page_to_extra_context(create_update.create_object) +update_object = add_page_to_extra_context(create_update.update_object) +delete_object = add_page_to_extra_context(create_update.delete_object) + diff --git a/feincms/views/generic/date_based.py b/feincms/views/generic/date_based.py new file mode 100644 index 0000000..1b0474a --- /dev/null +++ b/feincms/views/generic/date_based.py @@ -0,0 +1,12 @@ +from django.views.generic import date_based +from feincms.views.decorators import add_page_to_extra_context + + +archive_index = add_page_to_extra_context(date_based.archive_index) +archive_year = add_page_to_extra_context(date_based.archive_year) +archive_month = add_page_to_extra_context(date_based.archive_month) +archive_week = add_page_to_extra_context(date_based.archive_week) +archive_day = add_page_to_extra_context(date_based.archive_day) +archive_today = add_page_to_extra_context(date_based.archive_today) +object_detail = add_page_to_extra_context(date_based.object_detail) + diff --git a/feincms/views/generic/list_detail.py b/feincms/views/generic/list_detail.py new file mode 100644 index 0000000..6684e5a --- /dev/null +++ b/feincms/views/generic/list_detail.py @@ -0,0 +1,7 @@ +from django.views.generic import list_detail +from feincms.views.decorators import add_page_to_extra_context + + +object_list = add_page_to_extra_context(list_detail.object_list) +object_detail = add_page_to_extra_context(list_detail.object_detail) + diff --git a/feincms/views/generic/simple.py b/feincms/views/generic/simple.py new file mode 100644 index 0000000..22f1b7e --- /dev/null +++ b/feincms/views/generic/simple.py @@ -0,0 +1,6 @@ +from django.views.generic import simple +from feincms.views.decorators import add_page_to_extra_context + + +direct_to_template = add_page_to_extra_context(simple.direct_to_template) + diff --git a/parsers/surveys.py b/parsers/surveys.py index 290d550..142a2bb 100644 --- a/parsers/surveys.py +++ b/parsers/surveys.py @@ -12,6 +12,19 @@ from PIL import Image import csv import re import datetime +from save_carefully import save_carefully + +def get_or_create_placeholder(year): + """ All surveys must be related to a logbookentry. We don't have a way to + automatically figure out which survey went with which logbookentry, + so we create a survey placeholder logbook entry for each year. This + function always returns such a placeholder, and creates it if it doesn't + exist yet. + """ + lookupAttribs={'date__year':int(year), 'title':"placeholder for surveys",} + nonLookupAttribs={'text':"surveys temporarily attached to this should be re-attached to their actual trips", 'date':datetime.date(int(year),1,1)} + placeholder_logbook_entry, newly_created = save_carefully(LogbookEntry, lookupAttribs, nonLookupAttribs) + return placeholder_logbook_entry def readSurveysFromCSV(logfile=None): try: @@ -42,13 +55,16 @@ def readSurveysFromCSV(logfile=None): logfile.write("Beginning to import surveys from "+str(os.path.join(settings.SURVEYS, "Surveys.csv"))+"\n"+"-"*60+"\n") for survey in surveyreader: - walletNumberLetter = re.match(r'(?P\d*)(?P[a-zA-Z]*)',survey[header['Survey Number']]) #I hate this, but some surveys have a letter eg 2000#34a. This line deals with that. + #I hate this, but some surveys have a letter eg 2000#34a. The next line deals with that. + walletNumberLetter = re.match(r'(?P\d*)(?P[a-zA-Z]*)',survey[header['Survey Number']]) # print walletNumberLetter.groups() + year=survey[header['Year']] + surveyobj = Survey( - expedition = Expedition.objects.filter(year=survey[header['Year']])[0], + expedition = Expedition.objects.filter(year=year)[0], wallet_number = walletNumberLetter.group('number'), - + logbook_entry = get_or_create_placeholder(year), comments = survey[header['Comments']], location = survey[header['Location']] ) @@ -101,7 +117,8 @@ def parseSurveyScans(year, logfile=None): if type(surveyNumber)==types.TupleType: surveyNumber=surveyNumber[0] try: - survey=Survey.objects.get_or_create(wallet_number=surveyNumber, expedition=year)[0] + placeholder=get_or_create_placeholder(year=int(year.year)) + survey=Survey.objects.get_or_create(wallet_number=surveyNumber, expedition=year, defaults={'logbook_entry':placeholder})[0] except Survey.MultipleObjectsReturned: survey=Survey.objects.filter(wallet_number=surveyNumber, expedition=year)[0] file=os.path.join(year.year, surveyFolder, scan) diff --git a/settings.py b/settings.py index a3f5dd4..45d1867 100644 --- a/settings.py +++ b/settings.py @@ -86,6 +86,8 @@ INSTALLED_APPS = ( 'troggle.profiles', 'troggle.expo', 'troggle.imagekit', + 'mptt', #This is django-mptt (modifed preorder tree traversal) which allows the tree structure of subcaves. + 'feincms' #This is a little content management app that does the javascript admin page for mptt. ) from localsettings import * #localsettings needs to take precedence. Call it to override any existing vars. \ No newline at end of file diff --git a/templates/survey.html b/templates/survey.html index 290cd97..4b6ebbc 100644 --- a/templates/survey.html +++ b/templates/survey.html @@ -67,7 +67,7 @@
- +{% if current_survey %}

Choose a wallet number

+Wipe entire database and recreate tables:

Import (non-destructive):

From 9f13a584984c56dc68a090a0d55575b2cdbedaf5 Mon Sep 17 00:00:00 2001 From: substantialnoninfringinguser Date: Thu, 21 May 2009 22:55:08 +0100 Subject: [PATCH 026/449] [svn] minor logfile mistake --- databaseReset.py | 4 ++-- parsers/cavetab.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/databaseReset.py b/databaseReset.py index 3ef98c3..8000445 100644 --- a/databaseReset.py +++ b/databaseReset.py @@ -8,7 +8,7 @@ from django.db import connection from django.contrib.auth.models import User from django.http import HttpResponse -logfile=settings.LOGFILE + def reload_db(): cursor = connection.cursor() @@ -37,7 +37,7 @@ def import_people(): parsers.people.LoadPersonsExpos() def import_logbooks(): - logfile.write('\nBegun importing logbooks at ' + time.asctime() +'\n'+'-'*60) + settings.LOGFILE.write('\nBegun importing logbooks at ' + time.asctime() +'\n'+'-'*60) import parsers.logbooks parsers.logbooks.LoadLogbooks() diff --git a/parsers/cavetab.py b/parsers/cavetab.py index 89e7728..187c4ac 100644 --- a/parsers/cavetab.py +++ b/parsers/cavetab.py @@ -136,7 +136,7 @@ def html_to_wiki(text): text2 = "" return out -def LoadCaveTab(logfile=None): +def LoadCaveTab(logfile=settings.LOGFILE): cavetab = open(os.path.join(settings.EXPOWEB, "noinfo", "CAVETAB2.CSV"),'rU') caveReader = csv.reader(cavetab) caveReader.next() # Strip out column headers From 49ed6495ad296283ee0977777b2b2d8a72bc23af Mon Sep 17 00:00:00 2001 From: substantialnoninfringinguser Date: Fri, 22 May 2009 01:50:16 +0100 Subject: [PATCH 027/449] [svn] * Make subcave urls work. * Add json and xml download to admin. --- databaseReset.py | 8 +++++++- expo/admin.py | 26 +++++++++++++++++++++++++- expo/models.py | 18 +++++++++++++++--- expo/views_caves.py | 4 ++-- media/css/main3.css | 3 ++- templates/qm.html | 12 ++++++++++++ templates/subcave.html | 15 +++++++++++++++ 7 files changed, 78 insertions(+), 8 deletions(-) diff --git a/databaseReset.py b/databaseReset.py index 8000445..e9092ce 100644 --- a/databaseReset.py +++ b/databaseReset.py @@ -37,7 +37,13 @@ def import_people(): parsers.people.LoadPersonsExpos() def import_logbooks(): - settings.LOGFILE.write('\nBegun importing logbooks at ' + time.asctime() +'\n'+'-'*60) + # The below line was causing errors I didn't understand (it said LOGFILE was a string), and I couldn't be bothered to figure + # what was going on so I just catch the error with a try. - AC 21 May + try: + settings.LOGFILE.write('\nBegun importing logbooks at ' + time.asctime() +'\n'+'-'*60) + except: + pass + import parsers.logbooks parsers.logbooks.LoadLogbooks() diff --git a/expo/admin.py b/expo/admin.py index 3b727d2..9f804c2 100644 --- a/expo/admin.py +++ b/expo/admin.py @@ -4,6 +4,8 @@ from feincms.admin import editor from django.forms import ModelForm import django.forms as forms from expo.forms import LogbookEntryForm +from django.http import HttpResponse +from django.core import serializers #from troggle.reversion.admin import VersionAdmin #django-reversion version control #overriding admin save so we have the new since parsing field @@ -73,7 +75,6 @@ class CaveAdmin(TroggleModelAdmin): class SubcaveAdmin(editor.TreeEditorMixin,TroggleModelAdmin): pass - admin.site.register(Photo) admin.site.register(Subcave, SubcaveAdmin) admin.site.register(Cave, CaveAdmin) @@ -94,6 +95,29 @@ admin.site.register(QM, QMAdmin) admin.site.register(Survey, SurveyAdmin) admin.site.register(ScannedImage) +def export_as_json(modeladmin, request, queryset): + response = HttpResponse(mimetype="text/json") + response['Content-Disposition'] = 'attachment; filename=troggle_output.xml' + serializers.serialize("json", queryset, stream=response) + return response + +admin.site.add_action(export_as_json) + +def export_as_xml(modeladmin, request, queryset): + response = HttpResponse(mimetype="text/xml") + response['Content-Disposition'] = 'attachment; filename=troggle_output.xml' + return response + +admin.site.add_action(export_as_xml) + +def export_as_python(modeladmin, request, queryset): + response = HttpResponse(mimetype="text/python") + response['Content-Disposition'] = 'attachment; filename=troggle_output.py' + serializers.serialize("json", queryset, stream=response) + return response + +admin.site.add_action(export_as_python) + try: mptt.register(Subcave, order_insertion_by=['name']) except mptt.AlreadyRegistered: diff --git a/expo/models.py b/expo/models.py index ece0e26..70f8381 100644 --- a/expo/models.py +++ b/expo/models.py @@ -466,13 +466,25 @@ class Entrance(TroggleModel): class Subcave(TroggleModel): description = models.TextField(blank=True, null=True) title = models.CharField(max_length=200, ) - cave = models.ForeignKey('Cave', blank=True, null=True, help_text="Only the top-level subcave should be linked to a cave") + cave = models.ForeignKey('Cave', blank=True, null=True, help_text="Only the top-level subcave should be linked to a cave!") parent = models.ForeignKey('self', null=True, blank=True, related_name='children') #adjoining = models.ManyToManyField('Subcave',blank=True, null=True,) legacy_description_path = models.CharField(max_length=600, blank=True, null=True) def __unicode__(self): return self.title - + + def get_absolute_url(self): + + ancestor_titles='/'.join([subcave.title for subcave in self.get_ancestors()]) + if ancestor_titles: + res = '/'.join((self.get_root().cave.get_absolute_url(), ancestor_titles, self.title)) + + else: + res = '/'.join((self.get_root().cave.get_absolute_url(), self.title)) + + return res + +# This was the old way, before we were using django-mptt # def get_absolute_url(self): # urlString=self.name @@ -524,7 +536,7 @@ class QM(TroggleModel): def get_absolute_url(self): #return settings.URL_ROOT + '/cave/' + self.found_by.cave.kataster_number + '/' + str(self.found_by.date.year) + '-' + '%02d' %self.number - return settings.URL_ROOT + reverse('qm',kwargs={'cave_id':self.cave.kataster_number,'year':self.found_by.date.year,'qm_id':self.number,'grade':self.grade}) + return settings.URL_ROOT + reverse('qm',kwargs={'cave_id':self.found_by.cave.kataster_number,'year':self.found_by.date.year,'qm_id':self.number,'grade':self.grade}) def get_next_by_id(self): return QM.objects.get(id=self.id+1) diff --git a/expo/views_caves.py b/expo/views_caves.py index 51c9e11..64ec6e2 100644 --- a/expo/views_caves.py +++ b/expo/views_caves.py @@ -57,11 +57,11 @@ def subcave(request, cave_id, subcave): subcaveSeq=re.findall('(?:/)([^/]*)',subcave) print subcaveSeq cave=models.Cave.objects.get(kataster_number = cave_id) - subcave=models.Subcave.objects.get(name=subcaveSeq[0], cave=cave) + subcave=models.Subcave.objects.get(title=subcaveSeq[0], cave=cave) if len(subcaveSeq)>1: for subcaveUrlSegment in subcaveSeq[1:]: if subcaveUrlSegment: - subcave=subcave.children.get(name=subcaveUrlSegment) + subcave=subcave.children.get(title=subcaveUrlSegment) print subcave return render_response(request,'subcave.html', {'subcave': subcave,'cave':cave}) diff --git a/media/css/main3.css b/media/css/main3.css index 2105050..0d67fea 100644 --- a/media/css/main3.css +++ b/media/css/main3.css @@ -262,7 +262,8 @@ div#content { margin-right: 120px; padding-top: 10px; padding-left: 5em; - padding-right: 5em; + padding-right: 5em; + padding-bottom:5em; background:#CCC; } diff --git a/templates/qm.html b/templates/qm.html index 06e6e0e..52413f0 100644 --- a/templates/qm.html +++ b/templates/qm.html @@ -1,5 +1,6 @@ {% extends "base.html" %} {% load wiki_markup %} +{% load link %} {% block title %} QM: {{qm|wiki_to_html_short}} {% endblock %} @@ -19,6 +20,17 @@ {% block content %} + +
+ +

Related items

+ +Parent cave: {{qm.found_by.cave|link}} +(todo: add parent survey and parent subcave) + + +
+

Location

{{qm.location_description}} diff --git a/templates/subcave.html b/templates/subcave.html index c635833..4cd9be0 100644 --- a/templates/subcave.html +++ b/templates/subcave.html @@ -1,9 +1,24 @@ {% extends "cavebase.html" %} {% load wiki_markup %} +{% load mptt_tags %} {% block title %} Subcave {{subcave}} {% endblock title %} {% block editLink %}Edit subcave {{subcave|wiki_to_html_short}}{% endblock %} + +{% block contentheader %} + {{subcave.title}} +{% endblock contentheader %} + + + {% block content %} +ok here comes the drilldown
+{% drilldown_tree_for_node subcave as drilldown %} +{% for each in drilldown %} +{{ each }}> +{% endfor %} + +

{{subcave}}

{{subcave.description}} From 3784eb97209c00da703036031fa5127197ccef38 Mon Sep 17 00:00:00 2001 From: substantialnoninfringinguser Date: Fri, 22 May 2009 02:54:09 +0100 Subject: [PATCH 028/449] [svn] - Remove feature (admin JSON / XML downloads) which won't work until we have django 1.1 installed (works on my SVN version, but not on seagrass debian package version). - Copy feincms media to project so that we don't have to serve it separately. Also useful because we may want to customize it. --- expo/admin.py | 8 +- media/feincms/css/jquery.alerts.css | 54 +++ media/feincms/css/jquery.treeTable.css | 47 +++ media/feincms/css/layout.css | 242 +++++++++++++ media/feincms/helper.js | 88 +++++ media/feincms/img/arrow_down.gif | Bin 0 -> 224 bytes media/feincms/img/arrow_right.gif | Bin 0 -> 222 bytes media/feincms/img/circle.gif | Bin 0 -> 198 bytes media/feincms/img/cut.png | Bin 0 -> 648 bytes media/feincms/img/default-bg.gif | Bin 0 -> 844 bytes media/feincms/img/door.png | Bin 0 -> 412 bytes media/feincms/img/help.gif | Bin 0 -> 1582 bytes media/feincms/img/icon_deletelink.gif | Bin 0 -> 181 bytes media/feincms/img/icon_move.gif | Bin 0 -> 185 bytes media/feincms/img/important.gif | Bin 0 -> 1492 bytes media/feincms/img/info.gif | Bin 0 -> 1487 bytes media/feincms/img/nav-bg.gif | Bin 0 -> 273 bytes media/feincms/img/page_white_copy.png | Bin 0 -> 309 bytes media/feincms/img/page_white_delete.png | Bin 0 -> 536 bytes media/feincms/img/page_white_edit.png | Bin 0 -> 618 bytes media/feincms/img/page_white_paste.png | Bin 0 -> 620 bytes media/feincms/img/title.gif | Bin 0 -> 317 bytes media/feincms/img/toggle-collapse-dark.png | Bin 0 -> 2886 bytes media/feincms/img/toggle-collapse-light.png | Bin 0 -> 2864 bytes media/feincms/img/toggle-expand-dark.png | Bin 0 -> 2894 bytes media/feincms/img/toggle-expand-light.png | Bin 0 -> 2863 bytes media/feincms/jquery.alerts.css | 54 +++ media/feincms/jquery.alerts.js | 235 ++++++++++++ media/feincms/jquery.json-1.3.js | 156 ++++++++ media/feincms/jquery.livequery.js | 250 +++++++++++++ media/feincms/jquery.treeTable.js | 383 ++++++++++++++++++++ media/feincms/layout.css | 183 ++++++++++ media/feincms/listener.js | 90 +++++ 33 files changed, 1786 insertions(+), 4 deletions(-) create mode 100644 media/feincms/css/jquery.alerts.css create mode 100644 media/feincms/css/jquery.treeTable.css create mode 100644 media/feincms/css/layout.css create mode 100644 media/feincms/helper.js create mode 100644 media/feincms/img/arrow_down.gif create mode 100644 media/feincms/img/arrow_right.gif create mode 100644 media/feincms/img/circle.gif create mode 100644 media/feincms/img/cut.png create mode 100644 media/feincms/img/default-bg.gif create mode 100644 media/feincms/img/door.png create mode 100644 media/feincms/img/help.gif create mode 100644 media/feincms/img/icon_deletelink.gif create mode 100644 media/feincms/img/icon_move.gif create mode 100644 media/feincms/img/important.gif create mode 100644 media/feincms/img/info.gif create mode 100644 media/feincms/img/nav-bg.gif create mode 100644 media/feincms/img/page_white_copy.png create mode 100644 media/feincms/img/page_white_delete.png create mode 100644 media/feincms/img/page_white_edit.png create mode 100644 media/feincms/img/page_white_paste.png create mode 100644 media/feincms/img/title.gif create mode 100644 media/feincms/img/toggle-collapse-dark.png create mode 100644 media/feincms/img/toggle-collapse-light.png create mode 100644 media/feincms/img/toggle-expand-dark.png create mode 100644 media/feincms/img/toggle-expand-light.png create mode 100644 media/feincms/jquery.alerts.css create mode 100644 media/feincms/jquery.alerts.js create mode 100644 media/feincms/jquery.json-1.3.js create mode 100644 media/feincms/jquery.livequery.js create mode 100644 media/feincms/jquery.treeTable.js create mode 100644 media/feincms/layout.css create mode 100644 media/feincms/listener.js diff --git a/expo/admin.py b/expo/admin.py index 9f804c2..247b4b1 100644 --- a/expo/admin.py +++ b/expo/admin.py @@ -101,22 +101,22 @@ def export_as_json(modeladmin, request, queryset): serializers.serialize("json", queryset, stream=response) return response -admin.site.add_action(export_as_json) def export_as_xml(modeladmin, request, queryset): response = HttpResponse(mimetype="text/xml") response['Content-Disposition'] = 'attachment; filename=troggle_output.xml' return response -admin.site.add_action(export_as_xml) - def export_as_python(modeladmin, request, queryset): response = HttpResponse(mimetype="text/python") response['Content-Disposition'] = 'attachment; filename=troggle_output.py' serializers.serialize("json", queryset, stream=response) return response -admin.site.add_action(export_as_python) +#These require django newer than 1.0 (SVN version) so we'll have to wait. +#admin.site.add_action(export_as_xml) +#admin.site.add_action(export_as_json) +#admin.site.add_action(export_as_python) try: mptt.register(Subcave, order_insertion_by=['name']) diff --git a/media/feincms/css/jquery.alerts.css b/media/feincms/css/jquery.alerts.css new file mode 100644 index 0000000..0b5e1e8 --- /dev/null +++ b/media/feincms/css/jquery.alerts.css @@ -0,0 +1,54 @@ +#popup_container { + font-family: Arial, sans-serif; + font-size: 12px; + min-width: 300px; /* Dialog will be no smaller than this */ + max-width: 600px; /* Dialog will wrap after this width */ + background: #FFF; + border: solid 1px #666; + color: #000; +} + +#popup_title { + font-size: 14px; + font-weight: bold; + text-align: center; + line-height: 1.75em; + color: #666; + background: #eee url(images/title.gif) top repeat-x; + border: solid 1px #FFF; + border-bottom: solid 1px #666; + cursor: default; + padding: 0em; + margin: 0em; +} + +#popup_content { + background: 16px 16px no-repeat url(../img/info.gif); + padding: 1em 1.75em; + margin: 0em; +} + +#popup_content.alert { + background-image: url(../img/info.gif); +} + +#popup_content.confirm { + background-image: url(../img/important.gif); +} + +#popup_content.prompt { + background-image: url(../img/help.gif); +} + +#popup_message { + padding-left: 48px; +} + +#popup_panel { + text-align: center; + margin: 1em 0em 0em 1em; +} + +#popup_prompt { + margin: .5em 0em; +} \ No newline at end of file diff --git a/media/feincms/css/jquery.treeTable.css b/media/feincms/css/jquery.treeTable.css new file mode 100644 index 0000000..5305653 --- /dev/null +++ b/media/feincms/css/jquery.treeTable.css @@ -0,0 +1,47 @@ +/* jQuery TreeTable Core 2.0 stylesheet + * + * This file contains styles that are used to display the tree table. Each tree + * table is assigned the +treeTable+ class. + * ========================================================================= */ + +/* jquery.treeTable.collapsible + * ------------------------------------------------------------------------- */ +.treeTable tr td .expander { + background-position: left center; + background-repeat: no-repeat; + cursor: pointer; + padding: 0; + zoom: 1; /* IE7 Hack */ +} + +.treeTable tr.collapsed td .expander { + background-image: url(../img/toggle-expand-dark.png); +} + +.treeTable tr.expanded td .expander { + background-image: url(../img/toggle-collapse-dark.png); +} + +/* jquery.treeTable.sortable + * ------------------------------------------------------------------------- */ +.treeTable tr.selected, .treeTable tr.accept { + /*background-color: #7799ff; + color: #fff;*/ +} + +.treeTable tr.append { + border-bottom: 2px solid #7799ff; +} + +.treeTable tr.collapsed.selected td .expander, .treeTable tr.collapsed.accept td .expander { + /*background-image: url(../img/toggle-expand-light.png);*/ +} + +.treeTable tr.expanded.selected td .expander, .treeTable tr.expanded.accept td .expander { + /*background-image: url(../img/toggle-collapse-light.png);*/ +} + +.treeTable .ui-draggable-dragging { + color: #000; + z-index: 1; +} \ No newline at end of file diff --git a/media/feincms/css/layout.css b/media/feincms/css/layout.css new file mode 100644 index 0000000..d7be020 --- /dev/null +++ b/media/feincms/css/layout.css @@ -0,0 +1,242 @@ + +#overview { + background-color:none; + padding:5px 0px 0px 5px; + margin:10px 0px 0px 10px; +} + +#content { + background-color:#fff; + margin-bottom:0px; + width:745px; +} + +.navi_tab { + float:left; + padding: 3px 10px 3px 10px; + cursor:pointer; + border: 1px solid #aaa; + border-bottom:none; + min-width:100px; +} +.tab_active { + height:16px; + font-weight: bold; + background-image:url('../img/default-bg.gif'); + color: white; +} +.tab_inactive { + margin-top:4px; + height:12px; + font-weight: normal; + background-image:none; + color: black; +} +#main_wrapper {margin-left:15px;} +#main { + clear:both; + padding: 10px 10px 10px 10px; + border: 1px solid #aaa; +} +.panel { + display:none; + position:relative; + padding-bottom:50px; +} + + +lab { font-weight: bold; margin-right:5px;} +span { margin-right: 5px;} + +.order-machine { + margin-bottom:10px; +} + +.order-item { + margin: 5px 15px 10px 5px; + background: #eee; + position:relative; + width:700px; + min-height:24px; + border: 1px solid #678; +} +.order-item span { + font-weight:bold; + margin-left:5px; +} +.order-item .handle { + float: left; + margin-top:2px; + cursor: move; +} +.item-content { + padding: 0px 10px 0px 15px; + margin-right:5px; + clear:both; + margin-left:10px; +} +.item-header { + padding: 3px 10px 3px 15px; + margin-left:10px; + margin-top:1px; +} +.item-header span { + margin-right:10px; + float:left; + font-weight: strong; +} +.item-delete { + cursor:pointer; + float:right; + margin:3px 0px 0px 5px; +} +.active-item { + border: 1px solid #00a; + background-color:#def; +} +.button { + margin:5px; padding:5px; + font-weight: bold; + background-image:url('../img/nav-bg.gif'); + cursor:pointer; + border: 1px solid #678; + width:30px; +} +th { vertical-align: middle; } + +#test-log { padding: 5px; border: 1px solid #ccc; } + +#overview span { margin-right:15px; margin-left:5px;} + +.richtextcontent {display:none;} + +textarea { + width: 100%; + margin-top:5px; + margin-bottom:5px; +} + +.item-minimize { + width: 15px; + height:15px; + float: left; + cursor: pointer; + margin-left:-17px; +} + +.item-minimize-disabled { + width: 15px; + height:15px; + float: left; + margin-left:-17px; +} + +.machine-control { + height:50px; + padding: 5px 0px 5px 0px; + border: 1px solid #678; + background-color:#ddd; + width:728px; + position:absolute; + left:-11px; + bottom:-21px; +} + +.control-unit { + float:left; + padding-left:15px; + padding-right:10px; + border-right: 1px solid #678; +} + +.control-unit span { + font-weight:bold; + margin-left:5px; +} + +hr { + color: #f00; + background-color: #bbb; + height: 1px; + margin-top: 5px; + margin-bottom: 5px; +} + +.empty-machine-msg { + margin:10px 0px 5px 300px; + font-style: italic; + font-size:14px; + color:#999; +} + +td span select { + width:600px; +} + +.popup_bg { + width:100%; height:100%; + background-color:white; + opacity: 0.5; + filter:alpha(opacity=50); + position:absolute; + top:0px; left:0px; +} + +#sitetree { + margin-left: 20px; + margin-top: 20px; + padding: 0px; +} + +.title-col { + position:relative; + z-index:2; + background-color:none; +} +td div.wrap { + border: 0px solid #000; + margin: -5px; + padding: 4px; + position:relative; +} + +.insert-as-child { + position:absolute; + bottom:-3px; + left: 15px; + height:15px; + width:100%; + border: 0px solid #0f0; +} +.insert-as-sibling { + position:absolute; + top:0px; + left: 15px; + height:10px; + width:100%; + border: 0px solid #f00; +} + +.nohover { + background-color: #eef; +} + +.flash { + background-color: #afa; +} + +.hover-as-child { + background-color:#cdf; +} + +.hover-as-sibling { + border: 1px solid #aee; +} + +.move-node { + cursor: move; + margin-left:5px; +} +.del-page { + cursor: pointer; +} diff --git a/media/feincms/helper.js b/media/feincms/helper.js new file mode 100644 index 0000000..58ca3eb --- /dev/null +++ b/media/feincms/helper.js @@ -0,0 +1,88 @@ +function region_append(region, obj, modname) { + var wrp = []; + wrp.push('

'); + if (obj.children(":visible").length > 0) + wrp.push('
'); + else + wrp.push('
'); + wrp.push('' + modname + ''); + wrp.push(''); + wrp.push('
'); + + $("#"+REGIONS[region]+"_body").children(".order-machine").append(wrp.join("")) + .children(".order-item:last").children(".item-content").append(obj); +} + +function create_new_from_form(form, modvar, last_id) { + var new_form = form.html().replace( + new RegExp(modvar+'-'+last_id, 'g'), + modvar+'-'+(last_id+1)); + new_form = '
'+new_form+'
'; + $("#"+modvar+"_set").append(new_form); +} + +function get_item_field_value(item,field) { + // item: DOM object created by 'region_append' function + // field: "order-field" | "delete-field" | "region-field" + if (field=="delete-field") + return item.find("."+field).attr("checked"); + else + return item.find("."+field).val(); +} + +function set_item_field_value(item,field, value) { + // item: DOM object created by 'region_append' function + // field: "order-field" | "delete-field" | "region-field" + if (field=="delete-field") + item.find("."+field).attr("checked",value); + else if (field=="region-choice-field") { + var old_region_id = REGION_MAP.indexOf(item.find("."+field).val()); + item.find("."+field).val(REGION_MAP[value]); + + old_region_item = $("#"+REGIONS[old_region_id]+"_body"); + old_region_item.children(".empty-machine-msg").hide(); + if (old_region_item.children(".order-machine").children().length == 0) + old_region_item.children(".empty-machine-msg").show(); + + new_region_item = $("#"+REGIONS[value]+"_body"); + new_region_item.children(".empty-machine-msg").hide(); + } + else + item.find("."+field).val(value); +} + +function move_item (region_id, item) { + poorify_rich(item); + $("#"+REGIONS[region_id]+"_body").children(".order-machine").append(item); + set_item_field_value(item, "region-choice-field", region_id); + richify_poor(item); +} + +function poorify_rich(item){ + item.children(".item-content").hide(); + if (item.find("div[id^=richtext]").length > 0) { + var editor_id = item.find(".mceEditor").prev().attr("id"); + tinyMCE.execCommand('mceRemoveControl',false,editor_id); + } +} +function richify_poor(item){ + item.children(".item-content").show(); + if (item.find("div[id^=richtext]").length > 0) { + var editor_id = item.find('textarea[name*=richtext]:visible').attr("id"); + tinyMCE.execCommand('mceAddControl',false,editor_id); + } +} + +function zucht_und_ordnung(move_item) { + for (var i=0; iWgn z=v87$KiRTG&ES}e~VR*_055oP(xoWZd)TJT|%Wy?Wgn z=v;-0ZV%M^QZnbsGibcva1oud z#o<`y)#f>-&mZ?YuYCSHREBN(+K43#YD!6k&6Ofkf_<796uz2?A6sBOB|?m8lc#9F N;yY`1Xs|F?0{{-%N}>P& literal 0 HcmV?d00001 diff --git a/media/feincms/img/circle.gif b/media/feincms/img/circle.gif new file mode 100644 index 0000000000000000000000000000000000000000..fa75af5100c57da2217745ecde8d70bd2cbdd255 GIT binary patch literal 198 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 literal 0 HcmV?d00001 diff --git a/media/feincms/img/icon_deletelink.gif b/media/feincms/img/icon_deletelink.gif new file mode 100644 index 0000000000000000000000000000000000000000..72523e3a3ba1446c8f768c157cea642119a02741 GIT binary patch literal 181 zcmZ?wbhEHbc&kkH2hg{xUB9fxq8%JG(R5 zT3?Eao`!{eDJnj1U~tLY{9|s;X>G0VWo1vp!yowhEs~V{|NlP&4xspxg^__loIwX9 z53-Yi)#yQKgxah|7So2 zia%Kx85pD(bU?x&I~iEb7N~U{$(Y1hyvU6Kb8z+epk9;rNu literal 0 HcmV?d00001 diff --git a/media/feincms/img/important.gif b/media/feincms/img/important.gif new file mode 100644 index 0000000000000000000000000000000000000000..41d49438fd08230095c699b2d6f80593f3aa8667 GIT binary patch literal 1492 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 literal 0 HcmV?d00001 diff --git a/media/feincms/img/nav-bg.gif b/media/feincms/img/nav-bg.gif new file mode 100644 index 0000000000000000000000000000000000000000..f8402b809dc1efec80db6e466d789f88429a79a8 GIT binary patch literal 273 zcmZ?wbhEHb6l9QRIKsg2{rmT~Z{I$D{`}ReSAYNh{q^hDhYue(@t*9{u_A=gpfpKY#xG_U+rVXV3oq`}hC||i=Q&8(m$()z5YF*CjUEw001<4;4tjrTwH+&%FA_x~E6rskH`CdQ7= zuI>)uzWxal`(>Zw+Pr1!Chi?O LckSMx$Y2cs9wnNk literal 0 HcmV?d00001 diff --git a/media/feincms/img/page_white_copy.png b/media/feincms/img/page_white_copy.png new file mode 100644 index 0000000000000000000000000000000000000000..a9f31a278e17993d8d4e13beac2f9d5f7b42d08f GIT binary patch literal 309 zcmV-50m}Y~P)(?iiXTHIMmcoLoO94I8;j@ zv^2DJ5#orqydFJX|Gm$_Bi_vyew+j6{r}$Qc@D1%fQqeAhJj)1!z4pP83k2MV2~s! zSt^w(<#HLFVBg_#xz1W8ioi(WY&Hu~6zil?DI^jJgu`K35(hkP)H%@Imesbg#5!Ps_$Ni*SiR8&sKb9?M`0-mH)gtg&YgRX#*TXz@Z+| z;|2H@xzE0TfuORhuO2k6#K8#sW^J`mQ0+E@$K`QkFV+DTlI$w{GJ;zid{*v9xeIe_ z$|Bp`@iKkgoFK3{4Z)#DWKV~W4K@5WZN+Ql_7%YxNqSx7%cWud&cX>)_PvD*UzxZg a%Kia9Rjz_59@~-t0000$>5Y&axjp2O=VLu>*f>1L;s0)kkvKC!*u?s6CVL=HJ6oP~pNfZc; zsKr=bq;7MITw8NXw{SZm%59TId2x_9BQ zV86`NuvGI!>o^V!Na!=$7GJE{Cq`b+XwknM{UcGHFTTfmuS+ zm-zYC!P3+zmY;SG$?!fYkOih`QYaLxyF}A86h$GGN}kFj)_o*0e zjPMP%zTG7FYMAfO2Nn1D`D0Cj?Wl>5q%@CE10nX)KxpNmwk+!IWkzywiYD( zqUXiYYIq3qcRyMGJ;IY`(Gz~E$J$zu2+R{)xGlE*88b3WK6V*J>}2iPY1HH|tER0W z_+^^FdppY?o)Gt5M2`%xwRDH@R3G}^i1l4|6uchm0X0f!@&YdVLB5K&dd7Rv{)DXX zt^&vP;}kqj3f>94j+4xd93>s|Q!Ezi>?r8(Il$P}PFxSqu{d*!Y%*#cX(R0f|Juz# z3o0_xI14Al->1uky@W-rCI_%l&>4A_ab^avY?n0hpS-#mn_4{O$e%cm-@NH=3`90Wq+3`~HKArSdfX`&Z12 z(CY$VW-MNtXX4xy%yUeE?}*~0-|iByA@ZrwXgph4S*bhcc5{HB!DFVm_v}P*g7+Q~K}7K0lcp(^N@X>U zV`{ZpeIf${R6Hgg4FL^`X$Eu75k(PE6ycl$AW0Ic)#@rR7Z(7;V?i-dR1K935Jgcx zPfkwK>2wGokf!Nih^ARp6-6arYFG#(9Ta!x93nFEjoA==z(g?#sDg?Owk?Mg7K+>l zWYsf(<`#+$h9Sp6gFOg_dd+80SkUpk&xM7h0`Sov9W73spU;GP073|VfZ&Gd$J$*0<~TV5aPS|qWH57|VJz+d0000aX1K}04b%WX}Ye94MPwF)JDW42FALk8HT|avmJZ1Lc3i`yOJcip7+J`vMh_D zG|8yn^VidsEfmtRdOgm? IU-!VyACo}9@&Et; literal 0 HcmV?d00001 diff --git a/media/feincms/img/toggle-collapse-dark.png b/media/feincms/img/toggle-collapse-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..76577a57a23105b4c821b851902cda145e348d9c GIT binary patch literal 2886 zcmV-M3%T@(P)KLZ*U+IBfRsybQWXdwQbLP>6pAqfylh#{fb6;Z(vMMVS~$e@S=j*ftg6;Uhf59&ghTmgWD0l;*T zI709Y^p6lP1rIRMx#05C~cW=H_Aw*bJ-5DT&Z2n+x)QHX^p z00esgV8|mQcmRZ%02D^@S3L16t`O%c004NIvOKvYIYoh62rY33S640`D9%Y2D-rV&neh&#Q1i z007~1e$oCcFS8neI|hJl{-P!B1ZZ9hpmq0)X0i`JwE&>$+E?>%_LC6RbVIkUx0b+_+BaR3cnT7Zv!AJxW zizFb)h!jyGOOZ85F;a?DAXP{m@;!0_IfqH8(HlgRxt7s3}k3K`kFu>>-2Q$QMFfPW!La{h336o>X zu_CMttHv6zR;&ZNiS=X8v3CR#fknUxHUxJ0uoBa_M6WNWeqIg~6QE69c9o#eyhGvpiOA@W-aonk<7r1(?fC{oI5N*U!4 zfg=2N-7=cNnjjOr{yriy6mMFgG#l znCF=fnQv8CDz++o6_Lscl}eQ+l^ZHARH>?_s@|##Rr6KLRFA1%Q+=*RRWnoLsR`7U zt5vFIcfW3@?wFpwUVxrVZ>QdQz32KIeJ}k~{cZZE^+ya? z2D1z#2HOnI7(B%_ac?{wFUQ;QQA1tBKtrWrm0_3Rgps+?Jfqb{jYbcQX~taRB;#$y zZN{S}1|}gUOHJxc?wV3fxuz+mJ4`!F$IZ;mqRrNsHJd##*D~ju=bP7?-?v~|cv>vB zsJ6IeNwVZxrdjT`yl#bBIa#GxRa#xMMy;K#CDyyGyQdMSxlWT#tDe?p!?5wT$+oGt z8L;Kp2HUQ-ZMJ=3XJQv;x5ci*?vuTfeY$;({XGW_huIFR9a(?@3)XSs8O^N5RyOM=TTmp(3=8^+zpz2r)C z^>JO{deZfso3oq3?Wo(Y?l$ge?uXo;%ru`Vo>?<<(8I_>;8Eq#KMS9gFl*neeosSB zfoHYnBQIkwkyowPu(zdms`p{<7e4kra-ZWq<2*OsGTvEV%s0Td$hXT+!*8Bnh2KMe zBmZRodjHV?r+_5^X9J0WL4jKW`}lf%A-|44I@@LTvf1rHjG(ze6+w@Jt%Bvjts!X0 z?2xS?_ve_-kiKB_KiJlZ$9G`c^=E@oNG)mWWaNo-3TIW8)$Hg0Ub-~8?KhvJ>$ z3*&nim@mj(aCxE5!t{lw7O5^0EIO7zOo&c6l<+|iDySBWCGrz@C5{St!X3hAA}`T4 z(TLbXTq+(;@<=L8dXnssyft|w#WSTW<++3>sgS%(4NTpeI-VAqb|7ssJvzNHgOZVu zaYCvgO_R1~>SyL=cFU|~g|hy|Zi}}s9+d~lYqOB71z9Z$wnC=pR9Yz4DhIM>Wmjgu z&56o6maCpC&F##y%G;1PobR9i?GnNg;gYtchD%p19a!eQtZF&3JaKv33gZ<8D~47E ztUS1iwkmDaPpj=$m#%)jCVEY4fnLGNg2A-`YwHVD3gv};>)hAvT~AmqS>Lr``i7kw zJ{5_It`yrBmlc25DBO7E8;5VoznR>Ww5hAaxn$2~(q`%A-YuS64wkBy=9dm`4cXeX z4c}I@?e+FW+b@^RDBHV(wnMq2zdX3SWv9u`%{xC-q*U}&`cyXV(%rRT*Z6MH?i+i& z_B8C(+grT%{XWUQ+f@NoP1R=AW&26{v-dx)iK^-Nmiuj8txj!m?Z*Ss1N{dh4z}01 z)YTo*JycSU)+_5r4#yw9{+;i4Ee$peRgIj+;v;ZGdF1K$3E%e~4LaI(jC-u%2h$&R z9cLXcYC@Xwnns&bn)_Q~Te?roKGD|d-g^8;+aC{{G(1^(O7m37Y1-+6)01cN&y1aw zoqc{T`P^XJqPBbIW6s}d4{z_f5Om?vMgNQEJG?v2T=KYd^0M3I6IZxbny)%vZR&LD zJpPl@Psh8QyPB@KTx+@RdcC!KX7}kEo;S|j^u2lU7XQ}Oo;f|;z4Ll+_r>@1-xl3| zawq-H%e&ckC+@AhPrP6BKT#_XdT7&;F71j}Joy zkC~6lh7E@6o;W@^IpRNZ{ptLtL(gQ-CY~4mqW;US7Zxvm_|@yz&e53Bp_lTPlfP|z zrTyx_>lv@x#=^!PzR7qqF<$gm`|ZJZ+;<)Cqu&ot2z=0000WV@Og>004R=004l4008;_004mL004C`008P>0026e000+nl3&F} z0001SNklKLZ*U+IBfRsybQWXdwQbLP>6pAqfylh#{fb6;Z(vMMVS~$e@S=j*ftg6;Uhf59&ghTmgWD0l;*T zI709Y^p6lP1rIRMx#05C~cW=H_Aw*bJ-5DT&Z2n+x)QHX^p z00esgV8|mQcmRZ%02D^@S3L16t`O%c004NIvOKvYIYoh62rY33S640`D9%Y2D-rV&neh&#Q1i z007~1e$oCcFS8neI|hJl{-P!B1ZZ9hpmq0)X0i`JwE&>$+E?>%_LC6RbVIkUx0b+_+BaR3cnT7Zv!AJxW zizFb)h!jyGOOZ85F;a?DAXP{m@;!0_IfqH8(HlgRxt7s3}k3K`kFu>>-2Q$QMFfPW!La{h336o>X zu_CMttHv6zR;&ZNiS=X8v3CR#fknUxHUxJ0uoBa_M6WNWeqIg~6QE69c9o#eyhGvpiOA@W-aonk<7r1(?fC{oI5N*U!4 zfg=2N-7=cNnjjOr{yriy6mMFgG#l znCF=fnQv8CDz++o6_Lscl}eQ+l^ZHARH>?_s@|##Rr6KLRFA1%Q+=*RRWnoLsR`7U zt5vFIcfW3@?wFpwUVxrVZ>QdQz32KIeJ}k~{cZZE^+ya? z2D1z#2HOnI7(B%_ac?{wFUQ;QQA1tBKtrWrm0_3Rgps+?Jfqb{jYbcQX~taRB;#$y zZN{S}1|}gUOHJxc?wV3fxuz+mJ4`!F$IZ;mqRrNsHJd##*D~ju=bP7?-?v~|cv>vB zsJ6IeNwVZxrdjT`yl#bBIa#GxRa#xMMy;K#CDyyGyQdMSxlWT#tDe?p!?5wT$+oGt z8L;Kp2HUQ-ZMJ=3XJQv;x5ci*?vuTfeY$;({XGW_huIFR9a(?@3)XSs8O^N5RyOM=TTmp(3=8^+zpz2r)C z^>JO{deZfso3oq3?Wo(Y?l$ge?uXo;%ru`Vo>?<<(8I_>;8Eq#KMS9gFl*neeosSB zfoHYnBQIkwkyowPu(zdms`p{<7e4kra-ZWq<2*OsGTvEV%s0Td$hXT+!*8Bnh2KMe zBmZRodjHV?r+_5^X9J0WL4jKW`}lf%A-|44I@@LTvf1rHjG(ze6+w@Jt%Bvjts!X0 z?2xS?_ve_-kiKB_KiJlZ$9G`c^=E@oNG)mWWaNo-3TIW8)$Hg0Ub-~8?KhvJ>$ z3*&nim@mj(aCxE5!t{lw7O5^0EIO7zOo&c6l<+|iDySBWCGrz@C5{St!X3hAA}`T4 z(TLbXTq+(;@<=L8dXnssyft|w#WSTW<++3>sgS%(4NTpeI-VAqb|7ssJvzNHgOZVu zaYCvgO_R1~>SyL=cFU|~g|hy|Zi}}s9+d~lYqOB71z9Z$wnC=pR9Yz4DhIM>Wmjgu z&56o6maCpC&F##y%G;1PobR9i?GnNg;gYtchD%p19a!eQtZF&3JaKv33gZ<8D~47E ztUS1iwkmDaPpj=$m#%)jCVEY4fnLGNg2A-`YwHVD3gv};>)hAvT~AmqS>Lr``i7kw zJ{5_It`yrBmlc25DBO7E8;5VoznR>Ww5hAaxn$2~(q`%A-YuS64wkBy=9dm`4cXeX z4c}I@?e+FW+b@^RDBHV(wnMq2zdX3SWv9u`%{xC-q*U}&`cyXV(%rRT*Z6MH?i+i& z_B8C(+grT%{XWUQ+f@NoP1R=AW&26{v-dx)iK^-Nmiuj8txj!m?Z*Ss1N{dh4z}01 z)YTo*JycSU)+_5r4#yw9{+;i4Ee$peRgIj+;v;ZGdF1K$3E%e~4LaI(jC-u%2h$&R z9cLXcYC@Xwnns&bn)_Q~Te?roKGD|d-g^8;+aC{{G(1^(O7m37Y1-+6)01cN&y1aw zoqc{T`P^XJqPBbIW6s}d4{z_f5Om?vMgNQEJG?v2T=KYd^0M3I6IZxbny)%vZR&LD zJpPl@Psh8QyPB@KTx+@RdcC!KX7}kEo;S|j^u2lU7XQ}Oo;f|;z4Ll+_r>@1-xl3| zawq-H%e&ckC+@AhPrP6BKT#_XdT7&;F71j}Joy zkC~6lh7E@6o;W@^IpRNZ{ptLtL(gQ-CY~4mqW;US7Zxvm_|@yz&e53Bp_lTPlfP|z zrTyx_>lv@x#=^!PzR7qqF<$gm`|ZJZ+;<)Cqu&ot2z=0000WV@Og>004R=004l4008;_004mL004C`008P>0026e000+nl3&F} z00016Nkl#o|j~;NOrUS;Iml8<=fB^uSx`83+-GX=k O0000KLZ*U+IBfRsybQWXdwQbLP>6pAqfylh#{fb6;Z(vMMVS~$e@S=j*ftg6;Uhf59&ghTmgWD0l;*T zI709Y^p6lP1rIRMx#05C~cW=H_Aw*bJ-5DT&Z2n+x)QHX^p z00esgV8|mQcmRZ%02D^@S3L16t`O%c004NIvOKvYIYoh62rY33S640`D9%Y2D-rV&neh&#Q1i z007~1e$oCcFS8neI|hJl{-P!B1ZZ9hpmq0)X0i`JwE&>$+E?>%_LC6RbVIkUx0b+_+BaR3cnT7Zv!AJxW zizFb)h!jyGOOZ85F;a?DAXP{m@;!0_IfqH8(HlgRxt7s3}k3K`kFu>>-2Q$QMFfPW!La{h336o>X zu_CMttHv6zR;&ZNiS=X8v3CR#fknUxHUxJ0uoBa_M6WNWeqIg~6QE69c9o#eyhGvpiOA@W-aonk<7r1(?fC{oI5N*U!4 zfg=2N-7=cNnjjOr{yriy6mMFgG#l znCF=fnQv8CDz++o6_Lscl}eQ+l^ZHARH>?_s@|##Rr6KLRFA1%Q+=*RRWnoLsR`7U zt5vFIcfW3@?wFpwUVxrVZ>QdQz32KIeJ}k~{cZZE^+ya? z2D1z#2HOnI7(B%_ac?{wFUQ;QQA1tBKtrWrm0_3Rgps+?Jfqb{jYbcQX~taRB;#$y zZN{S}1|}gUOHJxc?wV3fxuz+mJ4`!F$IZ;mqRrNsHJd##*D~ju=bP7?-?v~|cv>vB zsJ6IeNwVZxrdjT`yl#bBIa#GxRa#xMMy;K#CDyyGyQdMSxlWT#tDe?p!?5wT$+oGt z8L;Kp2HUQ-ZMJ=3XJQv;x5ci*?vuTfeY$;({XGW_huIFR9a(?@3)XSs8O^N5RyOM=TTmp(3=8^+zpz2r)C z^>JO{deZfso3oq3?Wo(Y?l$ge?uXo;%ru`Vo>?<<(8I_>;8Eq#KMS9gFl*neeosSB zfoHYnBQIkwkyowPu(zdms`p{<7e4kra-ZWq<2*OsGTvEV%s0Td$hXT+!*8Bnh2KMe zBmZRodjHV?r+_5^X9J0WL4jKW`}lf%A-|44I@@LTvf1rHjG(ze6+w@Jt%Bvjts!X0 z?2xS?_ve_-kiKB_KiJlZ$9G`c^=E@oNG)mWWaNo-3TIW8)$Hg0Ub-~8?KhvJ>$ z3*&nim@mj(aCxE5!t{lw7O5^0EIO7zOo&c6l<+|iDySBWCGrz@C5{St!X3hAA}`T4 z(TLbXTq+(;@<=L8dXnssyft|w#WSTW<++3>sgS%(4NTpeI-VAqb|7ssJvzNHgOZVu zaYCvgO_R1~>SyL=cFU|~g|hy|Zi}}s9+d~lYqOB71z9Z$wnC=pR9Yz4DhIM>Wmjgu z&56o6maCpC&F##y%G;1PobR9i?GnNg;gYtchD%p19a!eQtZF&3JaKv33gZ<8D~47E ztUS1iwkmDaPpj=$m#%)jCVEY4fnLGNg2A-`YwHVD3gv};>)hAvT~AmqS>Lr``i7kw zJ{5_It`yrBmlc25DBO7E8;5VoznR>Ww5hAaxn$2~(q`%A-YuS64wkBy=9dm`4cXeX z4c}I@?e+FW+b@^RDBHV(wnMq2zdX3SWv9u`%{xC-q*U}&`cyXV(%rRT*Z6MH?i+i& z_B8C(+grT%{XWUQ+f@NoP1R=AW&26{v-dx)iK^-Nmiuj8txj!m?Z*Ss1N{dh4z}01 z)YTo*JycSU)+_5r4#yw9{+;i4Ee$peRgIj+;v;ZGdF1K$3E%e~4LaI(jC-u%2h$&R z9cLXcYC@Xwnns&bn)_Q~Te?roKGD|d-g^8;+aC{{G(1^(O7m37Y1-+6)01cN&y1aw zoqc{T`P^XJqPBbIW6s}d4{z_f5Om?vMgNQEJG?v2T=KYd^0M3I6IZxbny)%vZR&LD zJpPl@Psh8QyPB@KTx+@RdcC!KX7}kEo;S|j^u2lU7XQ}Oo;f|;z4Ll+_r>@1-xl3| zawq-H%e&ckC+@AhPrP6BKT#_XdT7&;F71j}Joy zkC~6lh7E@6o;W@^IpRNZ{ptLtL(gQ-CY~4mqW;US7Zxvm_|@yz&e53Bp_lTPlfP|z zrTyx_>lv@x#=^!PzR7qqF<$gm`|ZJZ+;<)Cqu&ot2z=0000WV@Og>004R=004l4008;_004mL004C`008P>0026e000+nl3&F} z0001aNklKLZ*U+IBfRsybQWXdwQbLP>6pAqfylh#{fb6;Z(vMMVS~$e@S=j*ftg6;Uhf59&ghTmgWD0l;*T zI709Y^p6lP1rIRMx#05C~cW=H_Aw*bJ-5DT&Z2n+x)QHX^p z00esgV8|mQcmRZ%02D^@S3L16t`O%c004NIvOKvYIYoh62rY33S640`D9%Y2D-rV&neh&#Q1i z007~1e$oCcFS8neI|hJl{-P!B1ZZ9hpmq0)X0i`JwE&>$+E?>%_LC6RbVIkUx0b+_+BaR3cnT7Zv!AJxW zizFb)h!jyGOOZ85F;a?DAXP{m@;!0_IfqH8(HlgRxt7s3}k3K`kFu>>-2Q$QMFfPW!La{h336o>X zu_CMttHv6zR;&ZNiS=X8v3CR#fknUxHUxJ0uoBa_M6WNWeqIg~6QE69c9o#eyhGvpiOA@W-aonk<7r1(?fC{oI5N*U!4 zfg=2N-7=cNnjjOr{yriy6mMFgG#l znCF=fnQv8CDz++o6_Lscl}eQ+l^ZHARH>?_s@|##Rr6KLRFA1%Q+=*RRWnoLsR`7U zt5vFIcfW3@?wFpwUVxrVZ>QdQz32KIeJ}k~{cZZE^+ya? z2D1z#2HOnI7(B%_ac?{wFUQ;QQA1tBKtrWrm0_3Rgps+?Jfqb{jYbcQX~taRB;#$y zZN{S}1|}gUOHJxc?wV3fxuz+mJ4`!F$IZ;mqRrNsHJd##*D~ju=bP7?-?v~|cv>vB zsJ6IeNwVZxrdjT`yl#bBIa#GxRa#xMMy;K#CDyyGyQdMSxlWT#tDe?p!?5wT$+oGt z8L;Kp2HUQ-ZMJ=3XJQv;x5ci*?vuTfeY$;({XGW_huIFR9a(?@3)XSs8O^N5RyOM=TTmp(3=8^+zpz2r)C z^>JO{deZfso3oq3?Wo(Y?l$ge?uXo;%ru`Vo>?<<(8I_>;8Eq#KMS9gFl*neeosSB zfoHYnBQIkwkyowPu(zdms`p{<7e4kra-ZWq<2*OsGTvEV%s0Td$hXT+!*8Bnh2KMe zBmZRodjHV?r+_5^X9J0WL4jKW`}lf%A-|44I@@LTvf1rHjG(ze6+w@Jt%Bvjts!X0 z?2xS?_ve_-kiKB_KiJlZ$9G`c^=E@oNG)mWWaNo-3TIW8)$Hg0Ub-~8?KhvJ>$ z3*&nim@mj(aCxE5!t{lw7O5^0EIO7zOo&c6l<+|iDySBWCGrz@C5{St!X3hAA}`T4 z(TLbXTq+(;@<=L8dXnssyft|w#WSTW<++3>sgS%(4NTpeI-VAqb|7ssJvzNHgOZVu zaYCvgO_R1~>SyL=cFU|~g|hy|Zi}}s9+d~lYqOB71z9Z$wnC=pR9Yz4DhIM>Wmjgu z&56o6maCpC&F##y%G;1PobR9i?GnNg;gYtchD%p19a!eQtZF&3JaKv33gZ<8D~47E ztUS1iwkmDaPpj=$m#%)jCVEY4fnLGNg2A-`YwHVD3gv};>)hAvT~AmqS>Lr``i7kw zJ{5_It`yrBmlc25DBO7E8;5VoznR>Ww5hAaxn$2~(q`%A-YuS64wkBy=9dm`4cXeX z4c}I@?e+FW+b@^RDBHV(wnMq2zdX3SWv9u`%{xC-q*U}&`cyXV(%rRT*Z6MH?i+i& z_B8C(+grT%{XWUQ+f@NoP1R=AW&26{v-dx)iK^-Nmiuj8txj!m?Z*Ss1N{dh4z}01 z)YTo*JycSU)+_5r4#yw9{+;i4Ee$peRgIj+;v;ZGdF1K$3E%e~4LaI(jC-u%2h$&R z9cLXcYC@Xwnns&bn)_Q~Te?roKGD|d-g^8;+aC{{G(1^(O7m37Y1-+6)01cN&y1aw zoqc{T`P^XJqPBbIW6s}d4{z_f5Om?vMgNQEJG?v2T=KYd^0M3I6IZxbny)%vZR&LD zJpPl@Psh8QyPB@KTx+@RdcC!KX7}kEo;S|j^u2lU7XQ}Oo;f|;z4Ll+_r>@1-xl3| zawq-H%e&ckC+@AhPrP6BKT#_XdT7&;F71j}Joy zkC~6lh7E@6o;W@^IpRNZ{ptLtL(gQ-CY~4mqW;US7Zxvm_|@yz&e53Bp_lTPlfP|z zrTyx_>lv@x#=^!PzR7qqF<$gm`|ZJZ+;<)Cqu&ot2z=0000WV@Og>004R=004l4008;_004mL004C`008P>0026e000+nl3&F} z00015Nkl@mV}a;&z&P~t0>uUZ1^^Xcdn3u6aqR#A N002ovPDHLkV1hxePYD13 literal 0 HcmV?d00001 diff --git a/media/feincms/jquery.alerts.css b/media/feincms/jquery.alerts.css new file mode 100644 index 0000000..6c9532c --- /dev/null +++ b/media/feincms/jquery.alerts.css @@ -0,0 +1,54 @@ +#popup_container { + font-family: Arial, sans-serif; + font-size: 12px; + min-width: 300px; /* Dialog will be no smaller than this */ + max-width: 600px; /* Dialog will wrap after this width */ + background: #FFF; + border: solid 1px #666; + color: #000; +} + +#popup_title { + font-size: 14px; + font-weight: bold; + text-align: center; + line-height: 1.75em; + color: #666; + background: #eee url(img/title.gif) top repeat-x; + border: solid 1px #FFF; + border-bottom: solid 1px #666; + cursor: default; + padding: 0em; + margin: 0em; +} + +#popup_content { + background: 16px 16px no-repeat url(img/info.gif); + padding: 1em 1.75em; + margin: 0em; +} + +#popup_content.alert { + background-image: url(img/info.gif); +} + +#popup_content.confirm { + background-image: url(img/important.gif); +} + +#popup_content.prompt { + background-image: url(img/help.gif); +} + +#popup_message { + padding-left: 48px; +} + +#popup_panel { + text-align: center; + margin: 1em 0em 0em 1em; +} + +#popup_prompt { + margin: .5em 0em; +} \ No newline at end of file diff --git a/media/feincms/jquery.alerts.js b/media/feincms/jquery.alerts.js new file mode 100644 index 0000000..0fdee7a --- /dev/null +++ b/media/feincms/jquery.alerts.js @@ -0,0 +1,235 @@ +// jQuery Alert Dialogs Plugin +// +// Version 1.0 +// +// Cory S.N. LaViska +// A Beautiful Site (http://abeautifulsite.net/) +// 29 December 2008 +// +// Visit http://abeautifulsite.net/notebook/87 for more information +// +// Usage: +// jAlert( message, [title, callback] ) +// jConfirm( message, [title, callback] ) +// jPrompt( message, [value, title, callback] ) +// +// History: +// +// 1.00 - Released (29 December 2008) +// +// License: +// +// This plugin is licensed under the GNU General Public License: http://www.gnu.org/licenses/gpl.html +// +(function($) { + + $.alerts = { + + // These properties can be read/written by accessing $.alerts.propertyName from your scripts at any time + + verticalOffset: -75, // vertical offset of the dialog from center screen, in pixels + horizontalOffset: 0, // horizontal offset of the dialog from center screen, in pixels/ + repositionOnResize: true, // re-centers the dialog on window resize + overlayOpacity: .01, // transparency level of overlay + overlayColor: '#FFF', // base color of overlay + draggable: true, // make the dialogs draggable (requires UI Draggables plugin) + okButton: ' OK ', // text for the OK button + cancelButton: ' Cancel ', // text for the Cancel button + dialogClass: null, // if specified, this class will be applied to all dialogs + + // Public methods + + alert: function(message, title, callback) { + if( title == null ) title = 'Alert'; + $.alerts._show(title, message, null, 'alert', function(result) { + if( callback ) callback(result); + }); + }, + + confirm: function(message, title, callback) { + if( title == null ) title = 'Confirm'; + $.alerts._show(title, message, null, 'confirm', function(result) { + if( callback ) callback(result); + }); + }, + + prompt: function(message, value, title, callback) { + if( title == null ) title = 'Prompt'; + $.alerts._show(title, message, value, 'prompt', function(result) { + if( callback ) callback(result); + }); + }, + + // Private methods + + _show: function(title, msg, value, type, callback) { + + $.alerts._hide(); + $.alerts._overlay('show'); + + $("BODY").append( + ''); + + if( $.alerts.dialogClass ) $("#popup_container").addClass($.alerts.dialogClass); + + // IE6 Fix + var pos = ($.browser.msie && parseInt($.browser.version) <= 6 ) ? 'absolute' : 'fixed'; + + $("#popup_container").css({ + position: pos, + zIndex: 99999, + padding: 0, + margin: 0 + }); + + $("#popup_title").text(title); + $("#popup_content").addClass(type); + $("#popup_message").text(msg); + $("#popup_message").html( $("#popup_message").text().replace(/\n/g, '
') ); + + $("#popup_container").css({ + minWidth: $("#popup_container").outerWidth(), + maxWidth: $("#popup_container").outerWidth() + }); + + $.alerts._reposition(); + $.alerts._maintainPosition(true); + + switch( type ) { + case 'alert': + $("#popup_message").after(''); + $("#popup_ok").click( function() { + $.alerts._hide(); + callback(true); + }); + $("#popup_ok").focus().keypress( function(e) { + if( e.keyCode == 13 || e.keyCode == 27 ) $("#popup_ok").trigger('click'); + }); + break; + case 'confirm': + $("#popup_message").after(''); + $("#popup_ok").click( function() { + $.alerts._hide(); + if( callback ) callback(true); + }); + $("#popup_cancel").click( function() { + $.alerts._hide(); + if( callback ) callback(false); + }); + $("#popup_ok").focus(); + $("#popup_ok, #popup_cancel").keypress( function(e) { + if( e.keyCode == 13 ) $("#popup_ok").trigger('click'); + if( e.keyCode == 27 ) $("#popup_cancel").trigger('click'); + }); + break; + case 'prompt': + $("#popup_message").append('
').after(''); + $("#popup_prompt").width( $("#popup_message").width() ); + $("#popup_ok").click( function() { + var val = $("#popup_prompt").val(); + $.alerts._hide(); + if( callback ) callback( val ); + }); + $("#popup_cancel").click( function() { + $.alerts._hide(); + if( callback ) callback( null ); + }); + $("#popup_prompt, #popup_ok, #popup_cancel").keypress( function(e) { + if( e.keyCode == 13 ) $("#popup_ok").trigger('click'); + if( e.keyCode == 27 ) $("#popup_cancel").trigger('click'); + }); + if( value ) $("#popup_prompt").val(value); + $("#popup_prompt").focus().select(); + break; + } + + // Make draggable + if( $.alerts.draggable ) { + try { + $("#popup_container").draggable({ handle: $("#popup_title") }); + $("#popup_title").css({ cursor: 'move' }); + } catch(e) { /* requires jQuery UI draggables */ } + } + }, + + _hide: function() { + $("#popup_container").remove(); + $.alerts._overlay('hide'); + $.alerts._maintainPosition(false); + }, + + _overlay: function(status) { + switch( status ) { + case 'show': + $.alerts._overlay('hide'); + $("BODY").append(''); + $("#popup_overlay").css({ + position: 'absolute', + zIndex: 99998, + top: '0px', + left: '0px', + width: '100%', + height: $(document).height(), + background: $.alerts.overlayColor, + opacity: $.alerts.overlayOpacity, + display: 'none' + }); + break; + case 'hide': + $("#popup_overlay").remove(); + break; + } + }, + + _reposition: function() { + var top = (($(window).height() / 2) - ($("#popup_container").outerHeight() / 2)) + $.alerts.verticalOffset; + var left = (($(window).width() / 2) - ($("#popup_container").outerWidth() / 2)) + $.alerts.horizontalOffset; + if( top < 0 ) top = 0; + if( left < 0 ) left = 0; + + // IE6 fix + if( $.browser.msie && parseInt($.browser.version) <= 6 ) top = top + $(window).scrollTop(); + + $("#popup_container").css({ + top: top + 'px', + left: left + 'px' + }); + $("#popup_overlay").height( $(document).height() ); + }, + + _maintainPosition: function(status) { + if( $.alerts.repositionOnResize ) { + switch(status) { + case true: + $(window).bind('resize', function() { + $.alerts._reposition(); + }); + break; + case false: + $(window).unbind('resize'); + break; + } + } + } + + } + + // Shortuct functions + jAlert = function(message, title, callback) { + $.alerts.alert(message, title, callback); + } + + jConfirm = function(message, title, callback) { + $.alerts.confirm(message, title, callback); + }; + + jPrompt = function(message, value, title, callback) { + $.alerts.prompt(message, value, title, callback); + }; + +})(jQuery); \ No newline at end of file diff --git a/media/feincms/jquery.json-1.3.js b/media/feincms/jquery.json-1.3.js new file mode 100644 index 0000000..225ca82 --- /dev/null +++ b/media/feincms/jquery.json-1.3.js @@ -0,0 +1,156 @@ +/* + * jQuery JSON Plugin + * version: 1.0 (2008-04-17) + * + * This document is licensed as free software under the terms of the + * MIT License: http://www.opensource.org/licenses/mit-license.php + * + * Brantley Harris technically wrote this plugin, but it is based somewhat + * on the JSON.org website's http://www.json.org/json2.js, which proclaims: + * "NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.", a sentiment that + * I uphold. I really just cleaned it up. + * + * It is also based heavily on MochiKit's serializeJSON, which is + * copywrited 2005 by Bob Ippolito. + */ + +(function($) { + function toIntegersAtLease(n) + // Format integers to have at least two digits. + { + return n < 10 ? '0' + n : n; + } + + Date.prototype.toJSON = function(date) + // Yes, it polutes the Date namespace, but we'll allow it here, as + // it's damned usefull. + { + return this.getUTCFullYear() + '-' + + toIntegersAtLease(this.getUTCMonth()) + '-' + + toIntegersAtLease(this.getUTCDate()); + }; + + var escapeable = /["\\\x00-\x1f\x7f-\x9f]/g; + var meta = { // table of character substitutions + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '"' : '\\"', + '\\': '\\\\' + }; + + $.quoteString = function(string) + // Places quotes around a string, inteligently. + // If the string contains no control characters, no quote characters, and no + // backslash characters, then we can safely slap some quotes around it. + // Otherwise we must also replace the offending characters with safe escape + // sequences. + { + if (escapeable.test(string)) + { + return '"' + string.replace(escapeable, function (a) + { + var c = meta[a]; + if (typeof c === 'string') { + return c; + } + c = a.charCodeAt(); + return '\\u00' + Math.floor(c / 16).toString(16) + (c % 16).toString(16); + }) + '"'; + } + return '"' + string + '"'; + }; + + $.toJSON = function(o, compact) + { + var type = typeof(o); + + if (type == "undefined") + return "undefined"; + else if (type == "number" || type == "boolean") + return o + ""; + else if (o === null) + return "null"; + + // Is it a string? + if (type == "string") + { + return $.quoteString(o); + } + + // Does it have a .toJSON function? + if (type == "object" && typeof o.toJSON == "function") + return o.toJSON(compact); + + // Is it an array? + if (type != "function" && typeof(o.length) == "number") + { + var ret = []; + for (var i = 0; i < o.length; i++) { + ret.push( $.toJSON(o[i], compact) ); + } + if (compact) + return "[" + ret.join(",") + "]"; + else + return "[" + ret.join(", ") + "]"; + } + + // If it's a function, we have to warn somebody! + if (type == "function") { + throw new TypeError("Unable to convert object of type 'function' to json."); + } + + // It's probably an object, then. + var ret = []; + for (var k in o) { + var name; + type = typeof(k); + + if (type == "number") + name = '"' + k + '"'; + else if (type == "string") + name = $.quoteString(k); + else + continue; //skip non-string or number keys + + var val = $.toJSON(o[k], compact); + if (typeof(val) != "string") { + // skip non-serializable values + continue; + } + + if (compact) + ret.push(name + ":" + val); + else + ret.push(name + ": " + val); + } + return "{" + ret.join(", ") + "}"; + }; + + $.compactJSON = function(o) + { + return $.toJSON(o, true); + }; + + $.evalJSON = function(src) + // Evals JSON that we know to be safe. + { + return eval("(" + src + ")"); + }; + + $.secureEvalJSON = function(src) + // Evals JSON in a way that is *more* secure. + { + var filtered = src; + filtered = filtered.replace(/\\["\\\/bfnrtu]/g, '@'); + filtered = filtered.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']'); + filtered = filtered.replace(/(?:^|:|,)(?:\s*\[)+/g, ''); + + if (/^[\],:{}\s]*$/.test(filtered)) + return eval("(" + src + ")"); + else + throw new SyntaxError("Error parsing JSON, source is not valid."); + }; +})(jQuery); diff --git a/media/feincms/jquery.livequery.js b/media/feincms/jquery.livequery.js new file mode 100644 index 0000000..dde8ad8 --- /dev/null +++ b/media/feincms/jquery.livequery.js @@ -0,0 +1,250 @@ +/*! Copyright (c) 2008 Brandon Aaron (http://brandonaaron.net) + * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) + * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses. + * + * Version: 1.0.3 + * Requires jQuery 1.1.3+ + * Docs: http://docs.jquery.com/Plugins/livequery + */ + +(function($) { + +$.extend($.fn, { + livequery: function(type, fn, fn2) { + var self = this, q; + + // Handle different call patterns + if ($.isFunction(type)) + fn2 = fn, fn = type, type = undefined; + + // See if Live Query already exists + $.each( $.livequery.queries, function(i, query) { + if ( self.selector == query.selector && self.context == query.context && + type == query.type && (!fn || fn.$lqguid == query.fn.$lqguid) && (!fn2 || fn2.$lqguid == query.fn2.$lqguid) ) + // Found the query, exit the each loop + return (q = query) && false; + }); + + // Create new Live Query if it wasn't found + q = q || new $.livequery(this.selector, this.context, type, fn, fn2); + + // Make sure it is running + q.stopped = false; + + // Run it immediately for the first time + q.run(); + + // Contnue the chain + return this; + }, + + expire: function(type, fn, fn2) { + var self = this; + + // Handle different call patterns + if ($.isFunction(type)) + fn2 = fn, fn = type, type = undefined; + + // Find the Live Query based on arguments and stop it + $.each( $.livequery.queries, function(i, query) { + if ( self.selector == query.selector && self.context == query.context && + (!type || type == query.type) && (!fn || fn.$lqguid == query.fn.$lqguid) && (!fn2 || fn2.$lqguid == query.fn2.$lqguid) && !this.stopped ) + $.livequery.stop(query.id); + }); + + // Continue the chain + return this; + } +}); + +$.livequery = function(selector, context, type, fn, fn2) { + this.selector = selector; + this.context = context || document; + this.type = type; + this.fn = fn; + this.fn2 = fn2; + this.elements = []; + this.stopped = false; + + // The id is the index of the Live Query in $.livequery.queries + this.id = $.livequery.queries.push(this)-1; + + // Mark the functions for matching later on + fn.$lqguid = fn.$lqguid || $.livequery.guid++; + if (fn2) fn2.$lqguid = fn2.$lqguid || $.livequery.guid++; + + // Return the Live Query + return this; +}; + +$.livequery.prototype = { + stop: function() { + var query = this; + + if ( this.type ) + // Unbind all bound events + this.elements.unbind(this.type, this.fn); + else if (this.fn2) + // Call the second function for all matched elements + this.elements.each(function(i, el) { + query.fn2.apply(el); + }); + + // Clear out matched elements + this.elements = []; + + // Stop the Live Query from running until restarted + this.stopped = true; + }, + + run: function() { + // Short-circuit if stopped + if ( this.stopped ) return; + var query = this; + + var oEls = this.elements, + els = $(this.selector, this.context), + nEls = els.not(oEls); + + // Set elements to the latest set of matched elements + this.elements = els; + + if (this.type) { + // Bind events to newly matched elements + nEls.bind(this.type, this.fn); + + // Unbind events to elements no longer matched + if (oEls.length > 0) + $.each(oEls, function(i, el) { + if ( $.inArray(el, els) < 0 ) + $.event.remove(el, query.type, query.fn); + }); + } + else { + // Call the first function for newly matched elements + nEls.each(function() { + query.fn.apply(this); + }); + + // Call the second function for elements no longer matched + if ( this.fn2 && oEls.length > 0 ) + $.each(oEls, function(i, el) { + if ( $.inArray(el, els) < 0 ) + query.fn2.apply(el); + }); + } + } +}; + +$.extend($.livequery, { + guid: 0, + queries: [], + queue: [], + running: false, + timeout: null, + + checkQueue: function() { + if ( $.livequery.running && $.livequery.queue.length ) { + var length = $.livequery.queue.length; + // Run each Live Query currently in the queue + while ( length-- ) + $.livequery.queries[ $.livequery.queue.shift() ].run(); + } + }, + + pause: function() { + // Don't run anymore Live Queries until restarted + $.livequery.running = false; + }, + + play: function() { + // Restart Live Queries + $.livequery.running = true; + // Request a run of the Live Queries + $.livequery.run(); + }, + + registerPlugin: function() { + $.each( arguments, function(i,n) { + // Short-circuit if the method doesn't exist + if (!$.fn[n]) return; + + // Save a reference to the original method + var old = $.fn[n]; + + // Create a new method + $.fn[n] = function() { + // Call the original method + var r = old.apply(this, arguments); + + // Request a run of the Live Queries + $.livequery.run(); + + // Return the original methods result + return r; + } + }); + }, + + run: function(id) { + if (id != undefined) { + // Put the particular Live Query in the queue if it doesn't already exist + if ( $.inArray(id, $.livequery.queue) < 0 ) + $.livequery.queue.push( id ); + } + else + // Put each Live Query in the queue if it doesn't already exist + $.each( $.livequery.queries, function(id) { + if ( $.inArray(id, $.livequery.queue) < 0 ) + $.livequery.queue.push( id ); + }); + + // Clear timeout if it already exists + if ($.livequery.timeout) clearTimeout($.livequery.timeout); + // Create a timeout to check the queue and actually run the Live Queries + $.livequery.timeout = setTimeout($.livequery.checkQueue, 20); + }, + + stop: function(id) { + if (id != undefined) + // Stop are particular Live Query + $.livequery.queries[ id ].stop(); + else + // Stop all Live Queries + $.each( $.livequery.queries, function(id) { + $.livequery.queries[ id ].stop(); + }); + } +}); + +// Register core DOM manipulation methods +$.livequery.registerPlugin('append', 'prepend', 'after', 'before', 'wrap', 'attr', 'removeAttr', 'addClass', 'removeClass', 'toggleClass', 'empty', 'remove'); + +// Run Live Queries when the Document is ready +$(function() { $.livequery.play(); }); + + +// Save a reference to the original init method +var init = $.prototype.init; + +// Create a new init method that exposes two new properties: selector and context +$.prototype.init = function(a,c) { + // Call the original init and save the result + var r = init.apply(this, arguments); + + // Copy over properties if they exist already + if (a && a.selector) + r.context = a.context, r.selector = a.selector; + + // Set properties + if ( typeof a == 'string' ) + r.context = c || document, r.selector = a; + + // Return the result + return r; +}; + +// Give the init function the jQuery prototype for later instantiation (needed after Rev 4091) +$.prototype.init.prototype = $.prototype; + +})(jQuery); \ No newline at end of file diff --git a/media/feincms/jquery.treeTable.js b/media/feincms/jquery.treeTable.js new file mode 100644 index 0000000..83d0fb0 --- /dev/null +++ b/media/feincms/jquery.treeTable.js @@ -0,0 +1,383 @@ +/* jQuery treeTable Plugin 2.2 - http://ludo.cubicphuse.nl/jquery-plugins/treeTable/ */ +(function($) { + // Helps to make options available to all functions + // TODO: This gives problems when there are both expandable and non-expandable + // trees on a page. The options shouldn't be global to all these instances! + var options; + + $.fn.treeTable = function(opts) { + options = $.extend({}, $.fn.treeTable.defaults, opts); + + return this.each(function() { + $(this).addClass("treeTable").find("tbody tr").each(function() { + // Initialize root nodes only whenever possible + if(!options.expandable || $(this)[0].className.search("child-of-") == -1) { + initialize($(this)); + } + }); + }); + }; + + $.fn.treeTable.defaults = { + childPrefix: "child-of-", + expandable: true, + indent: 19, + initialState: "collapsed", + treeColumn: 0 + }; + + // Recursively hide all node's children in a tree + $.fn.collapse = function() { + $(this).addClass("collapsed"); + + childrenOf($(this)).each(function() { + initialize($(this)); + + if(!$(this).hasClass("collapsed")) { + $(this).collapse(); + } + + $(this).hide(); + }); + + return this; + }; + + // Recursively show all node's children in a tree + $.fn.expand = function() { + $(this).removeClass("collapsed").addClass("expanded"); + + childrenOf($(this)).each(function() { + initialize($(this)); + + if($(this).is(".expanded.parent")) { + $(this).expand(); + } + + $(this).show(); + }); + + return this; + }; + + // Add an entire branch to +destination+ + $.fn.appendBranchTo = function(destination) { + var node = $(this); + var parent = parentOf(node); + + var ancestorNames = $.map(ancestorsOf(destination), function(a) { return a.id; }); + + // Conditions: + // 1: +node+ should not be inserted in a location in a branch if this would + // result in +node+ being an ancestor of itself. + // 2: +node+ should not have a parent OR the destination should not be the + // same as +node+'s current parent (this last condition prevents +node+ + // from being moved to the same location where it already is). + // 3: +node+ should not be inserted as a child of +node+ itself. + if($.inArray(node[0].id, ancestorNames) == -1 && (!parent || (destination.attr("id") != parent[0].id)) && destination.attr("id") != node[0].id) { + indent(node, ancestorsOf(node).length * options.indent * -1); // Remove indentation + + if(parent) { node.removeClass(options.childPrefix + parent[0].id); } + + var dest_id = $(destination).attr("id"); + while ($(".child-of-"+dest_id).length > 0) { + var move_to = $(".child-of-"+dest_id+":last"); + dest_id = move_to.attr("id"); + } + + node.addClass(options.childPrefix + destination.attr("id")); + if (move_to) + moveChild(node, move_to); // Recursively move nodes to new location + else + moveChild(node, destination); + indent(node, ancestorsOf(node).length * options.indent); + } + + return this; + }; + + $.fn.insertBranchBefore = function(destination) { + var node = $(this); + var parent = parentOf_jQuery(node); + var dest_parent = parentOf_jQuery(destination); + + if ($(this).attr("id")==destination.attr("id")) + return false; + + var ancestorNames = $.map(ancestorsOf_jQuery(destination), function(a) { return a.id; }); + + indent(node, ancestorsOf_jQuery(node).length * options.indent * -1); // Remove indentation + + if(parent) { node.removeClass(options.childPrefix + parent[0].id); } + + if (dest_parent) + node.addClass(options.childPrefix + dest_parent.attr("id")); + + moveBefore(node, destination); // Recursively move nodes to new location + indent(node, (ancestorsOf_jQuery(node).length * options.indent)); + + return this; + }; + + // Add reverse() function from JS Arrays + $.fn.reverse = function() { + return this.pushStack(this.get().reverse(), arguments); + }; + + // Toggle an entire branch + $.fn.toggleBranch = function() { + if($(this).hasClass("collapsed")) { + $(this).expand(); + } else { + $(this).removeClass("expanded").collapse(); + } + + return this; + }; + + // === Private functions + + function ancestorsOf(node) { + var ancestors = []; + while(node = parentOf(node)) { + ancestors[ancestors.length] = node[0]; + } + return ancestors; + }; + + function childrenOf(node) { + return $("table.treeTable tbody tr." + options.childPrefix + node[0].id); + }; + + function indent(node, value) { + var cell = $(node.children("td")[options.treeColumn]); + var padding = parseInt(cell.css("padding-left"), 10) + value; + + cell.css("padding-left", + padding + "px"); + + childrenOf(node).each(function() { + indent($(this), value); + }); + }; + + function initialize(node) { + if(!node.hasClass("initialized")) { + node.addClass("initialized"); + + var childNodes = childrenOf(node); + + if(!node.hasClass("parent") && childNodes.length > 0) { + node.addClass("parent"); + } + + if(node.hasClass("parent")) { + var cell = $(node.children("td")[options.treeColumn]); + var padding = parseInt(cell.css("padding-left"), 10) + options.indent; + + childNodes.each(function() { + $($(this).children("td")[options.treeColumn]).css("padding-left", padding + "px"); + }); + + if(options.expandable) { + cell.children(":first").children("span").prepend(''); + //$(cell[0].firstChild).click(function() { node.toggleBranch(); }); + + // Check for a class set explicitly by the user, otherwise set the default class + if(!(node.hasClass("expanded") || node.hasClass("collapsed"))) { + node.addClass(options.initialState); + } + + if(node.hasClass("collapsed")) { + node.collapse(); + } else if (node.hasClass("expanded")) { + node.expand(); + } + } + } else { + var cell = $(node.children("td")[options.treeColumn]); + cell.children(":first").children("span").prepend(''); + } + node.children(":first").addClass("padded"); + } + }; + + function move(node, destination) { + node.insertAfter(destination); + childrenOf(node).reverse().each(function() { move($(this), node[0]); }); + }; + + function moveChild(node, destination) { + node.insertAfter(destination) + childrenOf(node).reverse().each(function() { move($(this), node[0]); }); + + }; + + function moveBefore(node, destination) { + node.insertBefore(destination) + childrenOf(node).reverse().each(function() { move($(this), node[0]); }); + }; + + function parentOf(node) { + + var classNames = node[0].className.split(' '); + + for(key in classNames) { + if(classNames[key].match("child-of-")) { + return $("#" + classNames[key].substring(9)); + } + } + }; +})(jQuery); + +// public functions +function handle_drop_event(source, dest, method){ + var ancestorNames = $.map(ancestorsOf_jQuery(dest), function(a) { return a.attr("id"); }); + if (method=="child") + dest.find(".wrap").removeClass("hover-as-child").addClass("nohover"); + else // method == "sibling" + dest.find("div:first").remove(); + // do not drop on itself or its own children, if method == "child" + if ( (method == "sibling") || (source.attr("id") != dest.attr("id") && $.inArray(source.attr("id"), ancestorNames) == -1) ) { + var source_child_of = null; + if (source.attr("class").match(/child-of-node-(\d+)/)) + source_child_of = source.attr("class").match(/child-of-node-(\d+)/)[0]; + var dest_child_of = "child-of-" + dest.attr("id"); + if (source_child_of && $("."+source_child_of).length - 1 == 0) { + var parent_id = "#" + source_child_of.substring(9); + $(parent_id).removeClass("parent"); + if ($(parent_id).hasClass("expanded")) + $(parent_id).removeClass("expanded").addClass("collapsed"); + $(parent_id+" .title-col span").removeClass("expander"); + } + if (method=="child") { + if ($("."+dest_child_of).length == 0) { + var parent_id = "#" + dest_child_of.substring(9); + $(parent_id).addClass("parent").find(".title-col span").addClass("expander"); + } + if (!dest.hasClass("expanded")) + dest.expand(); + // *** INSERT *** + source.appendBranchTo(dest); + } else // method == "sibling" + source.insertBranchBefore(dest); + } + source.find(".wrap").switchClass("nohover","flash",0).switchClass("flash","nohover",500); +} + +function handle_page_delete(node) { + var page_id = node.attr("class").match(/page-id-(\d+)/)[1]; + var parent_id = null; + if (node.attr("class").match(/child-of-node-(\d+)/)) + parent_id = node.attr("class").match(/child-of-node-(\d+)/)[1]; + var popup_bg = ''; + $("body").append(popup_bg); + if (node.hasClass("parent")){ + jAlert('Cannot delete item, because it is parent of at least one other item.', + 'Cannot delete item', function(){ + $(".popup_bg").remove(); + }); + } else { + jConfirm('Really delete item?', 'Confirm to delete item', function(r) { + if (r==true) { + $.post('.', {'__cmd': 'delete_item', 'item_id': page_id}, function(data){ + if (data == "OK") { + if (parent_id && $(".child-of-node-"+parent_id).length == 1) { + $("#node-"+parent_id).removeClass("parent") + .removeClass("expanded").addClass("collapsed") + .find(".expander").removeClass("expander"); + } + node.remove(); + $("body").append(popup_bg); + jAlert('Item deleted successfully.', + 'Item deleted', function(){ + $(".popup_bg").remove(); + }); + } + }); + } + $(".popup_bg").remove(); + }); + } +} + +function parentOf_jQuery(node) { + if (node.attr("class").match(/child-of-node-(\d+)/)) { + var parent_id = node.attr("class").match(/child-of-node-(\d+)/)[1]; + return $("#node-"+parent_id); + } + return null; +}; + +function ancestorsOf_jQuery(node) { + var ancestors = []; + while(node = parentOf_jQuery(node)) { + ancestors[ancestors.length] = node; + } + return ancestors; +}; + +function save_page_tree() { + var send_tree = new Array(); + + // prepare tree + var i = 0; + var ancestor_tree_ids = []; + var ancestor_indeces = []; + var tree_id = 0; + $("#sitetree tbody tr").each(function(){ + var tobj = new Array(); + // 0 = tree_id, 1 = parent_id, 2 = left, 3 = right, 4 = level, 5 = page_id + var classNames = $(this).attr("class").split(' '); + var is_child = false; var is_parent = false; + var parent_id = ""; + tobj[1] = null; + // gather information + for (key in classNames) { + if(classNames[key].match("page-id-")) + tobj[5] = parseInt(classNames[key].substring(8)); + if(classNames[key].match("parent")) + is_parent = true; + if(classNames[key].match("child-of-")) { + is_child = true; + var node_parent_id = classNames[key].substring(9); + var parent_id = parseInt($("#"+node_parent_id).attr("class").match(/page-id-(\d+)/)[1]) + tobj[1] = parent_id; + } + } + // save info + var inArray = ancestor_tree_ids.indexOf(parent_id); + while ( ( is_child && inArray < ancestor_tree_ids.length - 1 && inArray >= 0) || ( !is_child && ancestor_tree_ids.length > 0 ) ) { + send_tree[ancestor_indeces.pop()][3] = i++; + ancestor_tree_ids.pop(); + } + if (!is_child) { + tree_id++; + i = 0; + } + tobj[0] = tree_id; + tobj[4] = ancestor_tree_ids.length; + tobj[2] = i++; + if (is_parent) { + ancestor_tree_ids.push(tobj[5]); + ancestor_indeces.push(send_tree.length); + } else { + tobj[3] = i++; + } + send_tree.push(tobj); + }); + while (ancestor_tree_ids.length>0) { + send_tree[ancestor_indeces.pop()][3] = i++; + ancestor_tree_ids.pop(); + } + + // send tree to url + $.post('.', {'__cmd': 'save_tree', 'tree': $.toJSON(send_tree)}, function(data){ + if (data == "OK") { + var popup_bg = ''; + $("body").append(popup_bg); + jAlert("Tree saved successfully.", "Tree saved", function(){ + $(".popup_bg").remove(); + }); + } + }); +} diff --git a/media/feincms/layout.css b/media/feincms/layout.css new file mode 100644 index 0000000..23c2ccd --- /dev/null +++ b/media/feincms/layout.css @@ -0,0 +1,183 @@ + +#overview { + background-color:none; + padding:5px 0px 0px 5px; + margin:10px 0px 0px 10px; +} + +#content-main { + background-color:#fff; + margin-bottom:0px; + width:730px; +} + +.navi_tab { + float:left; + padding: 3px 10px 3px 10px; + cursor:pointer; + border: 1px solid #aaa; + border-bottom:none; + min-width:100px; +} +.tab_active { + height:16px; + font-weight: bold; + background-image:url('img/default-bg.gif'); + color: white; +} +.tab_inactive { + margin-top:4px; + height:12px; + font-weight: normal; + background-image:none; + color: black; +} +#main { + clear:both; + padding: 10px 10px 10px 10px; + border: 1px solid #aaa; +} +.panel { + display:none; + position:relative; + padding-bottom:70px; +} + + +span { margin-right: 5px;} + +.order-machine { + margin-bottom:10px; +} + +.order-item { + margin: 5px 15px 10px 5px; + background: #eee; + position:relative; + width:700px; + min-height:24px; + border: 1px solid #678; +} +.order-item span { + font-weight:bold; + margin-left:5px; +} +.order-item .handle { + float: left; + cursor: move; + background-color: #456; + height:100%; + border: 0px solid #456; + width:5px; + position:absolute; +} +.item-content { + padding: 0px 10px 10px 15px; + margin-right:5px; + clear:both; + margin-left:10px; +} +.item-header { + padding: 3px 10px 3px 15px; + margin-left:10px; + margin-top:1px; +} +.item-header span { + margin-right:10px; + float:left; + font-weight: strong; +} +.item-delete { + cursor:pointer; + float:right; + margin:3px 0px 0px 5px; +} +.active-item { + border: 1px solid #00a; + background-color:#def; +} +.button { + margin:5px; padding:5px; + font-weight: bold; + background-image:url('img/nav-bg.gif'); + cursor:pointer; + border: 1px solid #678; + width:30px; +} + +#test-log { padding: 5px; border: 1px solid #ccc; } + +#overview span { margin-right:15px; margin-left:5px;} + +.richtextcontent {display:none;} + +textarea { + width: 100%; + height: 80px; +} + +.item-minimize { + width: 15px; + height:15px; + float: left; + cursor: pointer; + margin-left:-17px; +} + +.item-minimize-disabled { + width: 15px; + height:15px; + float: left; + margin-left:-17px; +} + +.machine-control { + height:50px; + padding: 5px 0px 5px 0px; + border: 1px solid #678; + background-color:#ddd; + width:700px; + position:absolute; + left:5px; + bottom:5px; +} + +.control-unit { + float:left; + padding-left:15px; + padding-right:10px; + border-right: 1px solid #678; +} + +.control-unit span { + font-weight:bold; + margin-left:5px; +} + +hr { + color: #f00; + background-color: #bbb; + height: 1px; + margin-top: 5px; + margin-bottom: 5px; +} + +.empty-machine-msg { + margin:10px 0px 5px 300px; + font-style: italic; + font-size:14px; + color:#999; +} + +td span select { + width:600px; +} + +.popup_bg { + width:100%; height:100%; + background-color:white; + opacity: 0.5; + filter:alpha(opacity=50); + position:absolute; + top:0px; left:0px; +} diff --git a/media/feincms/listener.js b/media/feincms/listener.js new file mode 100644 index 0000000..2b6a5c6 --- /dev/null +++ b/media/feincms/listener.js @@ -0,0 +1,90 @@ +$(document).ready(function(){ + $(".navi_tab").livequery('click',function(){ + $(".navi_tab").removeClass("tab_active").addClass("tab_inactive"); + $(this).removeClass("tab_inactive").addClass("tab_active"); + $("#main > div:visible").hide(); + + var tab_str = $(this).attr("id").substr(0,$(this).attr("id").length-4); + $('#'+tab_str+'_body').show(); + ACTIVE_REGION = REGIONS.indexOf(tab_str); + + if (tab_str == "settings") + $(".machine-control").hide(); + else + $(".machine-control").show(); + + window.location.hash = '#'+tab_str; + }); + + $(".order-machine-add-button").livequery('click', function(){ + var modvar = $(this).prev().val(); + var modname = $(this).prev().children("option:selected").html(); + var total_forms = $('#id_'+modvar+'-TOTAL_FORMS'); + var last_id = parseInt(total_forms.val()) - 1; + var form = $("#"+modvar+"_set_item_"+last_id); + + total_forms.val(last_id+2); + create_new_from_form(form, modvar, last_id); + region_append(ACTIVE_REGION, form, modname, modvar); + set_item_field_value(form,"region-choice-field", ACTIVE_REGION); + + init_pagecontent(); + }); + + $(".order-machine-move-button").livequery('click', function(){ + var moveTo = $(this).prev().val(); + move_item(REGIONS.indexOf(moveTo),$(".active-item")); + }); + + $(".item-delete").livequery('click',function(){ + popup_bg = ''; + $("body").append(popup_bg); + var item = $(this).parents(".order-item"); + jConfirm('Really delete item?', 'Confirm to delete item', function(r) { + if (r==true) { + set_item_field_value(item,"delete-field","checked"); + item.fadeOut(200); + } + $(".popup_bg").remove(); + }); + }); + + $(".cancel").livequery('click',function(){ + popup_bg = ''; + $("body").append(popup_bg); + jConfirm('Really change template?
All content will be moved to main region.', + 'Change template', function(r) { + if (r==true) { + var items = $(".panel").children(".order-machine").children(); + move_item(0, items); + $(".submit_form").click(); + } else { + $(".popup_bg").remove(); + } + }); + }); + + $(".item-minimize").livequery('click',function(){ + var item = $(this).parent().next(); + if (item.is(":visible")) { + $(this).html(''); + item.slideUp(200); + } else { + $(this).html(''); + item.slideDown(200); + } + }); + + $(".order-item").livequery('click',function(){ + $(".order-item").removeClass("active-item"); + $(this).addClass("active-item"); + }); + + $(".submit_form").livequery('click',function(){ + zucht_und_ordnung(false); + var form = $(this).parents('form'); + form.attr('action', form.attr('action')+window.location.hash); + return true; + }); + +}); From b774c42333841d5b26cde5c69e79502d5a0373b1 Mon Sep 17 00:00:00 2001 From: substantialnoninfringinguser Date: Fri, 22 May 2009 06:17:24 +0100 Subject: [PATCH 029/449] [svn] switched from dodgy manually writing to logfile to using python's logging module, which seems great --- expo/models.py | 6 +++++- parsers/cavetab.py | 34 +++++++++++++--------------------- parsers/surveys.py | 22 ++++++++++------------ save_carefully.py | 18 ++++++++---------- templates/controlPanel.html | 2 +- 5 files changed, 37 insertions(+), 45 deletions(-) diff --git a/expo/models.py b/expo/models.py index 70f8381..42b2be6 100644 --- a/expo/models.py +++ b/expo/models.py @@ -1,4 +1,4 @@ -import urllib, urlparse, string, os, datetime +import urllib, urlparse, string, os, datetime, logging import troggle.mptt as mptt from django.forms import ModelForm from django.db import models @@ -14,6 +14,10 @@ getcontext().prec=2 #use 2 significant figures for decimal calculations from models_survex import * +logging.basicConfig(level=logging.DEBUG, + filename=settings.LOGFILE, + filemode='w') + #This class is for adding fields and methods which all of our models will have. class TroggleModel(models.Model): new_since_parsing = models.BooleanField(default=False, editable=False) diff --git a/parsers/cavetab.py b/parsers/cavetab.py index 187c4ac..3a990fe 100644 --- a/parsers/cavetab.py +++ b/parsers/cavetab.py @@ -1,12 +1,7 @@ # -*- coding: utf-8 -*- import troggle.expo.models as models from django.conf import settings -import csv -import time - -import re -import os - +import csv, time, re, os, logging from troggle.save_carefully import save_carefully ##format of CAVETAB2.CSV is @@ -136,20 +131,18 @@ def html_to_wiki(text): text2 = "" return out -def LoadCaveTab(logfile=settings.LOGFILE): +def LoadCaveTab(): cavetab = open(os.path.join(settings.EXPOWEB, "noinfo", "CAVETAB2.CSV"),'rU') caveReader = csv.reader(cavetab) caveReader.next() # Strip out column headers - if logfile: - logfile.write("Beginning to import caves from "+str(cavetab)+"\n"+"-"*60+"\n") + logging.info("Beginning to import caves from "+str(cavetab)+"\n"+"-"*60+"\n") for katArea in ['1623', '1626']: if not models.Area.objects.filter(short_name = katArea): newArea = models.Area(short_name = katArea) newArea.save() - if logfile: - logfile.write("Added area "+str(newArea.short_name)+"\n") + logging.info("Added area "+str(newArea.short_name)+"\n") area1626 = models.Area.objects.filter(short_name = '1626')[0] area1623 = models.Area.objects.filter(short_name = '1623')[0] @@ -190,8 +183,7 @@ def LoadCaveTab(logfile=settings.LOGFILE): addToDefaultArgs(Notes, "notes") newCave, created=save_carefully(models.Cave, lookupAttribs=args, nonLookupAttribs=defaultArgs) - if logfile: - logfile.write("Added cave "+str(newCave)+"\n") + logging.info("Added cave "+str(newCave)+"\n") #If we created a new cave, add the area to it. This does mean that if a cave's identifying features have not changed, areas will not be updated from csv. if created and line[Area]: @@ -209,14 +201,14 @@ def LoadCaveTab(logfile=settings.LOGFILE): newCave.area.add(area1623) newCave.save() - if logfile: - logfile.write("Added area "+line[Area]+" to cave "+str(newCave)+"\n") + + logging.info("Added area "+line[Area]+" to cave "+str(newCave)+"\n") if created and line[UnofficialName]: newUnofficialName = models.OtherCaveName(cave = newCave, name = line[UnofficialName]) newUnofficialName.save() - if logfile: - logfile.write("Added unofficial name "+str(newUnofficialName)+" to cave "+str(newCave)+"\n") + + logging.info("Added unofficial name "+str(newUnofficialName)+" to cave "+str(newCave)+"\n") if created and line[MultipleEntrances] == '' or \ line[MultipleEntrances] == 'entrance' or \ @@ -277,8 +269,8 @@ def LoadCaveTab(logfile=settings.LOGFILE): addToArgs(Bearings, 'bearings') newEntrance = models.Entrance(**args) newEntrance.save() - if logfile: - logfile.write("Added entrance "+str(newEntrance)+"\n") + + logging.info("Added entrance "+str(newEntrance)+"\n") if line[Entrances]: entrance_letter = line[Entrances] @@ -287,8 +279,8 @@ def LoadCaveTab(logfile=settings.LOGFILE): newCaveAndEntrance = models.CaveAndEntrance(cave = newCave, entrance = newEntrance, entrance_letter = entrance_letter) newCaveAndEntrance.save() - if logfile: - logfile.write("Added CaveAndEntrance "+str(newCaveAndEntrance)+"\n") + + logging.info("Added CaveAndEntrance "+str(newCaveAndEntrance)+"\n") # lookup function modelled on GetPersonExpeditionNameLookup diff --git a/parsers/surveys.py b/parsers/surveys.py index 142a2bb..da94fe6 100644 --- a/parsers/surveys.py +++ b/parsers/surveys.py @@ -1,6 +1,4 @@ -import sys -import os -import types +import sys, os, types, logging #sys.path.append('C:\\Expo\\expoweb') #from troggle import * #os.environ['DJANGO_SETTINGS_MODULE']='troggle.settings' @@ -26,7 +24,7 @@ def get_or_create_placeholder(year): placeholder_logbook_entry, newly_created = save_carefully(LogbookEntry, lookupAttribs, nonLookupAttribs) return placeholder_logbook_entry -def readSurveysFromCSV(logfile=None): +def readSurveysFromCSV(): try: surveytab = open(os.path.join(settings.SURVEYS, "Surveys.csv")) except IOError: @@ -43,16 +41,16 @@ def readSurveysFromCSV(logfile=None): print "There are no expeditions in the database. Please run the logbook parser." sys.exit() - if logfile: - logfile.write("Deleting all scanned images") + + logging.info("Deleting all scanned images") ScannedImage.objects.all().delete() - if logfile: - logfile.write("Deleting all survey objects") + + logging.info("Deleting all survey objects") Survey.objects.all().delete() - if logfile: - logfile.write("Beginning to import surveys from "+str(os.path.join(settings.SURVEYS, "Surveys.csv"))+"\n"+"-"*60+"\n") + + logging.info("Beginning to import surveys from "+str(os.path.join(settings.SURVEYS, "Surveys.csv"))+"\n"+"-"*60+"\n") for survey in surveyreader: #I hate this, but some surveys have a letter eg 2000#34a. The next line deals with that. @@ -74,8 +72,8 @@ def readSurveysFromCSV(logfile=None): pass surveyobj.save() - if logfile: - logfile.write("added survey " + survey[header['Year']] + "#" + surveyobj.wallet_number + "\r") + + logging.info("added survey " + survey[header['Year']] + "#" + surveyobj.wallet_number + "\r") def listdir(*directories): try: diff --git a/save_carefully.py b/save_carefully.py index f72b4fa..ee7c0cc 100644 --- a/save_carefully.py +++ b/save_carefully.py @@ -1,4 +1,4 @@ -from settings import LOGFILE +import logging def save_carefully(objectType, lookupAttribs={}, nonLookupAttribs={}): """Looks up instance using lookupAttribs and carries out the following: @@ -18,14 +18,12 @@ def save_carefully(objectType, lookupAttribs={}, nonLookupAttribs={}): setattr(instance, k, v) instance.save() - if LOGFILE: - if created: - LOGFILE.write(unicode(instance)+u' was just added to the database for the first time. \n') - - if not created and instance.new_since_parsing: - LOGFILE.write(unicode(instance)+" has been modified using Troggle, so the current script left it as is. \n") + if created: + logging.info(unicode(instance)+u' was just added to the database for the first time. \n') + + if not created and instance.new_since_parsing: + logging.info(unicode(instance)+" has been modified using Troggle, so the current script left it as is. \n") - if not created and not instance.new_since_parsing: - LOGFILE.write(unicode(instance)+" existed in the database unchanged since last parse. It was overwritten by the current script. \n") - LOGFILE.flush() + if not created and not instance.new_since_parsing: + logging.info(unicode(instance)+" existed in the database unchanged since last parse. It was overwritten by the current script. \n") return (instance, created) \ No newline at end of file diff --git a/templates/controlPanel.html b/templates/controlPanel.html index d62030f..209fa20 100644 --- a/templates/controlPanel.html +++ b/templates/controlPanel.html @@ -10,7 +10,7 @@
  • {{ job }}
  • {% endfor %} - {% if settings.LOGFILE %}See the logfile at {{settings.LOGFILE.path}} for more information.{% endif %} + {% if settings.LOGFILE %}See the logfile at {{settings.LOGFILE}} for more information.{% endif %} dismiss this message {% endif %} From a2154b411e16866ecbabf0f0ef8dff439d720efb Mon Sep 17 00:00:00 2001 From: substantialnoninfringinguser Date: Fri, 22 May 2009 06:43:25 +0100 Subject: [PATCH 030/449] [svn] fix wrongly named template tags --- templates/survey.html | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/templates/survey.html b/templates/survey.html index 4b6ebbc..b24e4e5 100644 --- a/templates/survey.html +++ b/templates/survey.html @@ -39,7 +39,7 @@ {% if current_survey %} {{current_survey}} {% else %} - {{current_year }} + {{current_expedition}} {% endif %} @@ -67,7 +67,7 @@ -{% if current_survey %} +{% if current_expedition %}

    Choose a wallet number

    +