From d5da2664b063711aee28851f41062e3666963fb3 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Marek=20St=C4=99pniowski?= Date: Mon, 12 Oct 2009 02:13:04 +0200 Subject: [PATCH 1/1] Dopracowanie aplikacji django-sponsors. --- apps/sponsors/admin.py | 24 ++-- apps/sponsors/fields.py | 61 ++++++++ apps/sponsors/models.py | 39 ++++-- apps/sponsors/static/css/sponsors.css | 4 - .../static/js/ordered_select_multiple.js | 63 --------- .../static/sponsors/css/footer_admin.css | 67 +++++++++ .../static/sponsors/js/footer_admin.js | 131 ++++++++++++++++++ .../static/sponsors/js/jquery.json.min.js | 31 +++++ apps/sponsors/templates/sponsors/page.html | 11 ++ .../sponsors/templates/sponsors/sponsors.html | 11 -- apps/sponsors/templatetags/sponsor_tags.py | 11 +- apps/sponsors/widgets.py | 27 ++-- fabfile.py | 34 +++-- wolnelektury/media/css/sponsors.css | 4 +- .../media/sponsors/css/footer_admin.css | 67 +++++++++ .../media/sponsors/js/footer_admin.js | 131 ++++++++++++++++++ .../media/sponsors/js/jquery.json.min.js | 31 +++++ wolnelektury/templates/base.html | 2 +- 18 files changed, 624 insertions(+), 125 deletions(-) create mode 100644 apps/sponsors/fields.py delete mode 100644 apps/sponsors/static/css/sponsors.css delete mode 100644 apps/sponsors/static/js/ordered_select_multiple.js create mode 100644 apps/sponsors/static/sponsors/css/footer_admin.css create mode 100644 apps/sponsors/static/sponsors/js/footer_admin.js create mode 100644 apps/sponsors/static/sponsors/js/jquery.json.min.js create mode 100644 apps/sponsors/templates/sponsors/page.html delete mode 100644 apps/sponsors/templates/sponsors/sponsors.html create mode 100644 wolnelektury/media/sponsors/css/footer_admin.css create mode 100644 wolnelektury/media/sponsors/js/footer_admin.js create mode 100644 wolnelektury/media/sponsors/js/jquery.json.min.js diff --git a/apps/sponsors/admin.py b/apps/sponsors/admin.py index c66a08653..55af9d764 100644 --- a/apps/sponsors/admin.py +++ b/apps/sponsors/admin.py @@ -1,21 +1,25 @@ -from django.db import models from django.contrib import admin +from django.conf import settings -from sponsors.models import Sponsor, SponsorGroup -from sponsors.widgets import OrderedSelectMultiple +from sponsors import models +from sponsors import fields +from sponsors import widgets -class SponsorGroupAdmin(admin.ModelAdmin): - formfield_overrides = { - models.CommaSeparatedIntegerField: {'widget': OrderedSelectMultiple}, - } + +class SponsorAdmin(admin.ModelAdmin): list_display = ('name',) search_fields = ('name',) ordering = ('name',) -class SponsorAdmin(admin.ModelAdmin): + +class SponsorPageAdmin(admin.ModelAdmin): + formfield_overrides = { + fields.JSONField: {'widget': widgets.SponsorPageWidget}, + } list_display = ('name',) search_fields = ('name',) ordering = ('name',) -admin.site.register(SponsorGroup, SponsorGroupAdmin) -admin.site.register(Sponsor, SponsorAdmin) + +admin.site.register(models.Sponsor, SponsorAdmin) +admin.site.register(models.SponsorPage, SponsorPageAdmin) diff --git a/apps/sponsors/fields.py b/apps/sponsors/fields.py new file mode 100644 index 000000000..678788e75 --- /dev/null +++ b/apps/sponsors/fields.py @@ -0,0 +1,61 @@ +# -*- encoding: utf-8 -*- +import datetime + +from django.conf import settings +from django.db import models +from django import forms +from django.utils import simplejson as json + + +class JSONEncoder(json.JSONEncoder): + def default(self, obj): + if isinstance(obj, datetime.datetime): + return obj.strftime('%Y-%m-%d %H:%M:%S') + elif isinstance(obj, datetime.date): + return obj.strftime('%Y-%m-%d') + elif isinstance(obj, datetime.time): + return obj.strftime('%H:%M:%S') + return json.JSONEncoder.default(self, obj) + + +def dumps(data): + return JSONEncoder().encode(data) + + +def loads(str): + return json.loads(str, encoding=settings.DEFAULT_CHARSET) + + +class JSONFormField(forms.CharField): + widget = forms.Textarea + + def clean(self, value): + try: + loads(value) + return value + except ValueError, e: + raise forms.ValidationError('Enter a valid JSON value. Error: %s' % e) + + +class JSONField(models.TextField): + def formfield(self, **kwargs): + defaults = {'form_class': JSONFormField} + defaults.update(kwargs) + return super(JSONField, self).formfield(**defaults) + + def db_type(self): + return 'text' + + def get_internal_type(self): + return 'TextField' + + def contribute_to_class(self, cls, name): + super(JSONField, self).contribute_to_class(cls, name) + + def get_value(model_instance): + return loads(getattr(model_instance, self.attname, None)) + setattr(cls, 'get_%s_value' % self.name, get_value) + + def set_value(model_instance, json): + return setattr(model_instance, self.attname, dumps(json)) + setattr(cls, 'set_%s_value' % self.name, set_value) diff --git a/apps/sponsors/models.py b/apps/sponsors/models.py index ffad8e774..1df990f68 100644 --- a/apps/sponsors/models.py +++ b/apps/sponsors/models.py @@ -1,5 +1,8 @@ from django.db import models from django.utils.translation import ugettext_lazy as _ +from django.template.loader import render_to_string + +from sponsors.fields import JSONField class Sponsor(models.Model): @@ -18,17 +21,35 @@ class Sponsor(models.Model): return self.name -class SponsorGroup(models.Model): +class SponsorPage(models.Model): name = models.CharField(_('name'), max_length=120) - order = models.IntegerField(_('order'), default=0) - column_width = models.PositiveIntegerField(_('column width')) - sponsor_ids = models.CommaSeparatedIntegerField(_('sponsors'), max_length=255) + column_width = models.PositiveIntegerField(_('column width'), default=200) + sponsors = JSONField(_('sponsors'), default={}) + _html = models.TextField(blank=True, editable=False) + + def populated_sponsors(self): + result = [] + for column in self.get_sponsors_value(): + result_group = {'name': column['name'], 'sponsors': []} + sponsor_objects = Sponsor.objects.in_bulk(column['sponsors']) + for sponsor_pk in column['sponsors']: + try: + result_group['sponsors'].append(sponsor_objects[sponsor_pk]) + except KeyError: + pass + result.append(result_group) + return result - def sponsors(self): - ids = [int(pk) for pk in self.sponsor_ids.split(',')] - result = Sponsor.objects.in_bulk(ids) - return [result[pk] for pk in ids] - sponsors.changes_data = False + def html(self): + return self._html + html = property(fget=html) + + def save(self, *args, **kwargs): + self._html = render_to_string('sponsors/page.html', { + 'column_width': self.column_width, + 'sponsors': self.populated_sponsors(), + }) + return super(SponsorPage, self).save(*args, **kwargs) def __unicode__(self): return self.name diff --git a/apps/sponsors/static/css/sponsors.css b/apps/sponsors/static/css/sponsors.css deleted file mode 100644 index f3d7ed977..000000000 --- a/apps/sponsors/static/css/sponsors.css +++ /dev/null @@ -1,4 +0,0 @@ -.sponsor-group { - float: left; - overflow: hidden; -} diff --git a/apps/sponsors/static/js/ordered_select_multiple.js b/apps/sponsors/static/js/ordered_select_multiple.js deleted file mode 100644 index e4fd74daa..000000000 --- a/apps/sponsors/static/js/ordered_select_multiple.js +++ /dev/null @@ -1,63 +0,0 @@ -(function($) { - $.fn.orderedSelectMultiple = function(options) { - var settings = { - choices: [] - }; - $.extend(settings, options); - - var input = $(this).hide(); - var values = input.val().split(','); - - var container = $('
').insertAfter($(this)); - var choicesList = $('
    ').appendTo(container).css({ - width: 200, float: 'left', minHeight: 200, backgroundColor: '#eee', margin: 0, padding: 0 - }); - var valuesList = $('
      ').appendTo(container).css({ - width: 200, float: 'left', minHeight: 200, backgroundColor: '#eee', margin: 0, padding: 0 - }); - var choiceIds = []; - $.each(settings.choices, function() { - choiceIds.push('' + this.id); - }); - - function createItem(hash) { - return $('
    1. ' + hash.name + '
    2. ').css({ - backgroundColor: '#cff', - display: 'block', - border: '1px solid #cdd', - padding: 2, - margin: 0 - }).data('obj-id', hash.id); - } - - $.each(settings.choices, function() { - if ($.inArray('' + this.id, values) == -1) { - choicesList.append(createItem(this)); - } - }); - - $.each(values, function() { - var index = $.inArray('' + this, choiceIds); // Why this[0]? - if (index != -1) { - valuesList.append(createItem(settings.choices[index])); - } - }); - - choicesList.sortable({ - connectWith: '.connectedSortable' - }).disableSelection(); - - valuesList.sortable({ - connectWith: '.connectedSortable', - update: function() { - values = []; - $('li', valuesList).each(function(index) { - values.push($(this).data('obj-id')); - console.log($(this).data('obj-id')); - }); - console.log('update', values.join(','), input); - input.val(values.join(',')); - } - }).disableSelection(); - }; -})(jQuery); diff --git a/apps/sponsors/static/sponsors/css/footer_admin.css b/apps/sponsors/static/sponsors/css/footer_admin.css new file mode 100644 index 000000000..ba56771d8 --- /dev/null +++ b/apps/sponsors/static/sponsors/css/footer_admin.css @@ -0,0 +1,67 @@ +.sponsors { + display: block; + clear: both; + margin-top: 5px; +} + +.sponsors .sponsors-sponsor-group { + float: left; + width: 200px; + border: 1px solid #CCC; + margin: 2px 2px 0 0; +} + +.sponsors .sponsors-sponsor-group-name { + border-bottom: 1px solid #CCC; + padding: 2px 2px 2px 4px; + margin: 0; + color: #FFF; + background-color: #7CA0C7; + font-weight: bold; + height: 15px; +} + +.sponsors .sponsors-sponsor-group-name input { + margin: -2px -2px -2px -4px; + padding: 0; + height: 15px; + width: 180px; +} + +.sponsors .sponsors-remove-sponsor-group { + float: right; + background-color: #CC3434; + color: #FFF; + width: 10px; + height: 15px; + padding: 2px; + text-align: center; + font-weight: bold; + display: block; + cursor: default; +} + +.sponsors .sponsors-remove-sponsor-group:hover { + color: #CC3434; + background-color: white; +} + +.sponsors .sponsors-unused-sponsor-group-name { + background-color: #FFF; + color: #666; +} + +.sponsors .sponsors-sponsor-group-list { + margin: 0; + padding: 2px; + list-style: none; + min-height: 200px; +} + +.sponsors-sponsor { + margin: 0 0 2px 0; + padding: 2px; + border: 1px solid #CCC; + background-color: #EEE; + cursor: default; +} diff --git a/apps/sponsors/static/sponsors/js/footer_admin.js b/apps/sponsors/static/sponsors/js/footer_admin.js new file mode 100644 index 000000000..2f2cd93da --- /dev/null +++ b/apps/sponsors/static/sponsors/js/footer_admin.js @@ -0,0 +1,131 @@ +(function($) { + $.fn.sponsorsFooter = function(options) { + var settings = { + sponsors: [] + }; + $.extend(settings, options); + + var input = $(this).hide(); + + var container = $('
      ').appendTo(input.parent()); + var groups = $.evalJSON(input.val()); + + var unusedDiv = $('
      ') + .appendTo(container) + .append('

      dostępni sponsorzy

      '); + var unusedList = $('
        ') + .appendTo(unusedDiv) + .sortable({ + connectWith: '.sponsors-sponsor-group-list' + }); + + // Edit group name inline + function editNameInline(name) { + name.unbind('click.sponsorsFooter'); + var inlineInput = $('').val(name.html()); + name.html(''); + + function endEditing() { + name.html(inlineInput.val()); + inlineInput.remove(); + name.bind('click.sponsorsFooter', function() { + editNameInline($(this)); + }); + input.parents('form').unbind('submit.sponsorsFooter', endEditing); + return false; + } + + inlineInput.appendTo(name).focus().blur(endEditing); + input.parents('form').bind('submit.sponsorsFooter', endEditing); + } + + // Remove sponsor with passed id from sponsors array and return it + function popSponsor(id) { + for (var i=0; i < settings.sponsors.length; i++) { + if (settings.sponsors[i].id == id) { + var s = settings.sponsors[i]; + settings.sponsors.splice(i, 1); + return s; + } + } + return null; + } + + // Create sponsor group and bind events + function createGroup(name, sponsors) { + if (!sponsors) { + sponsors = []; + } + + var groupDiv = $('
        '); + + $('X') + .click(function() { + groupDiv.fadeOut('slow', function() { + $('.sponsors-sponsor', groupDiv).hide().appendTo(unusedList).fadeIn(); + groupDiv.remove(); + }); + }).appendTo(groupDiv); + + $('

        ' + name + '

        ') + .bind('click.sponsorsFooter', function() { + editNameInline($(this)); + }).appendTo(groupDiv); + + var groupList = $('
          ') + .appendTo(groupDiv) + .sortable({ + connectWith: '.sponsors-sponsor-group-list' + }); + + + for (var i = 0; i < sponsors.length; i++) { + $('
        1. ' + sponsors[i].name + '
        2. ') + .data('obj_id', sponsors[i].id) + .appendTo(groupList); + } + return groupDiv; + } + + // Create groups from data in input value + for (var i = 0; i < groups.length; i++) { + var group = groups[i]; + var sponsors = []; + + for (var j = 0; j < group.sponsors.length; j++) { + var s = popSponsor(group.sponsors[j]); + if (s) { + sponsors.push(s); + } + } + createGroup(group.name, sponsors).appendTo(container); + } + + // Serialize input value before submiting form + input.parents('form').submit(function(event) { + var groups = []; + $('.sponsors-sponsor-group', container).not('.sponsors-unused-sponsor-group').each(function() { + var group = {name: $('.sponsors-sponsor-group-name', this).html(), sponsors: []}; + $('.sponsors-sponsor', this).each(function() { + group.sponsors.push($(this).data('obj_id')); + }); + groups.push(group); + }); + input.val($.toJSON(groups)); + }); + + for (i = 0; i < settings.sponsors.length; i++) { + $('
        3. ' + settings.sponsors[i].name + '
        4. ') + .data('obj_id', settings.sponsors[i].id) + .appendTo(unusedList); + } + + $('') + .click(function() { + var newGroup = createGroup('').appendTo(container); + editNameInline($('.sponsors-sponsor-group-name', newGroup)); + }).prependTo(input.parent()); + + input.parent().append('
          '); + }; +})(jQuery); diff --git a/apps/sponsors/static/sponsors/js/jquery.json.min.js b/apps/sponsors/static/sponsors/js/jquery.json.min.js new file mode 100644 index 000000000..bad4a0afa --- /dev/null +++ b/apps/sponsors/static/sponsors/js/jquery.json.min.js @@ -0,0 +1,31 @@ + +(function($){$.toJSON=function(o) +{if(typeof(JSON)=='object'&&JSON.stringify) +return JSON.stringify(o);var type=typeof(o);if(o===null) +return"null";if(type=="undefined") +return undefined;if(type=="number"||type=="boolean") +return o+"";if(type=="string") +return $.quoteString(o);if(type=='object') +{if(typeof o.toJSON=="function") +return $.toJSON(o.toJSON());if(o.constructor===Date) +{var month=o.getUTCMonth()+1;if(month<10)month='0'+month;var day=o.getUTCDate();if(day<10)day='0'+day;var year=o.getUTCFullYear();var hours=o.getUTCHours();if(hours<10)hours='0'+hours;var minutes=o.getUTCMinutes();if(minutes<10)minutes='0'+minutes;var seconds=o.getUTCSeconds();if(seconds<10)seconds='0'+seconds;var milli=o.getUTCMilliseconds();if(milli<100)milli='0'+milli;if(milli<10)milli='0'+milli;return'"'+year+'-'+month+'-'+day+'T'+ +hours+':'+minutes+':'+seconds+'.'+milli+'Z"';} +if(o.constructor===Array) +{var ret=[];for(var i=0;i +{% for column in sponsors %} +
          +

          {{ column.name }}

          + {% for sponsor in column.sponsors %} +
          {% if sponsor.url %}{% endif %}{% if sponsor.url %}{% endif %}
          + {% endfor %} +
          +{% endfor %} +
          +
          diff --git a/apps/sponsors/templates/sponsors/sponsors.html b/apps/sponsors/templates/sponsors/sponsors.html deleted file mode 100644 index d5decae71..000000000 --- a/apps/sponsors/templates/sponsors/sponsors.html +++ /dev/null @@ -1,11 +0,0 @@ -
          -{% for group in sponsor_groups %} - -{% endfor %} -
          -
          diff --git a/apps/sponsors/templatetags/sponsor_tags.py b/apps/sponsors/templatetags/sponsor_tags.py index 87289e8d5..c1d18d157 100644 --- a/apps/sponsors/templatetags/sponsor_tags.py +++ b/apps/sponsors/templatetags/sponsor_tags.py @@ -1,4 +1,5 @@ from django import template +from django.utils.safestring import mark_safe from sponsors import models @@ -6,7 +7,11 @@ from sponsors import models register = template.Library() -def sponsors(): - return {'sponsor_groups': models.SponsorGroup.objects.all()} +def sponsor_page(name): + try: + page = models.SponsorPage.objects.get(name=name) + except: + return u'' + return mark_safe(page.html) -compressed_js = register.inclusion_tag('sponsors/sponsors.html')(sponsors) +sponsor_page = register.simple_tag(sponsor_page) diff --git a/apps/sponsors/widgets.py b/apps/sponsors/widgets.py index 2ed579370..3bb586bcf 100644 --- a/apps/sponsors/widgets.py +++ b/apps/sponsors/widgets.py @@ -1,30 +1,29 @@ -from django import forms from django.conf import settings +from django import forms from django.utils.safestring import mark_safe -from django.utils.translation import ugettext_lazy as _ from sponsors import models -class OrderedSelectMultiple(forms.TextInput): - """ - A SelectMultiple with a JavaScript interface. - """ +class SponsorPageWidget(forms.Textarea): class Media: js = ( 'http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js', 'http://ajax.googleapis.com/ajax/libs/jqueryui/1.7.1/jquery-ui.min.js', - settings.MEDIA_URL + 'js/ordered_select_multiple.js', + settings.MEDIA_URL + 'sponsors/js/jquery.json.min.js', + settings.MEDIA_URL + 'sponsors/js/footer_admin.js', ) + css = { + 'all': (settings.MEDIA_URL + 'sponsors/css/footer_admin.css',), + } - def render(self, name, value, attrs=None, choices=()): - output = [super(OrderedSelectMultiple, self).render(name, value, attrs)] - choices = [(unicode(obj), obj.pk) for obj in models.Sponsor.objects.all()] - choices_js = ', '.join('{name: "%s", id: %d}' % choice for choice in choices) + def render(self, name, value, attrs=None): + output = [super(SponsorPageWidget, self).render(name, value, attrs)] + sponsors = [(unicode(obj), obj.pk) for obj in models.Sponsor.objects.all()] + sponsors_js = ', '.join('{name: "%s", id: %d}' % sponsor for sponsor in sponsors) output.append(u'\n' % - (name, choices_js)) + output.append(u'$("#id_%s").sponsorsFooter({sponsors: [%s]}); });\n' % + (name, sponsors_js)) return mark_safe(u''.join(output)) - diff --git a/fabfile.py b/fabfile.py index 679186459..2acd88bba 100644 --- a/fabfile.py +++ b/fabfile.py @@ -26,8 +26,8 @@ def production(): # ========= def test(): "Run the test suite and bail out if it fails" - require('project_dir', provided_by=[staging, production]) - result = local('cd %(path)s; python manage.py test' % env) + require('hosts', 'path', provided_by=[staging, production]) + result = run('cd %(path)s/%(project_name)s; python manage.py test' % env) def deploy(): """ @@ -35,13 +35,13 @@ def deploy(): install any required third party modules, install the virtual host and then restart the webserver """ - require('hosts', provided_by=[staging, production]) - require('path') + require('hosts', 'path', provided_by=[staging, production]) import time env.release = time.strftime('%Y-%m-%dT%H%M') upload_tar_from_git() + upload_requirements_bundle() install_requirements() symlink_current_release() migrate() @@ -49,8 +49,7 @@ def deploy(): def deploy_version(version): "Specify a specific version to be made live" - require('hosts', provided_by=[localhost,webserver]) - require('path') + require('hosts', 'path', provided_by=[localhost,webserver]) env.version = version with cd(env.path): run('rm releases/previous; mv releases/current releases/previous;', pty=True) @@ -99,6 +98,7 @@ def rollback(): # ===================================================================== def upload_tar_from_git(): "Create an archive from the current Git master branch and upload it" + print '>>> upload tar from git' require('release', provided_by=[deploy]) local('git archive --format=tar master | gzip > %(release)s.tar.gz' % env) run('mkdir -p %(path)s/releases/%(release)s' % env, pty=True) @@ -107,16 +107,32 @@ def upload_tar_from_git(): run('cd %(path)s/releases/%(release)s && tar zxf ../../packages/%(release)s.tar.gz' % env, pty=True) local('rm %(release)s.tar.gz' % env) +def upload_requirements_bundle(): + "Create a pybundle from requirements.txt file and upload it" + print '>>> upload requirements bundle' + require('release', provided_by=[deploy]) + requirements_mtime = os.path.getmtime('requirements.txt') + pybundle_mtime = 0 + try: + pybundle_mtime = os.path.getmtime('requirements.pybundle') + except os.error: + pass + if pybundle_mtime < requirements_mtime: + pip_options = file('pip-options.txt').read().strip() + local('pip bundle %s -r requirements.txt requirements.pybundle' % pip_options) + put('requirements.pybundle', '%(path)s/releases/%(release)s' % env) + def install_requirements(): "Install the required packages from the requirements file using pip" + print '>>> install requirements' require('release', provided_by=[deploy]) - pip_options = file('pip-options.txt').read().strip() with cd('%(path)s/releases/%(release)s' % env): run('virtualenv --no-site-packages .') - run('pip install -E . %s -r requirements.txt' % pip_options) + run('pip install -E . requirements.pybundle') def symlink_current_release(): "Symlink our current release" + print '>>> symlink current release' require('release', provided_by=[deploy]) require('path', provided_by=[staging, production]) with cd(env.path): @@ -130,6 +146,7 @@ def symlink_current_release(): def migrate(): "Update the database" + print '>>> migrate' require('project_name', provided_by=[staging, production]) with cd('%(path)s/releases/current/%(project_name)s' % env): run('../bin/python manage.py syncdb --noinput' % env, pty=True) @@ -137,6 +154,7 @@ def migrate(): def restart_webserver(): "Restart the web server" + print '>>> restart webserver' run('touch %(path)s/releases/current/%(project_name)s/%(project_name)s.wsgi' % env) # def install_site(): diff --git a/wolnelektury/media/css/sponsors.css b/wolnelektury/media/css/sponsors.css index f5497c135..810e1ff30 100644 --- a/wolnelektury/media/css/sponsors.css +++ b/wolnelektury/media/css/sponsors.css @@ -1,8 +1,8 @@ -.sponsor-group { +.sponsors-sponsor-group { float: left; overflow: hidden; } -.sponsor-logo { +.sponsors-sponsor-logo { float: left; } \ No newline at end of file diff --git a/wolnelektury/media/sponsors/css/footer_admin.css b/wolnelektury/media/sponsors/css/footer_admin.css new file mode 100644 index 000000000..ba56771d8 --- /dev/null +++ b/wolnelektury/media/sponsors/css/footer_admin.css @@ -0,0 +1,67 @@ +.sponsors { + display: block; + clear: both; + margin-top: 5px; +} + +.sponsors .sponsors-sponsor-group { + float: left; + width: 200px; + border: 1px solid #CCC; + margin: 2px 2px 0 0; +} + +.sponsors .sponsors-sponsor-group-name { + border-bottom: 1px solid #CCC; + padding: 2px 2px 2px 4px; + margin: 0; + color: #FFF; + background-color: #7CA0C7; + font-weight: bold; + height: 15px; +} + +.sponsors .sponsors-sponsor-group-name input { + margin: -2px -2px -2px -4px; + padding: 0; + height: 15px; + width: 180px; +} + +.sponsors .sponsors-remove-sponsor-group { + float: right; + background-color: #CC3434; + color: #FFF; + width: 10px; + height: 15px; + padding: 2px; + text-align: center; + font-weight: bold; + display: block; + cursor: default; +} + +.sponsors .sponsors-remove-sponsor-group:hover { + color: #CC3434; + background-color: white; +} + +.sponsors .sponsors-unused-sponsor-group-name { + background-color: #FFF; + color: #666; +} + +.sponsors .sponsors-sponsor-group-list { + margin: 0; + padding: 2px; + list-style: none; + min-height: 200px; +} + +.sponsors-sponsor { + margin: 0 0 2px 0; + padding: 2px; + border: 1px solid #CCC; + background-color: #EEE; + cursor: default; +} diff --git a/wolnelektury/media/sponsors/js/footer_admin.js b/wolnelektury/media/sponsors/js/footer_admin.js new file mode 100644 index 000000000..2f2cd93da --- /dev/null +++ b/wolnelektury/media/sponsors/js/footer_admin.js @@ -0,0 +1,131 @@ +(function($) { + $.fn.sponsorsFooter = function(options) { + var settings = { + sponsors: [] + }; + $.extend(settings, options); + + var input = $(this).hide(); + + var container = $('
          ').appendTo(input.parent()); + var groups = $.evalJSON(input.val()); + + var unusedDiv = $('
          ') + .appendTo(container) + .append('

          dostępni sponsorzy

          '); + var unusedList = $('
            ') + .appendTo(unusedDiv) + .sortable({ + connectWith: '.sponsors-sponsor-group-list' + }); + + // Edit group name inline + function editNameInline(name) { + name.unbind('click.sponsorsFooter'); + var inlineInput = $('').val(name.html()); + name.html(''); + + function endEditing() { + name.html(inlineInput.val()); + inlineInput.remove(); + name.bind('click.sponsorsFooter', function() { + editNameInline($(this)); + }); + input.parents('form').unbind('submit.sponsorsFooter', endEditing); + return false; + } + + inlineInput.appendTo(name).focus().blur(endEditing); + input.parents('form').bind('submit.sponsorsFooter', endEditing); + } + + // Remove sponsor with passed id from sponsors array and return it + function popSponsor(id) { + for (var i=0; i < settings.sponsors.length; i++) { + if (settings.sponsors[i].id == id) { + var s = settings.sponsors[i]; + settings.sponsors.splice(i, 1); + return s; + } + } + return null; + } + + // Create sponsor group and bind events + function createGroup(name, sponsors) { + if (!sponsors) { + sponsors = []; + } + + var groupDiv = $('
            '); + + $('X') + .click(function() { + groupDiv.fadeOut('slow', function() { + $('.sponsors-sponsor', groupDiv).hide().appendTo(unusedList).fadeIn(); + groupDiv.remove(); + }); + }).appendTo(groupDiv); + + $('

            ' + name + '

            ') + .bind('click.sponsorsFooter', function() { + editNameInline($(this)); + }).appendTo(groupDiv); + + var groupList = $('
              ') + .appendTo(groupDiv) + .sortable({ + connectWith: '.sponsors-sponsor-group-list' + }); + + + for (var i = 0; i < sponsors.length; i++) { + $('
            1. ' + sponsors[i].name + '
            2. ') + .data('obj_id', sponsors[i].id) + .appendTo(groupList); + } + return groupDiv; + } + + // Create groups from data in input value + for (var i = 0; i < groups.length; i++) { + var group = groups[i]; + var sponsors = []; + + for (var j = 0; j < group.sponsors.length; j++) { + var s = popSponsor(group.sponsors[j]); + if (s) { + sponsors.push(s); + } + } + createGroup(group.name, sponsors).appendTo(container); + } + + // Serialize input value before submiting form + input.parents('form').submit(function(event) { + var groups = []; + $('.sponsors-sponsor-group', container).not('.sponsors-unused-sponsor-group').each(function() { + var group = {name: $('.sponsors-sponsor-group-name', this).html(), sponsors: []}; + $('.sponsors-sponsor', this).each(function() { + group.sponsors.push($(this).data('obj_id')); + }); + groups.push(group); + }); + input.val($.toJSON(groups)); + }); + + for (i = 0; i < settings.sponsors.length; i++) { + $('
            3. ' + settings.sponsors[i].name + '
            4. ') + .data('obj_id', settings.sponsors[i].id) + .appendTo(unusedList); + } + + $('') + .click(function() { + var newGroup = createGroup('').appendTo(container); + editNameInline($('.sponsors-sponsor-group-name', newGroup)); + }).prependTo(input.parent()); + + input.parent().append('
              '); + }; +})(jQuery); diff --git a/wolnelektury/media/sponsors/js/jquery.json.min.js b/wolnelektury/media/sponsors/js/jquery.json.min.js new file mode 100644 index 000000000..bad4a0afa --- /dev/null +++ b/wolnelektury/media/sponsors/js/jquery.json.min.js @@ -0,0 +1,31 @@ + +(function($){$.toJSON=function(o) +{if(typeof(JSON)=='object'&&JSON.stringify) +return JSON.stringify(o);var type=typeof(o);if(o===null) +return"null";if(type=="undefined") +return undefined;if(type=="number"||type=="boolean") +return o+"";if(type=="string") +return $.quoteString(o);if(type=='object') +{if(typeof o.toJSON=="function") +return $.toJSON(o.toJSON());if(o.constructor===Date) +{var month=o.getUTCMonth()+1;if(month<10)month='0'+month;var day=o.getUTCDate();if(day<10)day='0'+day;var year=o.getUTCFullYear();var hours=o.getUTCHours();if(hours<10)hours='0'+hours;var minutes=o.getUTCMinutes();if(minutes<10)minutes='0'+minutes;var seconds=o.getUTCSeconds();if(seconds<10)seconds='0'+seconds;var milli=o.getUTCMilliseconds();if(milli<100)milli='0'+milli;if(milli<10)milli='0'+milli;return'"'+year+'-'+month+'-'+day+'T'+ +hours+':'+minutes+':'+seconds+'.'+milli+'Z"';} +if(o.constructor===Array) +{var ret=[];for(var i=0;ifundacja@nowoczesnapolska.org.pl

              - {% sponsors %} + {% sponsor_page "footer" %}
              -- 2.20.1