diff --git a/symposion/sponsorship/management/commands/export_sponsors_data.py b/symposion/sponsorship/management/commands/export_sponsors_data.py
new file mode 100644
index 00000000..61172d44
--- /dev/null
+++ b/symposion/sponsorship/management/commands/export_sponsors_data.py
@@ -0,0 +1,77 @@
+import csv
+import os
+import shutil
+import zipfile
+
+from contextlib import closing
+
+from django.core.management.base import BaseCommand
+from django.template.defaultfilters import slugify
+
+from sotmjp.sponsorship.models import Sponsor
+
+
+def zipdir(basedir, archivename):
+ assert os.path.isdir(basedir)
+ with closing(zipfile.ZipFile(archivename, "w", zipfile.ZIP_DEFLATED)) as z:
+ for root, dirs, files in os.walk(basedir):
+ #NOTE: ignore empty directories
+ for fn in files:
+ absfn = os.path.join(root, fn)
+ zfn = absfn[len(basedir) + len(os.sep):] # XXX: relative path
+ z.write(absfn, zfn)
+
+
+class Command(BaseCommand):
+
+ def handle(self, *args, **options):
+ try:
+ os.makedirs(os.path.join(os.getcwd(), "build"))
+ except:
+ pass
+
+ csv_file = csv.writer(
+ open(os.path.join(os.getcwd(), "build", "sponsors.csv"), "wb")
+ )
+ csv_file.writerow(["Name", "URL", "Level", "Description"])
+
+ for sponsor in Sponsor.objects.all():
+ path = os.path.join(os.getcwd(), "build", slugify(sponsor.name))
+ try:
+ os.makedirs(path)
+ except:
+ pass
+
+ data = {
+ "name": sponsor.name,
+ "url": sponsor.external_url,
+ "level": sponsor.level.name,
+ "description": "",
+ }
+ for sponsor_benefit in sponsor.sponsor_benefits.all():
+ if sponsor_benefit.benefit_id == 2:
+ data["description"] = sponsor_benefit.text
+ if sponsor_benefit.benefit_id == 1:
+ if sponsor_benefit.upload:
+ data["ad"] = sponsor_benefit.upload.path
+ if sponsor_benefit.benefit_id == 7:
+ if sponsor_benefit.upload:
+ data["logo"] = sponsor_benefit.upload.path
+
+ if "ad" in data:
+ ad_path = data.pop("ad")
+ shutil.copy(ad_path, path)
+ if "logo" in data:
+ logo_path = data.pop("logo")
+ shutil.copy(logo_path, path)
+
+ csv_file.writerow([
+ data["name"].encode("utf-8"),
+ data["url"].encode("utf-8"),
+ data["level"].encode("utf-8"),
+ data["description"].encode("utf-8")
+ ])
+
+ zipdir(
+ os.path.join(os.getcwd(), "build"),
+ os.path.join(os.getcwd(), "sponsors.zip"))
diff --git a/symposion/sponsorship/tests.py b/symposion/sponsorship/tests.py
new file mode 100644
index 00000000..a6eae66b
--- /dev/null
+++ b/symposion/sponsorship/tests.py
@@ -0,0 +1,307 @@
+from cStringIO import StringIO
+import os
+import shutil
+import tempfile
+from zipfile import ZipFile
+
+from django.conf import settings
+from django.contrib.auth.models import User
+from django.core.exceptions import ValidationError
+from django.core.urlresolvers import reverse
+from django.test import TestCase
+from django.test.utils import override_settings
+
+from pycon.sponsorship.models import Benefit, Sponsor, SponsorBenefit,\
+ SponsorLevel
+from symposion.conference.models import current_conference
+
+
+class TestSponsorZipDownload(TestCase):
+ def setUp(self):
+ self.user = User.objects.create_user(username='joe',
+ email='joe@example.com',
+ password='joe')
+ self.user.is_staff = True
+ self.user.save()
+ self.url = reverse("sponsor_zip_logos")
+ self.assertTrue(self.client.login(username='joe@example.com',
+ password='joe'))
+
+ # we need a sponsor
+ conference = current_conference()
+ self.sponsor_level = SponsorLevel.objects.create(
+ conference=conference, name="Lead", cost=1)
+ self.sponsor = Sponsor.objects.create(
+ name="Big Daddy",
+ level=self.sponsor_level,
+ active=True,
+ )
+
+ # Create our benefits, of various types
+ self.text_benefit = Benefit.objects.create(name="text", type="text")
+ self.file_benefit = Benefit.objects.create(name="file", type="file")
+ # These names must be spelled exactly this way:
+ self.weblogo_benefit = Benefit.objects.create(name="Web logo", type="weblogo")
+ self.printlogo_benefit = Benefit.objects.create(name="Print logo", type="file")
+ self.advertisement_benefit = Benefit.objects.create(name="Advertisement", type="file")
+
+ def validate_response(self, rsp, names_and_sizes):
+ # Ensure a response from the view looks right, contains a valid
+ # zip archive, has files with the right names and sizes.
+ self.assertEqual("application/zip", rsp['Content-type'])
+ prefix = settings.CONFERENCE_URL_PREFIXES[settings.CONFERENCE_ID]
+
+ self.assertEqual(
+ 'attachment; filename="pycon_%s_sponsorlogos.zip"' % prefix,
+ rsp['Content-Disposition'])
+ zipfile = ZipFile(StringIO(rsp.content), "r")
+ # Check out the zip - testzip() returns None if no errors found
+ self.assertIsNone(zipfile.testzip())
+ # Compare contents to what is expected
+ infolist = zipfile.infolist()
+ self.assertEqual(len(names_and_sizes), len(infolist))
+ for info, name_and_size in zip(infolist, names_and_sizes):
+ name, size = name_and_size
+ self.assertEqual(name, info.filename)
+ self.assertEqual(size, info.file_size)
+
+ def make_temp_file(self, name, size=0):
+ # Create a temp file with the given name and size under self.temp_dir
+ path = os.path.join(self.temp_dir, name)
+ with open(path, "wb") as f:
+ f.write(size * "x")
+
+ def test_must_be_logged_in(self):
+ # Must be logged in to use the view
+ # If not logged in, doesn't redirect, just serves up a login view
+ self.client.logout()
+ rsp = self.client.get(self.url)
+ self.assertEqual(200, rsp.status_code)
+ self.assertIn("""
""", rsp.content)
+
+ def test_must_be_staff(self):
+ # Only staff can use the view
+ # If not staff, doesn't show error, just serves up a login view
+ # Also, the dashboard doesn't show the download button
+ self.user.is_staff = False
+ self.user.save()
+ rsp = self.client.get(self.url)
+ self.assertEqual(200, rsp.status_code)
+ self.assertIn("""""", rsp.content)
+ rsp = self.client.get(reverse('dashboard'))
+ self.assertNotIn(self.url, rsp.content)
+
+ def test_no_files(self):
+ # If there are no sponsor files, we still work
+ # And the dashboard shows our download button
+ rsp = self.client.get(self.url)
+ self.validate_response(rsp, [])
+ rsp = self.client.get(reverse('dashboard'))
+ self.assertIn(self.url, rsp.content)
+
+ def test_different_benefit_types(self):
+ # We only get files from the benefits named "Print logo" and "Web logo"
+ # And we ignore any non-existent files
+ try:
+ # Create a temp dir for media files
+ self.temp_dir = tempfile.mkdtemp()
+ with override_settings(MEDIA_ROOT=self.temp_dir):
+
+ # Give our sponsor some benefits
+ SponsorBenefit.objects.create(
+ sponsor=self.sponsor,
+ benefit=self.text_benefit,
+ text="Foo!"
+ )
+
+ self.make_temp_file("file1", 10)
+ SponsorBenefit.objects.create(
+ sponsor=self.sponsor,
+ benefit=self.file_benefit,
+ upload="file1"
+ )
+
+ self.make_temp_file("file2", 20)
+ SponsorBenefit.objects.create(
+ sponsor=self.sponsor,
+ benefit=self.weblogo_benefit,
+ upload="file2"
+ )
+
+ # Benefit whose file is missing from the disk
+ SponsorBenefit.objects.create(
+ sponsor=self.sponsor,
+ benefit=self.weblogo_benefit,
+ upload="file3"
+ )
+
+ # print logo benefit
+ self.make_temp_file("file4", 40)
+ SponsorBenefit.objects.create(
+ sponsor=self.sponsor,
+ benefit=self.printlogo_benefit,
+ upload="file4"
+ )
+
+ self.make_temp_file("file5", 50)
+ SponsorBenefit.objects.create(
+ sponsor=self.sponsor,
+ benefit=self.advertisement_benefit,
+ upload="file5"
+ )
+
+ rsp = self.client.get(self.url)
+ expected = [
+ ('web_logos/lead/big_daddy/file2', 20),
+ ('print_logos/lead/big_daddy/file4', 40),
+ ('advertisement/lead/big_daddy/file5', 50)
+ ]
+ self.validate_response(rsp, expected)
+ finally:
+ if hasattr(self, 'temp_dir'):
+ # Clean up any temp media files
+ shutil.rmtree(self.temp_dir)
+
+ def test_file_org(self):
+ # The zip file is organized into directories:
+ # {print_logos,web_logos,advertisement}///
+
+ # Add another sponsor at a different sponsor level
+ conference = current_conference()
+ self.sponsor_level2 = SponsorLevel.objects.create(
+ conference=conference, name="Silly putty", cost=1)
+ self.sponsor2 = Sponsor.objects.create(
+ name="Big Mama",
+ level=self.sponsor_level2,
+ active=True,
+ )
+ #
+ try:
+ # Create a temp dir for media files
+ self.temp_dir = tempfile.mkdtemp()
+ with override_settings(MEDIA_ROOT=self.temp_dir):
+
+ # Give our sponsors some benefits
+ self.make_temp_file("file1", 10)
+ SponsorBenefit.objects.create(
+ sponsor=self.sponsor,
+ benefit=self.weblogo_benefit,
+ upload="file1"
+ )
+ # print logo benefit
+ self.make_temp_file("file2", 20)
+ SponsorBenefit.objects.create(
+ sponsor=self.sponsor,
+ benefit=self.printlogo_benefit,
+ upload="file2"
+ )
+ # Sponsor 2
+ self.make_temp_file("file3", 30)
+ SponsorBenefit.objects.create(
+ sponsor=self.sponsor2,
+ benefit=self.weblogo_benefit,
+ upload="file3"
+ )
+ # print logo benefit
+ self.make_temp_file("file4", 42)
+ SponsorBenefit.objects.create(
+ sponsor=self.sponsor2,
+ benefit=self.printlogo_benefit,
+ upload="file4"
+ )
+ # ad benefit
+ self.make_temp_file("file5", 55)
+ SponsorBenefit.objects.create(
+ sponsor=self.sponsor2,
+ benefit=self.advertisement_benefit,
+ upload="file5"
+ )
+
+ rsp = self.client.get(self.url)
+ expected = [
+ ('web_logos/lead/big_daddy/file1', 10),
+ ('web_logos/silly_putty/big_mama/file3', 30),
+ ('print_logos/lead/big_daddy/file2', 20),
+ ('print_logos/silly_putty/big_mama/file4', 42),
+ ('advertisement/silly_putty/big_mama/file5', 55),
+ ]
+ self.validate_response(rsp, expected)
+ finally:
+ if hasattr(self, 'temp_dir'):
+ # Clean up any temp media files
+ shutil.rmtree(self.temp_dir)
+
+
+class TestBenefitValidation(TestCase):
+ """
+ It should not be possible to save a SponsorBenefit if it has the
+ wrong kind of data in it - e.g. a text-type benefit cannot have
+ an uploaded file, and vice-versa.
+ """
+ def setUp(self):
+ # we need a sponsor
+ conference = current_conference()
+ self.sponsor_level = SponsorLevel.objects.create(
+ conference=conference, name="Lead", cost=1)
+ self.sponsor = Sponsor.objects.create(
+ name="Big Daddy",
+ level=self.sponsor_level,
+ )
+
+ # Create our benefit types
+ self.text_type = Benefit.objects.create(name="text", type="text")
+ self.file_type = Benefit.objects.create(name="file", type="file")
+ self.weblogo_type = Benefit.objects.create(name="log", type="weblogo")
+ self.simple_type = Benefit.objects.create(name="simple", type="simple")
+
+ def validate(self, should_work, benefit_type, upload, text):
+ obj = SponsorBenefit(
+ benefit=benefit_type,
+ sponsor=self.sponsor,
+ upload=upload,
+ text=text
+ )
+ if should_work:
+ obj.save()
+ else:
+ with self.assertRaises(ValidationError):
+ obj.save()
+
+ def test_text_has_text(self):
+ self.validate(True, self.text_type, upload=None, text="Some text")
+
+ def test_text_has_upload(self):
+ self.validate(False, self.text_type, upload="filename", text='')
+
+ def test_text_has_both(self):
+ self.validate(False, self.text_type, upload="filename", text="Text")
+
+ def test_file_has_text(self):
+ self.validate(False, self.file_type, upload=None, text="Some text")
+
+ def test_file_has_upload(self):
+ self.validate(True, self.file_type, upload="filename", text='')
+
+ def test_file_has_both(self):
+ self.validate(False, self.file_type, upload="filename", text="Text")
+
+ def test_weblogo_has_text(self):
+ self.validate(False, self.weblogo_type, upload=None, text="Some text")
+
+ def test_weblogo_has_upload(self):
+ self.validate(True, self.weblogo_type, upload="filename", text='')
+
+ def test_weblogo_has_both(self):
+ self.validate(False, self.weblogo_type, upload="filename", text="Text")
+
+ def test_simple_has_neither(self):
+ self.validate(True, self.simple_type, upload=None, text='')
+
+ def test_simple_has_text(self):
+ self.validate(True, self.simple_type, upload=None, text="Some text")
+
+ def test_simple_has_upload(self):
+ self.validate(False, self.simple_type, upload="filename", text='')
+
+ def test_simple_has_both(self):
+ self.validate(False, self.simple_type, upload="filename", text="Text")
diff --git a/symposion/sponsorship/urls.py b/symposion/sponsorship/urls.py
index 17db5124..d7027fea 100644
--- a/symposion/sponsorship/urls.py
+++ b/symposion/sponsorship/urls.py
@@ -7,5 +7,6 @@ urlpatterns = patterns(
url(r"^$", TemplateView.as_view(template_name="sponsorship/list.html"), name="sponsor_list"),
url(r"^apply/$", "sponsor_apply", name="sponsor_apply"),
url(r"^add/$", "sponsor_add", name="sponsor_add"),
+ url(r"^ziplogos/$", "sponsor_zip_logo_files", name="sponsor_zip_logos"),
url(r"^(?P\d+)/$", "sponsor_detail", name="sponsor_detail"),
)
diff --git a/symposion/sponsorship/views.py b/symposion/sponsorship/views.py
index e3b22697..bf74ec3a 100644
--- a/symposion/sponsorship/views.py
+++ b/symposion/sponsorship/views.py
@@ -1,13 +1,28 @@
-from django.http import Http404
+from cStringIO import StringIO
+import itertools
+import logging
+import os
+import time
+from zipfile import ZipFile, ZipInfo
+
+from django.conf import settings
+
+from django.http import Http404, HttpResponse
from django.shortcuts import render_to_response, redirect, get_object_or_404
from django.template import RequestContext
+from django.utils.translation import ugettext_lazy as _
from django.contrib import messages
+from django.contrib.admin.views.decorators import staff_member_required
from django.contrib.auth.decorators import login_required
-from symposion.sponsorship.forms import SponsorApplicationForm, SponsorDetailsForm, \
- SponsorBenefitsFormSet
-from symposion.sponsorship.models import Sponsor, SponsorBenefit
+from symposion.sponsorship.forms import SponsorApplicationForm, \
+ SponsorDetailsForm, SponsorBenefitsFormSet
+from symposion.sponsorship.models import Benefit, Sponsor, SponsorBenefit, \
+ SponsorLevel
+
+
+log = logging.getLogger(__name__)
@login_required
@@ -18,13 +33,13 @@ def sponsor_apply(request):
sponsor = form.save()
if sponsor.sponsor_benefits.all():
# Redirect user to sponsor_detail to give extra information.
- messages.success(request, "Thank you for your sponsorship "
- "application. Please update your "
- "benefit details below.")
+ messages.success(request, _("Thank you for your sponsorship "
+ "application. Please update your "
+ "benefit details below."))
return redirect("sponsor_detail", pk=sponsor.pk)
else:
- messages.success(request, "Thank you for your sponsorship "
- "application.")
+ messages.success(request, _("Thank you for your sponsorship "
+ "application."))
return redirect("dashboard")
else:
form = SponsorApplicationForm(user=request.user)
@@ -87,3 +102,96 @@ def sponsor_detail(request, pk):
"form": form,
"formset": formset,
}, context_instance=RequestContext(request))
+
+
+@staff_member_required
+def sponsor_export_data(request):
+ sponsors = []
+ data = ""
+
+ for sponsor in Sponsor.objects.order_by("added"):
+ d = {
+ "name": sponsor.name,
+ "url": sponsor.external_url,
+ "level": (sponsor.level.order, sponsor.level.name),
+ "description": "",
+ }
+ for sponsor_benefit in sponsor.sponsor_benefits.all():
+ if sponsor_benefit.benefit_id == 2:
+ d["description"] = sponsor_benefit.text
+ sponsors.append(d)
+
+ def izip_longest(*args):
+ fv = None
+
+ def sentinel(counter=([fv] * (len(args) - 1)).pop):
+ yield counter()
+ iters = [itertools.chain(it, sentinel(), itertools.repeat(fv)) for it in args]
+ try:
+ for tup in itertools.izip(*iters):
+ yield tup
+ except IndexError:
+ pass
+
+ def pairwise(iterable):
+ a, b = itertools.tee(iterable)
+ b.next()
+ return izip_longest(a, b)
+
+ def level_key(s):
+ return s["level"]
+
+ for level, level_sponsors in itertools.groupby(sorted(sponsors, key=level_key), level_key):
+ data += "%s\n" % ("-" * (len(level[1]) + 4))
+ data += "| %s |\n" % level[1]
+ data += "%s\n\n" % ("-" * (len(level[1]) + 4))
+ for sponsor, next in pairwise(level_sponsors):
+ description = sponsor["description"].strip()
+ description = description if description else "-- NO DESCRIPTION FOR THIS SPONSOR --"
+ data += "%s\n\n%s" % (sponsor["name"], description)
+ if next is not None:
+ data += "\n\n%s\n\n" % ("-" * 80)
+ else:
+ data += "\n\n"
+
+ return HttpResponse(data, content_type="text/plain;charset=utf-8")
+
+
+@staff_member_required
+def sponsor_zip_logo_files(request):
+ """Return a zip file of sponsor web and print logos"""
+
+ zip_stringio = StringIO()
+ zipfile = ZipFile(zip_stringio, "w")
+ try:
+ benefits = Benefit.objects.all()
+ for benefit in benefits:
+ dir_name = benefit.name.lower().replace(" ", "_").replace('/', '_')
+ for level in SponsorLevel.objects.all():
+ level_name = level.name.lower().replace(" ", "_").replace('/', '_')
+ for sponsor in Sponsor.objects.filter(level=level, active=True):
+ sponsor_name = sponsor.name.lower().replace(" ", "_").replace('/', '_')
+ full_dir = "/".join([dir_name, level_name, sponsor_name])
+ for sponsor_benefit in SponsorBenefit.objects.filter(
+ benefit=benefit,
+ sponsor=sponsor,
+ active=True,
+ ).exclude(upload=''):
+ if os.path.exists(sponsor_benefit.upload.path):
+ modtime = time.gmtime(os.stat(sponsor_benefit.upload.path).st_mtime)
+ with open(sponsor_benefit.upload.path, "rb") as f:
+ fname = os.path.split(sponsor_benefit.upload.name)[-1]
+ zipinfo = ZipInfo(filename=full_dir + "/" + fname,
+ date_time=modtime)
+ zipfile.writestr(zipinfo, f.read())
+ else:
+ log.debug("No such sponsor file: %s" % sponsor_benefit.upload.path)
+ finally:
+ zipfile.close()
+
+ response = HttpResponse(zip_stringio.getvalue(),
+ content_type="application/zip")
+ prefix = settings.CONFERENCE_URL_PREFIXES[settings.CONFERENCE_ID]
+ response['Content-Disposition'] = \
+ 'attachment; filename="%s_sponsorlogos.zip"' % prefix
+ return response