From 086d2d1cc02853096f671880b9bb3cd2a122eb67 Mon Sep 17 00:00:00 2001 From: James Tauber Date: Sun, 5 Feb 2012 13:38:52 -0500 Subject: [PATCH 001/751] first commit --- README | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 README diff --git a/README b/README new file mode 100644 index 00000000..e69de29b From 0f6843b08dcf3010e037e99f70f87616be591868 Mon Sep 17 00:00:00 2001 From: James Tauber Date: Sun, 5 Feb 2012 13:53:49 -0500 Subject: [PATCH 002/751] pinax account project --- symposion_project/__init__.py | 8 + symposion_project/apps/__init__.py | 0 symposion_project/apps/about/__init__.py | 0 symposion_project/apps/about/models.py | 3 + symposion_project/apps/about/urls.py | 11 + symposion_project/apps/about/views.py | 1 + symposion_project/fixtures/initial_data.json | 10 + symposion_project/manage.py | 20 ++ symposion_project/requirements/base.txt | 25 +++ symposion_project/requirements/project.txt | 7 + symposion_project/settings.py | 204 ++++++++++++++++++ symposion_project/static/README | 13 ++ symposion_project/templates/_footer.html | 4 + .../templates/about/what_next.html | 38 ++++ symposion_project/templates/homepage.html | 44 ++++ symposion_project/templates/site_base.html | 19 ++ symposion_project/urls.py | 29 +++ symposion_project/wsgi.py | 11 + 18 files changed, 447 insertions(+) create mode 100644 symposion_project/__init__.py create mode 100644 symposion_project/apps/__init__.py create mode 100644 symposion_project/apps/about/__init__.py create mode 100644 symposion_project/apps/about/models.py create mode 100644 symposion_project/apps/about/urls.py create mode 100644 symposion_project/apps/about/views.py create mode 100644 symposion_project/fixtures/initial_data.json create mode 100755 symposion_project/manage.py create mode 100644 symposion_project/requirements/base.txt create mode 100644 symposion_project/requirements/project.txt create mode 100644 symposion_project/settings.py create mode 100644 symposion_project/static/README create mode 100644 symposion_project/templates/_footer.html create mode 100644 symposion_project/templates/about/what_next.html create mode 100644 symposion_project/templates/homepage.html create mode 100644 symposion_project/templates/site_base.html create mode 100644 symposion_project/urls.py create mode 100644 symposion_project/wsgi.py diff --git a/symposion_project/__init__.py b/symposion_project/__init__.py new file mode 100644 index 00000000..ccd713a2 --- /dev/null +++ b/symposion_project/__init__.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- + +__about__ = """ +This project takes the zero_project and adds basic account management +functionality such as sign up, log in, password change/reset and email +confirmation. It is a foundation suitable for most sites that have user +accounts. +""" diff --git a/symposion_project/apps/__init__.py b/symposion_project/apps/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/symposion_project/apps/about/__init__.py b/symposion_project/apps/about/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/symposion_project/apps/about/models.py b/symposion_project/apps/about/models.py new file mode 100644 index 00000000..71a83623 --- /dev/null +++ b/symposion_project/apps/about/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/symposion_project/apps/about/urls.py b/symposion_project/apps/about/urls.py new file mode 100644 index 00000000..3b5c17ea --- /dev/null +++ b/symposion_project/apps/about/urls.py @@ -0,0 +1,11 @@ +from django.conf.urls.defaults import * +from django.views.generic.simple import direct_to_template + + +urlpatterns = patterns("", + url(r"^$", direct_to_template, {"template": "about/about.html"}, name="about"), + url(r"^terms/$", direct_to_template, {"template": "about/terms.html"}, name="terms"), + url(r"^privacy/$", direct_to_template, {"template": "about/privacy.html"}, name="privacy"), + url(r"^dmca/$", direct_to_template, {"template": "about/dmca.html"}, name="dmca"), + url(r"^what_next/$", direct_to_template, {"template": "about/what_next.html"}, name="what_next"), +) diff --git a/symposion_project/apps/about/views.py b/symposion_project/apps/about/views.py new file mode 100644 index 00000000..60f00ef0 --- /dev/null +++ b/symposion_project/apps/about/views.py @@ -0,0 +1 @@ +# Create your views here. diff --git a/symposion_project/fixtures/initial_data.json b/symposion_project/fixtures/initial_data.json new file mode 100644 index 00000000..891f3860 --- /dev/null +++ b/symposion_project/fixtures/initial_data.json @@ -0,0 +1,10 @@ +[ + { + "pk": 1, + "model": "sites.site", + "fields": { + "domain": "example.com", + "name": "example.com" + } + } +] diff --git a/symposion_project/manage.py b/symposion_project/manage.py new file mode 100755 index 00000000..378e3b0b --- /dev/null +++ b/symposion_project/manage.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python +import sys + +try: + import pinax +except ImportError: + sys.stderr.write("Error: Can't import Pinax. Make sure you are in a " + "virtual environment that has\nPinax installed.\n") + sys.exit(1) +else: + import pinax.env + +from django.core.management import execute_from_command_line + + +pinax.env.setup_environ(__file__) + + +if __name__ == "__main__": + execute_from_command_line() diff --git a/symposion_project/requirements/base.txt b/symposion_project/requirements/base.txt new file mode 100644 index 00000000..ba75d619 --- /dev/null +++ b/symposion_project/requirements/base.txt @@ -0,0 +1,25 @@ +# base.txt is a pip requirements file which describes the necessary +# distributions required to run this project. If you need something that is +# project specific not listed here use project.txt. You can, of course, update +# versions of distributions here if needed. + +--extra-index-url=http://dist.pinaxproject.com/dev/ +--extra-index-url=http://dist.pinaxproject.com/alpha/ +--extra-index-url=http://dist.pinaxproject.com/fresh-start/ + +Django==1.3.1 +Pinax + +django-debug-toolbar==0.9.1 +django-staticfiles==1.1.2 +django_compressor==1.0.1 + +django-mailer==0.2a1 +django-email-confirmation==0.2 +django-timezones==0.2 +pytz==2011n +django-openid==0.3a1 +python-openid==2.2.5 +metron==0.1 + +pinax-theme-bootstrap==0.1.5 diff --git a/symposion_project/requirements/project.txt b/symposion_project/requirements/project.txt new file mode 100644 index 00000000..6d8b1485 --- /dev/null +++ b/symposion_project/requirements/project.txt @@ -0,0 +1,7 @@ +# project.txt is a pip requirements file which describes the distributions +# required by your project to run. + +--requirement=base.txt + +# Put project-specific requirements here. +# See http://pip-installer.org/requirement-format.html for more information. \ No newline at end of file diff --git a/symposion_project/settings.py b/symposion_project/settings.py new file mode 100644 index 00000000..c84b830a --- /dev/null +++ b/symposion_project/settings.py @@ -0,0 +1,204 @@ +# -*- coding: utf-8 -*- +# Django settings for account project + +import os.path +import posixpath + +PROJECT_ROOT = os.path.abspath(os.path.dirname(__file__)) + +DEBUG = True +TEMPLATE_DEBUG = DEBUG + +# tells Pinax to serve media through the staticfiles app. +SERVE_MEDIA = DEBUG + +# django-compressor is turned off by default due to deployment overhead for +# most users. See for more information +COMPRESS = False + +INTERNAL_IPS = [ + "127.0.0.1", +] + +ADMINS = [ + # ("Your Name", "your_email@domain.com"), +] + +MANAGERS = ADMINS + +DATABASES = { + "default": { + "ENGINE": "django.db.backends.sqlite3", # Add "postgresql_psycopg2", "postgresql", "mysql", "sqlite3" or "oracle". + "NAME": "dev.db", # Or path to database file if using sqlite3. + "USER": "", # Not used with sqlite3. + "PASSWORD": "", # Not used with sqlite3. + "HOST": "", # Set to empty string for localhost. Not used with sqlite3. + "PORT": "", # Set to empty string for default. Not used with sqlite3. + } +} + +# Local time zone for this installation. Choices can be found here: +# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name +# although not all choices may be available on all operating systems. +# If running in a Windows environment this must be set to the same as your +# system time zone. +TIME_ZONE = "US/Eastern" + +# Language code for this installation. All choices can be found here: +# http://www.i18nguy.com/unicode/language-identifiers.html +LANGUAGE_CODE = "en-us" + +SITE_ID = 1 + +# If you set this to False, Django will make some optimizations so as not +# to load the internationalization machinery. +USE_I18N = True + +# Absolute path to the directory that holds media. +# Example: "/home/media/media.lawrence.com/" +MEDIA_ROOT = os.path.join(PROJECT_ROOT, "site_media", "media") + +# URL that handles the media served from MEDIA_ROOT. Make sure to use a +# trailing slash if there is a path component (optional in other cases). +# Examples: "http://media.lawrence.com", "http://example.com/media/" +MEDIA_URL = "/site_media/media/" + +# Absolute path to the directory that holds static files like app media. +# Example: "/home/media/media.lawrence.com/apps/" +STATIC_ROOT = os.path.join(PROJECT_ROOT, "site_media", "static") + +# URL that handles the static files like app media. +# Example: "http://media.lawrence.com" +STATIC_URL = "/site_media/static/" + +# Additional directories which hold static files +STATICFILES_DIRS = [ + os.path.join(PROJECT_ROOT, "static"), +] + +STATICFILES_FINDERS = [ + "staticfiles.finders.FileSystemFinder", + "staticfiles.finders.AppDirectoriesFinder", + "staticfiles.finders.LegacyAppDirectoriesFinder", + "compressor.finders.CompressorFinder", +] + +# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a +# trailing slash. +# Examples: "http://foo.com/media/", "/media/". +ADMIN_MEDIA_PREFIX = posixpath.join(STATIC_URL, "admin/") + +# Subdirectory of COMPRESS_ROOT to store the cached media files in +COMPRESS_OUTPUT_DIR = "cache" + +# Make this unique, and don't share it with anybody. +SECRET_KEY = "8*br)9@fs!4nzg-imfrsst&oa2udy6z-fqtdk0*e5c1=wn)(t3" + +# List of callables that know how to import templates from various sources. +TEMPLATE_LOADERS = [ + "django.template.loaders.filesystem.load_template_source", + "django.template.loaders.app_directories.load_template_source", +] + +MIDDLEWARE_CLASSES = [ + "django.middleware.common.CommonMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django_openid.consumer.SessionConsumer", + "django.contrib.messages.middleware.MessageMiddleware", + "pinax.apps.account.middleware.LocaleMiddleware", + "pinax.middleware.security.HideSensistiveFieldsMiddleware", + "debug_toolbar.middleware.DebugToolbarMiddleware", +] + +ROOT_URLCONF = "symposion_project.urls" + +TEMPLATE_DIRS = [ + os.path.join(PROJECT_ROOT, "templates"), +] + +TEMPLATE_CONTEXT_PROCESSORS = [ + "django.contrib.auth.context_processors.auth", + "django.core.context_processors.debug", + "django.core.context_processors.i18n", + "django.core.context_processors.media", + "django.core.context_processors.request", + "django.contrib.messages.context_processors.messages", + + "staticfiles.context_processors.static", + + "pinax.core.context_processors.pinax_settings", + + "pinax.apps.account.context_processors.account", +] + +INSTALLED_APPS = [ + # Django + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.sites", + "django.contrib.messages", + "django.contrib.humanize", + + "pinax.templatetags", + + # theme + "pinax_theme_bootstrap", + + # external + "staticfiles", + "compressor", + "debug_toolbar", + "mailer", + "django_openid", + "timezones", + "emailconfirmation", + "metron", + + # Pinax + "pinax.apps.account", + "pinax.apps.signup_codes", + + # project + "about", +] + +FIXTURE_DIRS = [ + os.path.join(PROJECT_ROOT, "fixtures"), +] + +MESSAGE_STORAGE = "django.contrib.messages.storage.session.SessionStorage" + +EMAIL_BACKEND = "mailer.backend.DbBackend" + +ACCOUNT_OPEN_SIGNUP = True +ACCOUNT_USE_OPENID = False +ACCOUNT_REQUIRED_EMAIL = False +ACCOUNT_EMAIL_VERIFICATION = False +ACCOUNT_EMAIL_AUTHENTICATION = False +ACCOUNT_UNIQUE_EMAIL = EMAIL_CONFIRMATION_UNIQUE_EMAIL = False + +AUTHENTICATION_BACKENDS = [ + "pinax.apps.account.auth_backends.AuthenticationBackend", +] + +LOGIN_URL = "/account/login/" # @@@ any way this can be a url name? +LOGIN_REDIRECT_URLNAME = "what_next" +LOGOUT_REDIRECT_URLNAME = "home" + +EMAIL_CONFIRMATION_DAYS = 2 +EMAIL_DEBUG = DEBUG + +DEBUG_TOOLBAR_CONFIG = { + "INTERCEPT_REDIRECTS": False, +} + +# local_settings.py can be used to override environment-specific settings +# like database and email that differ between development and production. +try: + from local_settings import * +except ImportError: + pass diff --git a/symposion_project/static/README b/symposion_project/static/README new file mode 100644 index 00000000..cd444f3c --- /dev/null +++ b/symposion_project/static/README @@ -0,0 +1,13 @@ +This directory is used to store static assets for your project. User media files +(FileFields/ImageFields) are not stored here. + +The convention for this directory is: + + * css/ — stores CSS files + * js/ — stores Javascript files + * img/ — stores image files + +This directory is accessed at /site_media/static/ in runserver when SERVE_MEDIA +is True. It is built with build_static for production environments. + +See http://pinaxproject.com/docs/dev/media.html for more information. \ No newline at end of file diff --git a/symposion_project/templates/_footer.html b/symposion_project/templates/_footer.html new file mode 100644 index 00000000..79a20fd5 --- /dev/null +++ b/symposion_project/templates/_footer.html @@ -0,0 +1,4 @@ +{% load i18n %} + \ No newline at end of file diff --git a/symposion_project/templates/about/what_next.html b/symposion_project/templates/about/what_next.html new file mode 100644 index 00000000..6ac3b548 --- /dev/null +++ b/symposion_project/templates/about/what_next.html @@ -0,0 +1,38 @@ +{% extends "site_base.html" %} + +{% load i18n %} +{% load ifsetting_tag %} + +{% block head_title %}{% trans "What Next?" %}{% endblock %} + +{% block body %} +

{% trans "What Next?" %}

+ + {% if user.is_authenticated %} +

Here are some things to do to get started with this site:

+ +
+
verify an email address
+
so you can receive notifications, reset your password and so people can find you more easily.
+ + {% ifsetting ACCOUNT_OPEN_SIGNUP %} + {% else %} + {% if user.is_staff %} +
invite more people to the site [admin only]
+
so more people can share in the fun.
+ {% endif %} + {% endifsetting %} +
+ {% else %} + {% url acct_login as login_url %} + +

+ {% ifsetting ACCOUNT_OPEN_SIGNUP %} + {% url acct_signup as signup_url %} + {% blocktrans %}Start by signing up and logging in.{% endblocktrans %} + {% else %} + {% blocktrans %}Start by logging in.{% endblocktrans %} + {% endifsetting %} +

+ {% endif %} +{% endblock %} diff --git a/symposion_project/templates/homepage.html b/symposion_project/templates/homepage.html new file mode 100644 index 00000000..962af389 --- /dev/null +++ b/symposion_project/templates/homepage.html @@ -0,0 +1,44 @@ +{% extends "banner_base.html" %} + +{% load i18n %} +{% load ifsetting_tag %} + +{% block head_title %}{% trans "Welcome" %}{% endblock %} + +{% block body_class %}home{% endblock %} + +{% block banner %} +

{% trans "Welcome to Pinax" %}

+

+ {% blocktrans %} + Pinax is a Django + project intended to provide a starting point for websites. By + integrating numerous reusable Django apps to take care of the + things that many sites have in common, it lets you focus on what + makes your site different. + {% endblocktrans %} +

+ +

About Account Project

+

+ {% blocktrans %} + This project takes the zero_project and adds basic account management + functionality such as sign up, log in, password change/reset and email + confirmation. It is a foundation suitable for most sites that have user + accounts. + {% endblocktrans %} +

+ + {% if user.is_authenticated %} + {% url what_next as what_next_url %} +

{% blocktrans %}Wondering What Next?{% endblocktrans %}

+ {% else %} + {% url acct_login as login_url %} + {% ifsetting ACCOUNT_OPEN_SIGNUP %} + {% url acct_signup as signup_url %} +

{% blocktrans %}You can Log In or Sign Up to try out the site.{% endblocktrans %}

+ {% else %} +

{% blocktrans %}You can Log In to try out the site.{% endblocktrans %}

+ {% endifsetting %} + {% endif %} +{% endblock %} diff --git a/symposion_project/templates/site_base.html b/symposion_project/templates/site_base.html new file mode 100644 index 00000000..6c811b81 --- /dev/null +++ b/symposion_project/templates/site_base.html @@ -0,0 +1,19 @@ +{% extends "theme_base.html" %} + +{% load metron_tags %} +{% load i18n %} + +{% block extra_head_base %} + {% block extra_head %}{% endblock %} +{% endblock %} + +{% block footer %} + +{% endblock %} + +{% block extra_body_base %} + {% analytics %} + {% block extra_body %}{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/symposion_project/urls.py b/symposion_project/urls.py new file mode 100644 index 00000000..e8feb4b1 --- /dev/null +++ b/symposion_project/urls.py @@ -0,0 +1,29 @@ +from django.conf import settings +from django.conf.urls.defaults import * +from django.views.generic.simple import direct_to_template + +from django.contrib import admin +admin.autodiscover() + +from pinax.apps.account.openid_consumer import PinaxConsumer + + +handler500 = "pinax.views.server_error" + + +urlpatterns = patterns("", + url(r"^$", direct_to_template, { + "template": "homepage.html", + }, name="home"), + url(r"^admin/invite_user/$", "pinax.apps.signup_codes.views.admin_invite_user", name="admin_invite_user"), + url(r"^admin/", include(admin.site.urls)), + url(r"^about/", include("about.urls")), + url(r"^account/", include("pinax.apps.account.urls")), + url(r"^openid/", include(PinaxConsumer().urls)), +) + + +if settings.SERVE_MEDIA: + urlpatterns += patterns("", + url(r"", include("staticfiles.urls")), + ) diff --git a/symposion_project/wsgi.py b/symposion_project/wsgi.py new file mode 100644 index 00000000..726e2a7b --- /dev/null +++ b/symposion_project/wsgi.py @@ -0,0 +1,11 @@ +from django.core.handlers.wsgi import WSGIHandler + +import pinax.env + + +# setup the environment for Django and Pinax +pinax.env.setup_environ(__file__) + + +# set application for WSGI processing +application = WSGIHandler() \ No newline at end of file From 46e38bc758413be6d266a01b7e12f906a31abc34 Mon Sep 17 00:00:00 2001 From: James Tauber Date: Sun, 5 Feb 2012 15:03:30 -0500 Subject: [PATCH 003/751] initial sponsorship app adapted from DjangoCon --- .../apps/sponsorship/__init__.py | 0 symposion_project/apps/sponsorship/admin.py | 7 +++ symposion_project/apps/sponsorship/models.py | 32 ++++++++++++++ .../apps/sponsorship/templatetags/__init__.py | 0 .../templatetags/sponsorship_tags.py | 43 +++++++++++++++++++ 5 files changed, 82 insertions(+) create mode 100644 symposion_project/apps/sponsorship/__init__.py create mode 100644 symposion_project/apps/sponsorship/admin.py create mode 100644 symposion_project/apps/sponsorship/models.py create mode 100644 symposion_project/apps/sponsorship/templatetags/__init__.py create mode 100644 symposion_project/apps/sponsorship/templatetags/sponsorship_tags.py diff --git a/symposion_project/apps/sponsorship/__init__.py b/symposion_project/apps/sponsorship/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/symposion_project/apps/sponsorship/admin.py b/symposion_project/apps/sponsorship/admin.py new file mode 100644 index 00000000..6df73af2 --- /dev/null +++ b/symposion_project/apps/sponsorship/admin.py @@ -0,0 +1,7 @@ +from django.contrib import admin + +from symposion.sponsors.models import SponsorLevel, Sponsor + + +admin.site.register(SponsorLevel) +admin.site.register(Sponsor) diff --git a/symposion_project/apps/sponsorship/models.py b/symposion_project/apps/sponsorship/models.py new file mode 100644 index 00000000..743f6d75 --- /dev/null +++ b/symposion_project/apps/sponsorship/models.py @@ -0,0 +1,32 @@ +import datetime + +from django.db import models + + +class SponsorLevel(models.Model): + + name = models.CharField(max_length=100) + order = models.IntegerField(default=0) + description = models.TextField(blank=True) + + class Meta: + ordering = ["order"] + + def __unicode__(self): + return self.name + + +class Sponsor(models.Model): + + name = models.CharField(max_length=100) + external_url = models.URLField("external URL") + annotation = models.TextField(blank=True) + contact_name = models.CharField(max_length=100) + contact_email = models.EmailField(u"Contact e\u2011mail") + logo = models.ImageField(upload_to="sponsor_logos/") + level = models.ForeignKey(SponsorLevel) + added = models.DateTimeField(default=datetime.datetime.now) + active = models.BooleanField(default=False) + + def __unicode__(self): + return self.name diff --git a/symposion_project/apps/sponsorship/templatetags/__init__.py b/symposion_project/apps/sponsorship/templatetags/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/symposion_project/apps/sponsorship/templatetags/sponsorship_tags.py b/symposion_project/apps/sponsorship/templatetags/sponsorship_tags.py new file mode 100644 index 00000000..baae404b --- /dev/null +++ b/symposion_project/apps/sponsorship/templatetags/sponsorship_tags.py @@ -0,0 +1,43 @@ +from django import template + +from sponsorship.models import Sponsor + + +register = template.Library() + + +class SponsorsNode(template.Node): + + @classmethod + def handle_token(cls, parser, token): + bits = token.split_contents() + if len(bits) != 4: + raise template.TemplateSyntaxError("%r takes exactly three arguments " + "(second argument must be 'as')" % bits[0]) + if bits[2] != "as": + raise template.TemplateSyntaxError("Second argument to %r must be " + "'as'" % bits[0]) + return cls(bits[1], bits[3]) + + def __init__(self, level, context_var): + self.level = template.Variable(level) + self.context_var = context_var + + def render(self, context): + level = self.level.resolve(context) + queryset = Sponsor.objects.filter( + level__name__iexact = level, + active = True + ).order_by( + "added" + ) + context[self.context_var] = queryset + return u"" + + +@register.tag +def sponsors(parser, token): + """ + {% sponsors "gold" as sponsors %} + """ + return SponsorsNode.handle_token(parser, token) From 64569bccc72a7c30c163eac23dcd45c7ad8ff6b5 Mon Sep 17 00:00:00 2001 From: James Tauber Date: Sun, 5 Feb 2012 15:51:27 -0500 Subject: [PATCH 004/751] fixed incorrect module name in import --- symposion_project/apps/sponsorship/admin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/symposion_project/apps/sponsorship/admin.py b/symposion_project/apps/sponsorship/admin.py index 6df73af2..b69aff91 100644 --- a/symposion_project/apps/sponsorship/admin.py +++ b/symposion_project/apps/sponsorship/admin.py @@ -1,6 +1,6 @@ from django.contrib import admin -from symposion.sponsors.models import SponsorLevel, Sponsor +from sponsorship.models import SponsorLevel, Sponsor admin.site.register(SponsorLevel) From e4e7b890a2a6f39054cc414711de34a51d64850a Mon Sep 17 00:00:00 2001 From: James Tauber Date: Sun, 5 Feb 2012 17:13:06 -0500 Subject: [PATCH 005/751] added sponsorship template tags and template fragments; improved admin --- symposion_project/apps/sponsorship/admin.py | 4 +- symposion_project/apps/sponsorship/models.py | 3 + .../templatetags/sponsorship_tags.py | 65 ++++++++++++++----- symposion_project/requirements/project.txt | 5 +- symposion_project/settings.py | 2 + .../sponsorship/_horizontal_by_level.html | 11 ++++ .../templates/sponsorship/_sponsor_link.html | 10 +++ .../sponsorship/_vertical_by_level.html | 13 ++++ .../templates/sponsorship/_wall.html | 7 ++ 9 files changed, 99 insertions(+), 21 deletions(-) create mode 100644 symposion_project/templates/sponsorship/_horizontal_by_level.html create mode 100644 symposion_project/templates/sponsorship/_sponsor_link.html create mode 100644 symposion_project/templates/sponsorship/_vertical_by_level.html create mode 100644 symposion_project/templates/sponsorship/_wall.html diff --git a/symposion_project/apps/sponsorship/admin.py b/symposion_project/apps/sponsorship/admin.py index b69aff91..e234e794 100644 --- a/symposion_project/apps/sponsorship/admin.py +++ b/symposion_project/apps/sponsorship/admin.py @@ -3,5 +3,5 @@ from django.contrib import admin from sponsorship.models import SponsorLevel, Sponsor -admin.site.register(SponsorLevel) -admin.site.register(Sponsor) +admin.site.register(SponsorLevel, list_display=("order", "name")) +admin.site.register(Sponsor, list_display=("name", "level", "added", "active"), list_filter = ("level", )) diff --git a/symposion_project/apps/sponsorship/models.py b/symposion_project/apps/sponsorship/models.py index 743f6d75..f77de766 100644 --- a/symposion_project/apps/sponsorship/models.py +++ b/symposion_project/apps/sponsorship/models.py @@ -14,6 +14,9 @@ class SponsorLevel(models.Model): def __unicode__(self): return self.name + + def sponsors(self): + return self.sponsor_set.filter(active=True).order_by("added") class Sponsor(models.Model): diff --git a/symposion_project/apps/sponsorship/templatetags/sponsorship_tags.py b/symposion_project/apps/sponsorship/templatetags/sponsorship_tags.py index baae404b..1c59be41 100644 --- a/symposion_project/apps/sponsorship/templatetags/sponsorship_tags.py +++ b/symposion_project/apps/sponsorship/templatetags/sponsorship_tags.py @@ -1,6 +1,6 @@ from django import template -from sponsorship.models import Sponsor +from sponsorship.models import Sponsor, SponsorLevel register = template.Library() @@ -11,33 +11,62 @@ class SponsorsNode(template.Node): @classmethod def handle_token(cls, parser, token): bits = token.split_contents() - if len(bits) != 4: - raise template.TemplateSyntaxError("%r takes exactly three arguments " - "(second argument must be 'as')" % bits[0]) - if bits[2] != "as": - raise template.TemplateSyntaxError("Second argument to %r must be " - "'as'" % bits[0]) - return cls(bits[1], bits[3]) + if len(bits) == 3 and bits[1] == "as": + return cls(bits[2]) + elif len(bits) == 4 and bits[2] == "as": + return cls(bits[3], bits[1]) + else: + raise template.TemplateSyntaxError("%r takes 'as var' or 'level as var'" % bits[0]) - def __init__(self, level, context_var): - self.level = template.Variable(level) + def __init__(self, context_var, level=None): + if level: + self.level = template.Variable(level) + else: + self.level = None self.context_var = context_var def render(self, context): - level = self.level.resolve(context) - queryset = Sponsor.objects.filter( - level__name__iexact = level, - active = True - ).order_by( - "added" - ) + if self.level: + level = self.level.resolve(context) + queryset = Sponsor.objects.filter(level__name__iexact = level, active = True).order_by("added") + else: + queryset = Sponsor.objects.filter(active = True).order_by("level__order", "added") context[self.context_var] = queryset return u"" +class SponsorLevelNode(template.Node): + + @classmethod + def handle_token(cls, parser, token): + bits = token.split_contents() + if len(bits) == 3 and bits[1] == "as": + return cls(bits[2]) + else: + raise template.TemplateSyntaxError("%r takes 'as var'" % bits[0]) + + def __init__(self, context_var): + self.context_var = context_var + + def render(self, context): + context[self.context_var] = SponsorLevel.objects.all() + return u"" + + @register.tag def sponsors(parser, token): """ - {% sponsors "gold" as sponsors %} + {% sponsors as all_sponsors %} + or + {% sponsors "gold" as gold_sponsors %} """ return SponsorsNode.handle_token(parser, token) + + +@register.tag +def sponsor_levels(parser, token): + """ + {% sponsor_levels as levels %} + """ + return SponsorLevelNode.handle_token(parser, token) + \ No newline at end of file diff --git a/symposion_project/requirements/project.txt b/symposion_project/requirements/project.txt index 6d8b1485..0d0fa5f3 100644 --- a/symposion_project/requirements/project.txt +++ b/symposion_project/requirements/project.txt @@ -4,4 +4,7 @@ --requirement=base.txt # Put project-specific requirements here. -# See http://pip-installer.org/requirement-format.html for more information. \ No newline at end of file +# See http://pip-installer.org/requirement-format.html for more information. + +PIL==1.1.7 +easy-thumbnails==1.0-alpha-21 diff --git a/symposion_project/settings.py b/symposion_project/settings.py index c84b830a..4bef98ec 100644 --- a/symposion_project/settings.py +++ b/symposion_project/settings.py @@ -157,6 +157,7 @@ INSTALLED_APPS = [ "timezones", "emailconfirmation", "metron", + "easy_thumbnails", # Pinax "pinax.apps.account", @@ -164,6 +165,7 @@ INSTALLED_APPS = [ # project "about", + "sponsorship", ] FIXTURE_DIRS = [ diff --git a/symposion_project/templates/sponsorship/_horizontal_by_level.html b/symposion_project/templates/sponsorship/_horizontal_by_level.html new file mode 100644 index 00000000..98025053 --- /dev/null +++ b/symposion_project/templates/sponsorship/_horizontal_by_level.html @@ -0,0 +1,11 @@ +{% load sponsorship_tags %} +{% sponsor_levels as levels %} + diff --git a/symposion_project/templates/sponsorship/_sponsor_link.html b/symposion_project/templates/sponsorship/_sponsor_link.html new file mode 100644 index 00000000..39d9aade --- /dev/null +++ b/symposion_project/templates/sponsorship/_sponsor_link.html @@ -0,0 +1,10 @@ +{% load thumbnail %} +{% spaceless %} + + {% if dimensions %} + {{ sponsor.name }} + {% else %} + {{ sponsor.name }} + {% endif %} + +{% endspaceless %} diff --git a/symposion_project/templates/sponsorship/_vertical_by_level.html b/symposion_project/templates/sponsorship/_vertical_by_level.html new file mode 100644 index 00000000..b2ed5ec4 --- /dev/null +++ b/symposion_project/templates/sponsorship/_vertical_by_level.html @@ -0,0 +1,13 @@ +{% load sponsorship_tags %} +{% sponsor_levels as levels %} + diff --git a/symposion_project/templates/sponsorship/_wall.html b/symposion_project/templates/sponsorship/_wall.html new file mode 100644 index 00000000..7f1b9896 --- /dev/null +++ b/symposion_project/templates/sponsorship/_wall.html @@ -0,0 +1,7 @@ +{% load sponsorship_tags %} +{% sponsors as sponsors %} + From cfc5db0a08e7dc1201518b57828f22dfa23b6a60 Mon Sep 17 00:00:00 2001 From: James Tauber Date: Sun, 5 Feb 2012 17:14:46 -0500 Subject: [PATCH 006/751] upgraded bootstrap theme --- symposion_project/requirements/base.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/symposion_project/requirements/base.txt b/symposion_project/requirements/base.txt index ba75d619..310c4c1e 100644 --- a/symposion_project/requirements/base.txt +++ b/symposion_project/requirements/base.txt @@ -22,4 +22,4 @@ django-openid==0.3a1 python-openid==2.2.5 metron==0.1 -pinax-theme-bootstrap==0.1.5 +pinax-theme-bootstrap==0.2.1 From a3cae9f764a88ba5c0617bf0a5385dfba49631ed Mon Sep 17 00:00:00 2001 From: James Tauber Date: Sun, 5 Feb 2012 17:32:14 -0500 Subject: [PATCH 007/751] initial sphinx configuration --- symposion_project/docs/Makefile | 153 +++++++++++++++ symposion_project/docs/conf.py | 242 ++++++++++++++++++++++++ symposion_project/docs/index.rst | 22 +++ symposion_project/requirements/docs.txt | 3 + 4 files changed, 420 insertions(+) create mode 100644 symposion_project/docs/Makefile create mode 100644 symposion_project/docs/conf.py create mode 100644 symposion_project/docs/index.rst create mode 100644 symposion_project/requirements/docs.txt diff --git a/symposion_project/docs/Makefile b/symposion_project/docs/Makefile new file mode 100644 index 00000000..affca18b --- /dev/null +++ b/symposion_project/docs/Makefile @@ -0,0 +1,153 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + -rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/PinaxSymposion.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/PinaxSymposion.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/PinaxSymposion" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/PinaxSymposion" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." diff --git a/symposion_project/docs/conf.py b/symposion_project/docs/conf.py new file mode 100644 index 00000000..a6153576 --- /dev/null +++ b/symposion_project/docs/conf.py @@ -0,0 +1,242 @@ +# -*- coding: utf-8 -*- +# +# Pinax Symposion documentation build configuration file, created by +# sphinx-quickstart on Sun Feb 5 17:31:13 2012. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys, os + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.insert(0, os.path.abspath('.')) + +# -- General configuration ----------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.viewcode'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'Pinax Symposion' +copyright = u'2012, Eldarion Team' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '0.5' +# The full version, including alpha/beta/rc tags. +release = '0.5dev' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + + +# -- Options for HTML output --------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'default' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = 'PinaxSymposiondoc' + + +# -- Options for LaTeX output -------------------------------------------------- + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('index', 'PinaxSymposion.tex', u'Pinax Symposion Documentation', + u'Eldarion Team', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output -------------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'pinaxsymposion', u'Pinax Symposion Documentation', + [u'Eldarion Team'], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------------ + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ('index', 'PinaxSymposion', u'Pinax Symposion Documentation', + u'Eldarion Team', 'PinaxSymposion', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' diff --git a/symposion_project/docs/index.rst b/symposion_project/docs/index.rst new file mode 100644 index 00000000..ad20af5c --- /dev/null +++ b/symposion_project/docs/index.rst @@ -0,0 +1,22 @@ +.. Pinax Symposion documentation master file, created by + sphinx-quickstart on Sun Feb 5 17:31:13 2012. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to Pinax Symposion's documentation! +=========================================== + +Contents: + +.. toctree:: + :maxdepth: 2 + + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + diff --git a/symposion_project/requirements/docs.txt b/symposion_project/requirements/docs.txt new file mode 100644 index 00000000..82750ff4 --- /dev/null +++ b/symposion_project/requirements/docs.txt @@ -0,0 +1,3 @@ +# requirements needed to build the docs + +Sphinx==1.1.2 From df720729a0df8479ba88a295f14b0408972a0a25 Mon Sep 17 00:00:00 2001 From: James Tauber Date: Sun, 5 Feb 2012 17:45:46 -0500 Subject: [PATCH 008/751] added gitignore with docs _build --- .gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..e35d8850 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +_build From c4b03049ed79fe5ff1a32b4084b23b0aa61d903e Mon Sep 17 00:00:00 2001 From: James Tauber Date: Sun, 5 Feb 2012 17:51:04 -0500 Subject: [PATCH 009/751] added some initial wording to the docs --- symposion_project/docs/index.rst | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/symposion_project/docs/index.rst b/symposion_project/docs/index.rst index ad20af5c..af2535e8 100644 --- a/symposion_project/docs/index.rst +++ b/symposion_project/docs/index.rst @@ -1,10 +1,14 @@ -.. Pinax Symposion documentation master file, created by - sphinx-quickstart on Sun Feb 5 17:31:13 2012. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. +Pinax Symposion +=============== -Welcome to Pinax Symposion's documentation! -=========================================== +Pinax Symposion is an open-source Django project for conference websites. + +It came out of development done by Eldarion for DjangoCon US and US PyCon +but has been independently used for a number of other conferences. + +We are in the process of cleaning things up and making them more generic. + +The project homepage is http://pinax.github.com/symposion/ Contents: From 3d6a69466b57b9cd32e6cea605a9d62d7fb44699 Mon Sep 17 00:00:00 2001 From: James Tauber Date: Sun, 5 Feb 2012 18:18:30 -0500 Subject: [PATCH 010/751] initial documentation of sponsorship app --- symposion_project/docs/index.rst | 5 +- symposion_project/docs/sponsorship.rst | 82 ++++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 symposion_project/docs/sponsorship.rst diff --git a/symposion_project/docs/index.rst b/symposion_project/docs/index.rst index af2535e8..ec72ecbc 100644 --- a/symposion_project/docs/index.rst +++ b/symposion_project/docs/index.rst @@ -10,11 +10,12 @@ We are in the process of cleaning things up and making them more generic. The project homepage is http://pinax.github.com/symposion/ -Contents: +Apps: .. toctree:: :maxdepth: 2 - + + sponsorship Indices and tables diff --git a/symposion_project/docs/sponsorship.rst b/symposion_project/docs/sponsorship.rst new file mode 100644 index 00000000..5907a1a3 --- /dev/null +++ b/symposion_project/docs/sponsorship.rst @@ -0,0 +1,82 @@ +Sponsorship App +=============== + +Sponsorship is managed via the ``sponsorship`` app. + +Sponsorship levels and sponsors are added via the Django admin. + + +Models +------ + +Each sponsor level has a ``name`` (e.g. "Gold", "Silver") and an ``order`` +field which is an integer that is used to sort levels (lowest first). Each +level also has a ``description`` which is not currently exposed anywhere +but can be used for private annotation. + +Each sponsor has a ``name``, ``external_url`` (i.e. link to the sponsor's +website), ``contact_name`` and ``contact_email``, ``logo``, and ``level``. + +A sponsor may also have a private ``annotation`` that can be used by +organizers to take notes about the sponsor. + +A sponsor will not appear on the site until the ``active`` flag is set true. + + +Template Snippets +----------------- + +The easiest way to include sponsor logos, grouped by level, is to either:: + + {% include "sponsorship/_vertical_by_level.html" %} + +or:: + + {% include "sponsorship/_horizontal_by_level.html" %} + +You can get a wall of sponsors (without level designation) with:: + + {% include "sponsorship/_wall.html" %} + + +You can always tweak these templates or use them as the basis for your own. +This is often all you'll need to do to display sponsors on your site. + +If you want to display a specific sponsor logo you can use:: + + {% include "sponsorship/_sponsor_link.html" with sponsor=sponsor %} + +or:: + + {% include "sponsorship/_sponsor_link.html" with sponsor=sponsor dimensions="100x100" %} + +if you want different dimensions than the default 150 x 150. + + +Template Tags +------------- + +If you want to retrieve the sponsors and traverse them yourself, you can use +the provided template tags:: + + {% load sponsorship_tags %} + + {% sponsors as all_sponsors %} + +or:: + + {% load sponsorship_tags %} + + {% sponsors "Gold" as gold_sponsors %} + +if you want to just get a specific level. + + +You can get the levels with:: + + {% load sponsorship_tags %} + + {% sponsor_levels as levels %} + +and you can always iterate over those levels, calling ``level.sponsors`` to +get the sponsors at that level. From d0be075c89c69dfb65f1458d3fd8cd389af6732c Mon Sep 17 00:00:00 2001 From: Luke Hatcher Date: Sun, 5 Feb 2012 20:34:49 -0500 Subject: [PATCH 011/751] adding prototype cms app with pages and menu --- symposion_project/apps/cms/__init__.py | 0 symposion_project/apps/cms/admin.py | 19 +++++ .../apps/cms/context_processors.py | 10 +++ symposion_project/apps/cms/models.py | 81 +++++++++++++++++++ symposion_project/apps/cms/views.py | 13 +++ symposion_project/requirements/base.txt | 4 + symposion_project/settings.py | 6 ++ .../templates/cms/page_detail.html | 6 ++ symposion_project/urls.py | 3 + 9 files changed, 142 insertions(+) create mode 100644 symposion_project/apps/cms/__init__.py create mode 100644 symposion_project/apps/cms/admin.py create mode 100644 symposion_project/apps/cms/context_processors.py create mode 100644 symposion_project/apps/cms/models.py create mode 100644 symposion_project/apps/cms/views.py create mode 100644 symposion_project/templates/cms/page_detail.html diff --git a/symposion_project/apps/cms/__init__.py b/symposion_project/apps/cms/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/symposion_project/apps/cms/admin.py b/symposion_project/apps/cms/admin.py new file mode 100644 index 00000000..4246494c --- /dev/null +++ b/symposion_project/apps/cms/admin.py @@ -0,0 +1,19 @@ +from django.contrib import admin + +from mptt.admin import MPTTModelAdmin + +from cms.models import MenuItem, Page + + +class PageAdmin(MPTTModelAdmin): + prepopulated_fields = {"slug": ("title",)} + list_display = ("title", "published", "status") + +admin.site.register(Page, PageAdmin) + + +class MenuItemAdmin(MPTTModelAdmin): + prepopulated_fields = {"slug": ("name",)} + list_display = ("name", "login_required", "published",) + +admin.site.register(MenuItem, MenuItemAdmin) diff --git a/symposion_project/apps/cms/context_processors.py b/symposion_project/apps/cms/context_processors.py new file mode 100644 index 00000000..207c1793 --- /dev/null +++ b/symposion_project/apps/cms/context_processors.py @@ -0,0 +1,10 @@ +from cms.models import MenuItem + + +def menuitems(request): + qs = MenuItem.objects.filter(published=True) + if not request.user.is_authenticated(): + qs = qs.filter(login_required=False) + return { + "menuitems": qs, + } diff --git a/symposion_project/apps/cms/models.py b/symposion_project/apps/cms/models.py new file mode 100644 index 00000000..7f3f775e --- /dev/null +++ b/symposion_project/apps/cms/models.py @@ -0,0 +1,81 @@ +from datetime import datetime + +from django.db import models +from django.utils.translation import ugettext_lazy as _ + +from markitup.fields import MarkupField + +from taggit.managers import TaggableManager + +from mptt.models import MPTTModel, TreeForeignKey +from mptt.utils import drilldown_tree_for_node + + +class ContentBase(models.Model): + + STATUS_CHOICES = ( + (1, _("Draft")), + (2, _("Public")), + ) + + title = models.CharField(max_length=100) + slug = models.CharField(max_length=100, blank=True, null=True) + body = MarkupField() + + tags = TaggableManager(blank=True) + + status = models.IntegerField(choices=STATUS_CHOICES, default=2) + published = models.DateTimeField(default=datetime.now) + created = models.DateTimeField(editable=False, default=datetime.now) + updated = models.DateTimeField(editable=False, default=datetime.now) + + class Meta: + abstract = True + + +class Page(MPTTModel, ContentBase): + + parent = TreeForeignKey("self", null=True, blank=True, related_name="children") + ordering = models.PositiveIntegerField(default=1) + path = models.TextField(blank=True, editable=False) + + def __unicode__(self): + return self.title + + def save(self, calculate_path=True, *args, **kwargs): + super(Page, self).save(*args, **kwargs) + if calculate_path: + self.calculate_path() + + def calculate_path(self): + self.path = "" + for page in drilldown_tree_for_node(self): + if page == self: + self.path += page.slug + break + else: + self.path += "%s/" % page.slug + self.save(calculate_path=False) + + class MPTTMeta: + order_insertion_by = ["title"] + + +class MenuItem(MPTTModel): + + name = models.CharField(max_length=50, unique=True) + slug = models.SlugField() + parent = TreeForeignKey("self", null=True, blank=True, related_name="children") + + url = models.CharField(max_length=200) + #FIELDNAME = models.ForeignKey() + + published = models.BooleanField(default=True) + login_required = models.BooleanField(default=False) + + def __unicode__(self): + return self.name + + + class MPTTMeta: + order_insertion_by = ["name"] diff --git a/symposion_project/apps/cms/views.py b/symposion_project/apps/cms/views.py new file mode 100644 index 00000000..e21c790f --- /dev/null +++ b/symposion_project/apps/cms/views.py @@ -0,0 +1,13 @@ +from django.shortcuts import render_to_response +from django.template import RequestContext + +from cms.models import Page + + +def page(request, slug): + + page = Page.objects.get(path=slug) + + return render_to_response("cms/page_detail.html", { + "page": page, + }, context_instance=RequestContext(request)) diff --git a/symposion_project/requirements/base.txt b/symposion_project/requirements/base.txt index ba75d619..6c85c658 100644 --- a/symposion_project/requirements/base.txt +++ b/symposion_project/requirements/base.txt @@ -23,3 +23,7 @@ python-openid==2.2.5 metron==0.1 pinax-theme-bootstrap==0.1.5 +django-taggit==0.9.3 +django-reversion==1.5.1 +django-markitup==1.0.0 +markdown==2.1.1 diff --git a/symposion_project/settings.py b/symposion_project/settings.py index c84b830a..91a363de 100644 --- a/symposion_project/settings.py +++ b/symposion_project/settings.py @@ -157,6 +157,10 @@ INSTALLED_APPS = [ "timezones", "emailconfirmation", "metron", + "markitup", + "taggit", + "cms", + "mptt", # Pinax "pinax.apps.account", @@ -196,6 +200,8 @@ DEBUG_TOOLBAR_CONFIG = { "INTERCEPT_REDIRECTS": False, } +MARKITUP_FILTER = ("markdown.markdown", {"safe_mode": True}) + # local_settings.py can be used to override environment-specific settings # like database and email that differ between development and production. try: diff --git a/symposion_project/templates/cms/page_detail.html b/symposion_project/templates/cms/page_detail.html new file mode 100644 index 00000000..c6ee9b14 --- /dev/null +++ b/symposion_project/templates/cms/page_detail.html @@ -0,0 +1,6 @@ +{% extends "site_base.html" %} + +{% block body %} +

{{ page.title }}

+ {{ page.body }} +{% endblock %} \ No newline at end of file diff --git a/symposion_project/urls.py b/symposion_project/urls.py index e8feb4b1..c22b7126 100644 --- a/symposion_project/urls.py +++ b/symposion_project/urls.py @@ -10,6 +10,7 @@ from pinax.apps.account.openid_consumer import PinaxConsumer handler500 = "pinax.views.server_error" +WIKI_SLUG = r"(([\w-]{2,})(/[\w-]{2,})*)" urlpatterns = patterns("", url(r"^$", direct_to_template, { @@ -20,6 +21,8 @@ urlpatterns = patterns("", url(r"^about/", include("about.urls")), url(r"^account/", include("pinax.apps.account.urls")), url(r"^openid/", include(PinaxConsumer().urls)), + url(r"^markitup/", include("markitup.urls")), + url(r"^pages/(?P%s)/$" % WIKI_SLUG, "cms.views.page", name="cms_page"), ) From 53fecaafc16a747651a45630a93a72ad0b084f6d Mon Sep 17 00:00:00 2001 From: Luke Hatcher Date: Sun, 5 Feb 2012 20:50:17 -0500 Subject: [PATCH 012/751] add ordering to menus and pages --- symposion_project/apps/cms/models.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/symposion_project/apps/cms/models.py b/symposion_project/apps/cms/models.py index 7f3f775e..7a72cc1d 100644 --- a/symposion_project/apps/cms/models.py +++ b/symposion_project/apps/cms/models.py @@ -58,7 +58,7 @@ class Page(MPTTModel, ContentBase): self.save(calculate_path=False) class MPTTMeta: - order_insertion_by = ["title"] + order_insertion_by = ["ordering", "title"] class MenuItem(MPTTModel): @@ -68,14 +68,14 @@ class MenuItem(MPTTModel): parent = TreeForeignKey("self", null=True, blank=True, related_name="children") url = models.CharField(max_length=200) - #FIELDNAME = models.ForeignKey() published = models.BooleanField(default=True) login_required = models.BooleanField(default=False) + ordering = models.PositiveIntegerField(default=1) def __unicode__(self): return self.name class MPTTMeta: - order_insertion_by = ["name"] + order_insertion_by = ["ordering", "name"] From 37e6d51792c900e21b0f8be74af27c5d4bb6b5ac Mon Sep 17 00:00:00 2001 From: James Tauber Date: Sun, 5 Feb 2012 21:59:14 -0500 Subject: [PATCH 013/751] initial conference/section models --- symposion_project/apps/conference/__init__.py | 0 symposion_project/apps/conference/admin.py | 7 ++++ symposion_project/apps/conference/models.py | 40 +++++++++++++++++++ symposion_project/settings.py | 1 + 4 files changed, 48 insertions(+) create mode 100644 symposion_project/apps/conference/__init__.py create mode 100644 symposion_project/apps/conference/admin.py create mode 100644 symposion_project/apps/conference/models.py diff --git a/symposion_project/apps/conference/__init__.py b/symposion_project/apps/conference/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/symposion_project/apps/conference/admin.py b/symposion_project/apps/conference/admin.py new file mode 100644 index 00000000..e385acdf --- /dev/null +++ b/symposion_project/apps/conference/admin.py @@ -0,0 +1,7 @@ +from django.contrib import admin + +from conference.models import Conference, Section + + +admin.site.register(Conference, list_display=("title", "start_date", "end_date")) +admin.site.register(Section, list_display=("name", "conference", "start_date", "end_date")) diff --git a/symposion_project/apps/conference/models.py b/symposion_project/apps/conference/models.py new file mode 100644 index 00000000..b09c94bf --- /dev/null +++ b/symposion_project/apps/conference/models.py @@ -0,0 +1,40 @@ +from django.db import models + +from timezones.fields import TimeZoneField + + +class Conference(models.Model): + """ + the full conference for a specific year, e.g. US PyCon 2012. + """ + + title = models.CharField(max_length=100) + + # when the conference runs + start_date = models.DateField(null=True, blank=True) + end_date = models.DateField(null=True, blank=True) + + # timezone the conference is in + timezone = TimeZoneField(blank=True) + + def __unicode__(self): + return self.title + + +class Section(models.Model): + """ + a section of the conference such as "Tutorials", "Workshops", + "Talks", "Expo", "Sprints", that may have its own review and + scheduling process. + """ + + conference = models.ForeignKey(Conference) + + name = models.CharField(max_length=100) + + # when the section runs + start_date = models.DateField(null=True, blank=True) + end_date = models.DateField(null=True, blank=True) + + def __unicode__(self): + return self.name diff --git a/symposion_project/settings.py b/symposion_project/settings.py index c84b830a..84d742a5 100644 --- a/symposion_project/settings.py +++ b/symposion_project/settings.py @@ -164,6 +164,7 @@ INSTALLED_APPS = [ # project "about", + "conference", ] FIXTURE_DIRS = [ From 5b94e400590934a96fec841d4c6527f4eca0cd09 Mon Sep 17 00:00:00 2001 From: James Tauber Date: Sun, 5 Feb 2012 21:59:53 -0500 Subject: [PATCH 014/751] added pyc and dev.db to gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index e35d8850..f34d58c9 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ _build +*.pyc +dev.db From 4ca2118bca7ac539ef0028efeff4c24edfc483d4 Mon Sep 17 00:00:00 2001 From: Luke Hatcher Date: Sun, 5 Feb 2012 22:01:56 -0500 Subject: [PATCH 015/751] adding menu context processor and hooking up template --- symposion_project/settings.py | 2 ++ symposion_project/templates/site_base.html | 23 ++++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/symposion_project/settings.py b/symposion_project/settings.py index 91a363de..8bade403 100644 --- a/symposion_project/settings.py +++ b/symposion_project/settings.py @@ -131,6 +131,8 @@ TEMPLATE_CONTEXT_PROCESSORS = [ "pinax.core.context_processors.pinax_settings", "pinax.apps.account.context_processors.account", + + "cms.context_processors.menuitems", ] INSTALLED_APPS = [ diff --git a/symposion_project/templates/site_base.html b/symposion_project/templates/site_base.html index 6c811b81..7c1b8d09 100644 --- a/symposion_project/templates/site_base.html +++ b/symposion_project/templates/site_base.html @@ -2,11 +2,34 @@ {% load metron_tags %} {% load i18n %} +{% load mptt_tags %} {% block extra_head_base %} {% block extra_head %}{% endblock %} {% endblock %} +{% block nav %} + +{% endblock %} + {% block footer %} {% endblock %} -{% block extra_body_base %} +{% block extra_script %} {% analytics %} {% block extra_body %}{% endblock %} {% endblock %} \ No newline at end of file From 1ba6f03529c8ec366b4a7d541c1bb90f5517e88d Mon Sep 17 00:00:00 2001 From: Luke Hatcher Date: Tue, 22 May 2012 00:38:12 -0400 Subject: [PATCH 028/751] add side nav with sibling pages --- symposion_project/templates/cms/page_detail.html | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/symposion_project/templates/cms/page_detail.html b/symposion_project/templates/cms/page_detail.html index c6ee9b14..fb9e336e 100644 --- a/symposion_project/templates/cms/page_detail.html +++ b/symposion_project/templates/cms/page_detail.html @@ -1,4 +1,17 @@ -{% extends "site_base.html" %} +{% extends "subnav_base.html" %} + +{% block subnav %} + +{% endblock %} {% block body %}

{{ page.title }}

From b2b27eec16965e37c487fdec4440594c25fc7b01 Mon Sep 17 00:00:00 2001 From: Luke Hatcher Date: Tue, 22 May 2012 00:49:16 -0400 Subject: [PATCH 029/751] remove menu --- symposion_project/apps/cms/admin.py | 2 +- .../apps/cms/context_processors.py | 10 ---------- symposion_project/apps/cms/models.py | 20 ------------------- symposion_project/settings.py | 2 -- 4 files changed, 1 insertion(+), 33 deletions(-) delete mode 100644 symposion_project/apps/cms/context_processors.py diff --git a/symposion_project/apps/cms/admin.py b/symposion_project/apps/cms/admin.py index 4616b890..ded416ad 100644 --- a/symposion_project/apps/cms/admin.py +++ b/symposion_project/apps/cms/admin.py @@ -2,7 +2,7 @@ from django.contrib import admin from mptt.admin import MPTTModelAdmin -from cms.models import MenuItem, Page +from cms.models import Page class PageAdmin(MPTTModelAdmin): diff --git a/symposion_project/apps/cms/context_processors.py b/symposion_project/apps/cms/context_processors.py deleted file mode 100644 index 207c1793..00000000 --- a/symposion_project/apps/cms/context_processors.py +++ /dev/null @@ -1,10 +0,0 @@ -from cms.models import MenuItem - - -def menuitems(request): - qs = MenuItem.objects.filter(published=True) - if not request.user.is_authenticated(): - qs = qs.filter(login_required=False) - return { - "menuitems": qs, - } diff --git a/symposion_project/apps/cms/models.py b/symposion_project/apps/cms/models.py index 0f0d635c..5d07b538 100644 --- a/symposion_project/apps/cms/models.py +++ b/symposion_project/apps/cms/models.py @@ -63,23 +63,3 @@ class Page(MPTTModel, ContentBase): order_insertion_by = ["ordering", "title"] reversion.register(Page) - - -class MenuItem(MPTTModel): - - name = models.CharField(max_length=50, unique=True) - slug = models.SlugField() - parent = TreeForeignKey("self", null=True, blank=True, related_name="children") - - url = models.CharField(max_length=200) - - published = models.BooleanField(default=True) - login_required = models.BooleanField(default=False) - ordering = models.PositiveIntegerField(default=1) - - def __unicode__(self): - return self.name - - - class MPTTMeta: - order_insertion_by = ["ordering", "name"] diff --git a/symposion_project/settings.py b/symposion_project/settings.py index d6533a20..d2372f05 100644 --- a/symposion_project/settings.py +++ b/symposion_project/settings.py @@ -133,8 +133,6 @@ TEMPLATE_CONTEXT_PROCESSORS = [ "pinax.core.context_processors.pinax_settings", "pinax.apps.account.context_processors.account", - - "cms.context_processors.menuitems", ] INSTALLED_APPS = [ From e3252d71200cbbf69319ebb77aba69ae73d537e9 Mon Sep 17 00:00:00 2001 From: Luke Hatcher Date: Tue, 22 May 2012 00:49:32 -0400 Subject: [PATCH 030/751] move cms slug down to root url --- symposion_project/urls.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/symposion_project/urls.py b/symposion_project/urls.py index c22b7126..2df8a82b 100644 --- a/symposion_project/urls.py +++ b/symposion_project/urls.py @@ -22,7 +22,7 @@ urlpatterns = patterns("", url(r"^account/", include("pinax.apps.account.urls")), url(r"^openid/", include(PinaxConsumer().urls)), url(r"^markitup/", include("markitup.urls")), - url(r"^pages/(?P%s)/$" % WIKI_SLUG, "cms.views.page", name="cms_page"), + url(r"^(?P%s)/$" % WIKI_SLUG, "cms.views.page", name="cms_page"), ) From b65a953ba5f6d4fb9d29ca9cfa0bf73166a805a7 Mon Sep 17 00:00:00 2001 From: Luke Hatcher Date: Tue, 22 May 2012 01:22:32 -0400 Subject: [PATCH 031/751] get object or 404 on page objects --- symposion_project/apps/cms/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/symposion_project/apps/cms/views.py b/symposion_project/apps/cms/views.py index d09e1ece..478d2cee 100644 --- a/symposion_project/apps/cms/views.py +++ b/symposion_project/apps/cms/views.py @@ -1,4 +1,4 @@ -from django.shortcuts import render_to_response +from django.shortcuts import render_to_response, get_object_or_404 from django.template import RequestContext from cms.models import Page @@ -6,7 +6,7 @@ from cms.models import Page def page(request, slug): - page = Page.objects.get(path=slug) + page = get_object_or_404(Page, path=slug) siblings = page.get_siblings(include_self=True) return render_to_response("cms/page_detail.html", { From 87f14b8015faf951a9d226784b370cc67f912653 Mon Sep 17 00:00:00 2001 From: Luke Hatcher Date: Tue, 22 May 2012 02:11:40 -0400 Subject: [PATCH 032/751] adding in basic nav --- symposion_project/requirements/base.txt | 1 + symposion_project/settings.py | 1 + symposion_project/templates/site_base.html | 4 ++-- symposion_project/templates/sitetree/menu.html | 11 +++++++++++ 4 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 symposion_project/templates/sitetree/menu.html diff --git a/symposion_project/requirements/base.txt b/symposion_project/requirements/base.txt index 23c857bf..ca321225 100644 --- a/symposion_project/requirements/base.txt +++ b/symposion_project/requirements/base.txt @@ -27,6 +27,7 @@ django-taggit==0.9.3 django-reversion==1.5.1 django-markitup==1.0.0 markdown==2.1.1 +django-sitetree==0.6 pinax-theme-bootstrap==2.0.3 django-forms-bootstrap==2.0.3.post1 diff --git a/symposion_project/settings.py b/symposion_project/settings.py index d2372f05..d0a3366e 100644 --- a/symposion_project/settings.py +++ b/symposion_project/settings.py @@ -166,6 +166,7 @@ INSTALLED_APPS = [ "mptt", "reversion", "easy_thumbnails", + "sitetree", # Pinax "pinax.apps.account", diff --git a/symposion_project/templates/site_base.html b/symposion_project/templates/site_base.html index c33d40e2..b8474757 100644 --- a/symposion_project/templates/site_base.html +++ b/symposion_project/templates/site_base.html @@ -2,14 +2,14 @@ {% load metron_tags %} {% load i18n %} +{% load sitetree %} {% block extra_head_base %} {% block extra_head %}{% endblock %} {% endblock %} {% block nav %} - + {% sitetree_menu from "root" include "trunk" %} {% endblock %} {% block footer %} diff --git a/symposion_project/templates/sitetree/menu.html b/symposion_project/templates/sitetree/menu.html new file mode 100644 index 00000000..93a50ea8 --- /dev/null +++ b/symposion_project/templates/sitetree/menu.html @@ -0,0 +1,11 @@ +{% load sitetree %} + \ No newline at end of file From 09aa60777ffef0cfe01eaded2cf739f3dfab7134 Mon Sep 17 00:00:00 2001 From: James Tauber Date: Tue, 29 May 2012 14:53:04 -0400 Subject: [PATCH 033/751] updated project layout for 1.4 (without content changes) --- {symposion_project/docs => docs}/Makefile | 0 {symposion_project/docs => docs}/conf.py | 0 {symposion_project/docs => docs}/conference.rst | 0 {symposion_project/docs => docs}/index.rst | 0 {symposion_project/docs => docs}/sponsorship.rst | 0 {symposion_project/fixtures => fixtures}/initial_data.json | 0 symposion_project/manage.py => manage.py | 0 {symposion_project/requirements => requirements}/base.txt | 0 {symposion_project/requirements => requirements}/docs.txt | 0 {symposion_project/requirements => requirements}/project.txt | 0 {symposion_project => symposion}/__init__.py | 0 {symposion_project/apps => symposion/about}/__init__.py | 0 {symposion_project/apps => symposion}/about/models.py | 0 {symposion_project/apps => symposion}/about/urls.py | 0 {symposion_project/apps => symposion}/about/views.py | 0 .../apps/about => symposion/conference}/__init__.py | 0 {symposion_project/apps => symposion}/conference/admin.py | 0 {symposion_project/apps => symposion}/conference/models.py | 0 {symposion_project => symposion}/settings.py | 0 .../apps/conference => symposion/sponsorship}/__init__.py | 0 {symposion_project/apps => symposion}/sponsorship/admin.py | 0 {symposion_project/apps => symposion}/sponsorship/models.py | 0 .../sponsorship/templatetags}/__init__.py | 0 .../sponsorship/templatetags/sponsorship_tags.py | 0 {symposion_project => symposion}/static/README | 0 {symposion_project => symposion}/templates/_footer.html | 0 {symposion_project => symposion}/templates/about/what_next.html | 0 {symposion_project => symposion}/templates/homepage.html | 0 {symposion_project => symposion}/templates/site_base.html | 0 .../templates/sponsorship/_horizontal_by_level.html | 0 .../templates/sponsorship/_sponsor_link.html | 0 .../templates/sponsorship/_vertical_by_level.html | 0 {symposion_project => symposion}/templates/sponsorship/_wall.html | 0 {symposion_project => symposion}/urls.py | 0 {symposion_project => symposion}/wsgi.py | 0 symposion_project/apps/sponsorship/templatetags/__init__.py | 0 36 files changed, 0 insertions(+), 0 deletions(-) rename {symposion_project/docs => docs}/Makefile (100%) rename {symposion_project/docs => docs}/conf.py (100%) rename {symposion_project/docs => docs}/conference.rst (100%) rename {symposion_project/docs => docs}/index.rst (100%) rename {symposion_project/docs => docs}/sponsorship.rst (100%) rename {symposion_project/fixtures => fixtures}/initial_data.json (100%) rename symposion_project/manage.py => manage.py (100%) rename {symposion_project/requirements => requirements}/base.txt (100%) rename {symposion_project/requirements => requirements}/docs.txt (100%) rename {symposion_project/requirements => requirements}/project.txt (100%) rename {symposion_project => symposion}/__init__.py (100%) rename {symposion_project/apps => symposion/about}/__init__.py (100%) rename {symposion_project/apps => symposion}/about/models.py (100%) rename {symposion_project/apps => symposion}/about/urls.py (100%) rename {symposion_project/apps => symposion}/about/views.py (100%) rename {symposion_project/apps/about => symposion/conference}/__init__.py (100%) rename {symposion_project/apps => symposion}/conference/admin.py (100%) rename {symposion_project/apps => symposion}/conference/models.py (100%) rename {symposion_project => symposion}/settings.py (100%) rename {symposion_project/apps/conference => symposion/sponsorship}/__init__.py (100%) rename {symposion_project/apps => symposion}/sponsorship/admin.py (100%) rename {symposion_project/apps => symposion}/sponsorship/models.py (100%) rename {symposion_project/apps/sponsorship => symposion/sponsorship/templatetags}/__init__.py (100%) rename {symposion_project/apps => symposion}/sponsorship/templatetags/sponsorship_tags.py (100%) rename {symposion_project => symposion}/static/README (100%) rename {symposion_project => symposion}/templates/_footer.html (100%) rename {symposion_project => symposion}/templates/about/what_next.html (100%) rename {symposion_project => symposion}/templates/homepage.html (100%) rename {symposion_project => symposion}/templates/site_base.html (100%) rename {symposion_project => symposion}/templates/sponsorship/_horizontal_by_level.html (100%) rename {symposion_project => symposion}/templates/sponsorship/_sponsor_link.html (100%) rename {symposion_project => symposion}/templates/sponsorship/_vertical_by_level.html (100%) rename {symposion_project => symposion}/templates/sponsorship/_wall.html (100%) rename {symposion_project => symposion}/urls.py (100%) rename {symposion_project => symposion}/wsgi.py (100%) delete mode 100644 symposion_project/apps/sponsorship/templatetags/__init__.py diff --git a/symposion_project/docs/Makefile b/docs/Makefile similarity index 100% rename from symposion_project/docs/Makefile rename to docs/Makefile diff --git a/symposion_project/docs/conf.py b/docs/conf.py similarity index 100% rename from symposion_project/docs/conf.py rename to docs/conf.py diff --git a/symposion_project/docs/conference.rst b/docs/conference.rst similarity index 100% rename from symposion_project/docs/conference.rst rename to docs/conference.rst diff --git a/symposion_project/docs/index.rst b/docs/index.rst similarity index 100% rename from symposion_project/docs/index.rst rename to docs/index.rst diff --git a/symposion_project/docs/sponsorship.rst b/docs/sponsorship.rst similarity index 100% rename from symposion_project/docs/sponsorship.rst rename to docs/sponsorship.rst diff --git a/symposion_project/fixtures/initial_data.json b/fixtures/initial_data.json similarity index 100% rename from symposion_project/fixtures/initial_data.json rename to fixtures/initial_data.json diff --git a/symposion_project/manage.py b/manage.py similarity index 100% rename from symposion_project/manage.py rename to manage.py diff --git a/symposion_project/requirements/base.txt b/requirements/base.txt similarity index 100% rename from symposion_project/requirements/base.txt rename to requirements/base.txt diff --git a/symposion_project/requirements/docs.txt b/requirements/docs.txt similarity index 100% rename from symposion_project/requirements/docs.txt rename to requirements/docs.txt diff --git a/symposion_project/requirements/project.txt b/requirements/project.txt similarity index 100% rename from symposion_project/requirements/project.txt rename to requirements/project.txt diff --git a/symposion_project/__init__.py b/symposion/__init__.py similarity index 100% rename from symposion_project/__init__.py rename to symposion/__init__.py diff --git a/symposion_project/apps/__init__.py b/symposion/about/__init__.py similarity index 100% rename from symposion_project/apps/__init__.py rename to symposion/about/__init__.py diff --git a/symposion_project/apps/about/models.py b/symposion/about/models.py similarity index 100% rename from symposion_project/apps/about/models.py rename to symposion/about/models.py diff --git a/symposion_project/apps/about/urls.py b/symposion/about/urls.py similarity index 100% rename from symposion_project/apps/about/urls.py rename to symposion/about/urls.py diff --git a/symposion_project/apps/about/views.py b/symposion/about/views.py similarity index 100% rename from symposion_project/apps/about/views.py rename to symposion/about/views.py diff --git a/symposion_project/apps/about/__init__.py b/symposion/conference/__init__.py similarity index 100% rename from symposion_project/apps/about/__init__.py rename to symposion/conference/__init__.py diff --git a/symposion_project/apps/conference/admin.py b/symposion/conference/admin.py similarity index 100% rename from symposion_project/apps/conference/admin.py rename to symposion/conference/admin.py diff --git a/symposion_project/apps/conference/models.py b/symposion/conference/models.py similarity index 100% rename from symposion_project/apps/conference/models.py rename to symposion/conference/models.py diff --git a/symposion_project/settings.py b/symposion/settings.py similarity index 100% rename from symposion_project/settings.py rename to symposion/settings.py diff --git a/symposion_project/apps/conference/__init__.py b/symposion/sponsorship/__init__.py similarity index 100% rename from symposion_project/apps/conference/__init__.py rename to symposion/sponsorship/__init__.py diff --git a/symposion_project/apps/sponsorship/admin.py b/symposion/sponsorship/admin.py similarity index 100% rename from symposion_project/apps/sponsorship/admin.py rename to symposion/sponsorship/admin.py diff --git a/symposion_project/apps/sponsorship/models.py b/symposion/sponsorship/models.py similarity index 100% rename from symposion_project/apps/sponsorship/models.py rename to symposion/sponsorship/models.py diff --git a/symposion_project/apps/sponsorship/__init__.py b/symposion/sponsorship/templatetags/__init__.py similarity index 100% rename from symposion_project/apps/sponsorship/__init__.py rename to symposion/sponsorship/templatetags/__init__.py diff --git a/symposion_project/apps/sponsorship/templatetags/sponsorship_tags.py b/symposion/sponsorship/templatetags/sponsorship_tags.py similarity index 100% rename from symposion_project/apps/sponsorship/templatetags/sponsorship_tags.py rename to symposion/sponsorship/templatetags/sponsorship_tags.py diff --git a/symposion_project/static/README b/symposion/static/README similarity index 100% rename from symposion_project/static/README rename to symposion/static/README diff --git a/symposion_project/templates/_footer.html b/symposion/templates/_footer.html similarity index 100% rename from symposion_project/templates/_footer.html rename to symposion/templates/_footer.html diff --git a/symposion_project/templates/about/what_next.html b/symposion/templates/about/what_next.html similarity index 100% rename from symposion_project/templates/about/what_next.html rename to symposion/templates/about/what_next.html diff --git a/symposion_project/templates/homepage.html b/symposion/templates/homepage.html similarity index 100% rename from symposion_project/templates/homepage.html rename to symposion/templates/homepage.html diff --git a/symposion_project/templates/site_base.html b/symposion/templates/site_base.html similarity index 100% rename from symposion_project/templates/site_base.html rename to symposion/templates/site_base.html diff --git a/symposion_project/templates/sponsorship/_horizontal_by_level.html b/symposion/templates/sponsorship/_horizontal_by_level.html similarity index 100% rename from symposion_project/templates/sponsorship/_horizontal_by_level.html rename to symposion/templates/sponsorship/_horizontal_by_level.html diff --git a/symposion_project/templates/sponsorship/_sponsor_link.html b/symposion/templates/sponsorship/_sponsor_link.html similarity index 100% rename from symposion_project/templates/sponsorship/_sponsor_link.html rename to symposion/templates/sponsorship/_sponsor_link.html diff --git a/symposion_project/templates/sponsorship/_vertical_by_level.html b/symposion/templates/sponsorship/_vertical_by_level.html similarity index 100% rename from symposion_project/templates/sponsorship/_vertical_by_level.html rename to symposion/templates/sponsorship/_vertical_by_level.html diff --git a/symposion_project/templates/sponsorship/_wall.html b/symposion/templates/sponsorship/_wall.html similarity index 100% rename from symposion_project/templates/sponsorship/_wall.html rename to symposion/templates/sponsorship/_wall.html diff --git a/symposion_project/urls.py b/symposion/urls.py similarity index 100% rename from symposion_project/urls.py rename to symposion/urls.py diff --git a/symposion_project/wsgi.py b/symposion/wsgi.py similarity index 100% rename from symposion_project/wsgi.py rename to symposion/wsgi.py diff --git a/symposion_project/apps/sponsorship/templatetags/__init__.py b/symposion_project/apps/sponsorship/templatetags/__init__.py deleted file mode 100644 index e69de29b..00000000 From e96fe0bd5e77d92ab25c1df5f01dbdfcc98a2eae Mon Sep 17 00:00:00 2001 From: James Tauber Date: Tue, 29 May 2012 15:00:14 -0400 Subject: [PATCH 034/751] updated requirements to 1.4 / DUA and latest versions --- requirements/base.txt | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/requirements/base.txt b/requirements/base.txt index 4074a6c2..343f6f0b 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -7,20 +7,19 @@ --extra-index-url=http://dist.pinaxproject.com/alpha/ --extra-index-url=http://dist.pinaxproject.com/fresh-start/ -Django==1.3.1 -Pinax - +Django==1.4 +pinax-theme-bootstrap==2.0.3 +metron==0.1 # 0.2.dev3 +pinax-utils==1.0b1.dev3 django-debug-toolbar==0.9.1 -django-staticfiles==1.1.2 -django_compressor==1.0.1 - django-mailer==0.2a1 -django-email-confirmation==0.2 django-timezones==0.2 pytz==2011n django-openid==0.3a1 python-openid==2.2.5 -metron==0.1 +django_compressor==1.2a1 -pinax-theme-bootstrap==2.0.3 -django-forms-bootstrap==2.0.3.post1 +-e git+git://github.com/pinax/pinax-theme-bootstrap-account.git@70c0be0279d61bd2d0f949698f94b8f938918277#egg=pinax-theme-bootstrap-account +-e git+git://github.com/pinax/django-user-accounts.git@3676d2c7ea0e9a5c3f8510ac8e6f8d58175e6b5f#egg=django-user-accounts + +# django-forms-bootstrap==2.0.3.post1 From e3c79c056cbc62ef01e3c587ca4e83c3759603c5 Mon Sep 17 00:00:00 2001 From: James Tauber Date: Tue, 29 May 2012 15:00:42 -0400 Subject: [PATCH 035/751] updated manage.py --- manage.py | 23 ++++++----------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/manage.py b/manage.py index 378e3b0b..2587018b 100755 --- a/manage.py +++ b/manage.py @@ -1,20 +1,9 @@ #!/usr/bin/env python -import sys - -try: - import pinax -except ImportError: - sys.stderr.write("Error: Can't import Pinax. Make sure you are in a " - "virtual environment that has\nPinax installed.\n") - sys.exit(1) -else: - import pinax.env - -from django.core.management import execute_from_command_line - - -pinax.env.setup_environ(__file__) - +import os, sys if __name__ == "__main__": - execute_from_command_line() + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "symposion.settings") + + from django.core.management import execute_from_command_line + + execute_from_command_line(sys.argv) From fc69aa85e8455c1a3252bdcd47312e1d7b42f638 Mon Sep 17 00:00:00 2001 From: James Tauber Date: Tue, 29 May 2012 15:06:24 -0400 Subject: [PATCH 036/751] updated settings --- symposion/settings.py | 39 ++++++++++++++------------------------- 1 file changed, 14 insertions(+), 25 deletions(-) diff --git a/symposion/settings.py b/symposion/settings.py index 0544bf0f..cb53d6d9 100644 --- a/symposion/settings.py +++ b/symposion/settings.py @@ -79,7 +79,6 @@ STATICFILES_DIRS = [ STATICFILES_FINDERS = [ "staticfiles.finders.FileSystemFinder", "staticfiles.finders.AppDirectoriesFinder", - "staticfiles.finders.LegacyAppDirectoriesFinder", "compressor.finders.CompressorFinder", ] @@ -96,8 +95,8 @@ SECRET_KEY = "8*br)9@fs!4nzg-imfrsst&oa2udy6z-fqtdk0*e5c1=wn)(t3" # List of callables that know how to import templates from various sources. TEMPLATE_LOADERS = [ - "django.template.loaders.filesystem.load_template_source", - "django.template.loaders.app_directories.load_template_source", + "django.template.loaders.filesystem.Loader", + "django.template.loaders.app_directories.Loader", ] MIDDLEWARE_CLASSES = [ @@ -107,12 +106,10 @@ MIDDLEWARE_CLASSES = [ "django.contrib.auth.middleware.AuthenticationMiddleware", "django_openid.consumer.SessionConsumer", "django.contrib.messages.middleware.MessageMiddleware", - "pinax.apps.account.middleware.LocaleMiddleware", - "pinax.middleware.security.HideSensistiveFieldsMiddleware", "debug_toolbar.middleware.DebugToolbarMiddleware", ] -ROOT_URLCONF = "symposion_project.urls" +ROOT_URLCONF = "symposion.urls" TEMPLATE_DIRS = [ os.path.join(PROJECT_ROOT, "templates"), @@ -125,12 +122,8 @@ TEMPLATE_CONTEXT_PROCESSORS = [ "django.core.context_processors.media", "django.core.context_processors.request", "django.contrib.messages.context_processors.messages", - - "staticfiles.context_processors.static", - - "pinax.core.context_processors.pinax_settings", - - "pinax.apps.account.context_processors.account", + "pinax_utils.context_processors.settings", + "account.context_processors.account", ] INSTALLED_APPS = [ @@ -143,31 +136,27 @@ INSTALLED_APPS = [ "django.contrib.messages", "django.contrib.humanize", - "pinax.templatetags", - # theme + "pinax_theme_bootstrap_account", "pinax_theme_bootstrap", - "django_forms_bootstrap", + # "django_forms_bootstrap", # external - "staticfiles", "compressor", "debug_toolbar", "mailer", "django_openid", "timezones", - "emailconfirmation", "metron", "easy_thumbnails", # Pinax - "pinax.apps.account", - "pinax.apps.signup_codes", + "account", # project - "about", - "sponsorship", - "conference", + "symposion.about", + "symposion.sponsorship", + "symposion.conference", ] FIXTURE_DIRS = [ @@ -185,9 +174,9 @@ ACCOUNT_EMAIL_VERIFICATION = False ACCOUNT_EMAIL_AUTHENTICATION = False ACCOUNT_UNIQUE_EMAIL = EMAIL_CONFIRMATION_UNIQUE_EMAIL = False -AUTHENTICATION_BACKENDS = [ - "pinax.apps.account.auth_backends.AuthenticationBackend", -] +# AUTHENTICATION_BACKENDS = [ +# "pinax.apps.account.auth_backends.AuthenticationBackend", +# ] LOGIN_URL = "/account/login/" # @@@ any way this can be a url name? LOGIN_REDIRECT_URLNAME = "what_next" From c9e600e42cba114fda828b65b62ad3eb237d6e0e Mon Sep 17 00:00:00 2001 From: James Tauber Date: Tue, 29 May 2012 15:08:05 -0400 Subject: [PATCH 037/751] updated imports to reflect new layout --- symposion/sponsorship/admin.py | 2 +- symposion/sponsorship/models.py | 2 +- symposion/sponsorship/templatetags/sponsorship_tags.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/symposion/sponsorship/admin.py b/symposion/sponsorship/admin.py index 6814c4da..a26b0a96 100644 --- a/symposion/sponsorship/admin.py +++ b/symposion/sponsorship/admin.py @@ -1,6 +1,6 @@ from django.contrib import admin -from sponsorship.models import SponsorLevel, Sponsor +from symposion.sponsorship.models import SponsorLevel, Sponsor admin.site.register(SponsorLevel) diff --git a/symposion/sponsorship/models.py b/symposion/sponsorship/models.py index 07d0cb30..a902c0be 100644 --- a/symposion/sponsorship/models.py +++ b/symposion/sponsorship/models.py @@ -3,7 +3,7 @@ import datetime from django.db import models from django.utils.translation import ugettext_lazy as _ -from conference.models import Conference +from symposion.conference.models import Conference class SponsorLevel(models.Model): diff --git a/symposion/sponsorship/templatetags/sponsorship_tags.py b/symposion/sponsorship/templatetags/sponsorship_tags.py index 00fb8139..cd1f33ba 100644 --- a/symposion/sponsorship/templatetags/sponsorship_tags.py +++ b/symposion/sponsorship/templatetags/sponsorship_tags.py @@ -1,7 +1,7 @@ from django import template -from conference.models import current_conference -from sponsorship.models import Sponsor, SponsorLevel +from symposion.conference.models import current_conference +from symposion.sponsorship.models import Sponsor, SponsorLevel register = template.Library() From 4b87c512510d7a00a198642ffd83cd4fd2dd3cd5 Mon Sep 17 00:00:00 2001 From: James Tauber Date: Tue, 29 May 2012 15:08:05 -0400 Subject: [PATCH 038/751] updated imports to reflect new layout --- symposion/conference/admin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/symposion/conference/admin.py b/symposion/conference/admin.py index e385acdf..3d2bccaa 100644 --- a/symposion/conference/admin.py +++ b/symposion/conference/admin.py @@ -1,6 +1,6 @@ from django.contrib import admin -from conference.models import Conference, Section +from symposion.conference.models import Conference, Section admin.site.register(Conference, list_display=("title", "start_date", "end_date")) From 303877add6149366fa322705f96d4443c732035d Mon Sep 17 00:00:00 2001 From: James Tauber Date: Tue, 29 May 2012 15:12:22 -0400 Subject: [PATCH 039/751] updated templates --- symposion/templates/_footer.html | 6 +++++- symposion/templates/site_base.html | 2 -- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/symposion/templates/_footer.html b/symposion/templates/_footer.html index 79a20fd5..4f334c9b 100644 --- a/symposion/templates/_footer.html +++ b/symposion/templates/_footer.html @@ -1,4 +1,8 @@ {% load i18n %} \ No newline at end of file diff --git a/symposion/templates/site_base.html b/symposion/templates/site_base.html index 6c811b81..65953f86 100644 --- a/symposion/templates/site_base.html +++ b/symposion/templates/site_base.html @@ -8,9 +8,7 @@ {% endblock %} {% block footer %} - {% endblock %} {% block extra_body_base %} From 02319e51c86580aecac797df75635de5e701a463 Mon Sep 17 00:00:00 2001 From: James Tauber Date: Tue, 29 May 2012 15:16:41 -0400 Subject: [PATCH 040/751] updated urls --- symposion/urls.py | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/symposion/urls.py b/symposion/urls.py index e8feb4b1..645d558f 100644 --- a/symposion/urls.py +++ b/symposion/urls.py @@ -1,29 +1,24 @@ from django.conf import settings from django.conf.urls.defaults import * +from django.conf.urls.static import static + from django.views.generic.simple import direct_to_template from django.contrib import admin admin.autodiscover() -from pinax.apps.account.openid_consumer import PinaxConsumer - - -handler500 = "pinax.views.server_error" +# from pinax.apps.account.openid_consumer import PinaxConsumer urlpatterns = patterns("", url(r"^$", direct_to_template, { "template": "homepage.html", }, name="home"), - url(r"^admin/invite_user/$", "pinax.apps.signup_codes.views.admin_invite_user", name="admin_invite_user"), url(r"^admin/", include(admin.site.urls)), - url(r"^about/", include("about.urls")), - url(r"^account/", include("pinax.apps.account.urls")), - url(r"^openid/", include(PinaxConsumer().urls)), + url(r"^about/", include("symposion.about.urls")), + url(r"^account/", include("account.urls")), + # url(r"^openid/", include(PinaxConsumer().urls)), ) -if settings.SERVE_MEDIA: - urlpatterns += patterns("", - url(r"", include("staticfiles.urls")), - ) +urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) \ No newline at end of file From 6160ede722710859ca7e6c3e9b567464315834e6 Mon Sep 17 00:00:00 2001 From: James Tauber Date: Tue, 29 May 2012 15:17:08 -0400 Subject: [PATCH 041/751] removed homepage use of ifsetting_tag --- symposion/templates/homepage.html | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/symposion/templates/homepage.html b/symposion/templates/homepage.html index 962af389..ba995260 100644 --- a/symposion/templates/homepage.html +++ b/symposion/templates/homepage.html @@ -1,7 +1,6 @@ {% extends "banner_base.html" %} {% load i18n %} -{% load ifsetting_tag %} {% block head_title %}{% trans "Welcome" %}{% endblock %} @@ -34,11 +33,7 @@

{% blocktrans %}Wondering What Next?{% endblocktrans %}

{% else %} {% url acct_login as login_url %} - {% ifsetting ACCOUNT_OPEN_SIGNUP %} - {% url acct_signup as signup_url %} -

{% blocktrans %}You can Log In or Sign Up to try out the site.{% endblocktrans %}

- {% else %} -

{% blocktrans %}You can Log In to try out the site.{% endblocktrans %}

- {% endifsetting %} + {% url acct_signup as signup_url %} +

{% blocktrans %}You can Log In or Sign Up to try out the site.{% endblocktrans %}

{% endif %} {% endblock %} From 9477c92abfc4a198ed93a3d983e0f99a8b629346 Mon Sep 17 00:00:00 2001 From: James Tauber Date: Tue, 29 May 2012 15:30:35 -0400 Subject: [PATCH 042/751] fixed staticfiles settings --- symposion/settings.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/symposion/settings.py b/symposion/settings.py index cb53d6d9..f4344a05 100644 --- a/symposion/settings.py +++ b/symposion/settings.py @@ -77,8 +77,8 @@ STATICFILES_DIRS = [ ] STATICFILES_FINDERS = [ - "staticfiles.finders.FileSystemFinder", - "staticfiles.finders.AppDirectoriesFinder", + "django.contrib.staticfiles.finders.FileSystemFinder", + "django.contrib.staticfiles.finders.AppDirectoriesFinder", "compressor.finders.CompressorFinder", ] @@ -120,6 +120,8 @@ TEMPLATE_CONTEXT_PROCESSORS = [ "django.core.context_processors.debug", "django.core.context_processors.i18n", "django.core.context_processors.media", + "django.core.context_processors.static", + "django.core.context_processors.tz", "django.core.context_processors.request", "django.contrib.messages.context_processors.messages", "pinax_utils.context_processors.settings", @@ -134,6 +136,7 @@ INSTALLED_APPS = [ "django.contrib.sessions", "django.contrib.sites", "django.contrib.messages", + "django.contrib.staticfiles", "django.contrib.humanize", # theme From 44fb5325f197c2ddb74e13ecb53be64593c773a7 Mon Sep 17 00:00:00 2001 From: James Tauber Date: Tue, 29 May 2012 15:34:14 -0400 Subject: [PATCH 043/751] fixed account links on homepage --- symposion/templates/homepage.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/symposion/templates/homepage.html b/symposion/templates/homepage.html index ba995260..2365f67a 100644 --- a/symposion/templates/homepage.html +++ b/symposion/templates/homepage.html @@ -32,8 +32,8 @@ {% url what_next as what_next_url %}

{% blocktrans %}Wondering What Next?{% endblocktrans %}

{% else %} - {% url acct_login as login_url %} - {% url acct_signup as signup_url %} + {% url account_login as login_url %} + {% url account_signup as signup_url %}

{% blocktrans %}You can Log In or Sign Up to try out the site.{% endblocktrans %}

{% endif %} {% endblock %} From 1191608a8adbdea1c56b91d949d5e08f0e69b3db Mon Sep 17 00:00:00 2001 From: James Tauber Date: Tue, 29 May 2012 21:50:46 -0400 Subject: [PATCH 044/751] use django-forms-bootstrap --- requirements/base.txt | 2 +- symposion/settings.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements/base.txt b/requirements/base.txt index 343f6f0b..f2e779bb 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -22,4 +22,4 @@ django_compressor==1.2a1 -e git+git://github.com/pinax/pinax-theme-bootstrap-account.git@70c0be0279d61bd2d0f949698f94b8f938918277#egg=pinax-theme-bootstrap-account -e git+git://github.com/pinax/django-user-accounts.git@3676d2c7ea0e9a5c3f8510ac8e6f8d58175e6b5f#egg=django-user-accounts -# django-forms-bootstrap==2.0.3.post1 +django-forms-bootstrap==2.0.3.post1 diff --git a/symposion/settings.py b/symposion/settings.py index f4344a05..b9433d77 100644 --- a/symposion/settings.py +++ b/symposion/settings.py @@ -142,7 +142,7 @@ INSTALLED_APPS = [ # theme "pinax_theme_bootstrap_account", "pinax_theme_bootstrap", - # "django_forms_bootstrap", + "django_forms_bootstrap", # external "compressor", From 8dac027fff92dcf313613eb037863c83f966de91 Mon Sep 17 00:00:00 2001 From: James Tauber Date: Mon, 4 Jun 2012 18:17:38 -0300 Subject: [PATCH 045/751] Update master --- README | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README b/README index e69de29b..e8832bb1 100644 --- a/README +++ b/README @@ -0,0 +1,4 @@ +SYMPOSION +A Django project for conference websites. + +A Pinax project sponsored by Eldarion and the Python Software Foundation. From c1b7550ed8f051767bebd9d24a954af104de4e08 Mon Sep 17 00:00:00 2001 From: James Tauber Date: Tue, 5 Jun 2012 15:13:29 -0400 Subject: [PATCH 046/751] added BSD license --- LICENSE | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..3ef50d92 --- /dev/null +++ b/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2010-2012, Eldarion, Inc. and contributors +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of Eldarion, Inc. nor the names of its contributors may + be used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. From 1b04b7a9f73c4985c74193034593dec15ea9a8b3 Mon Sep 17 00:00:00 2001 From: Luke Hatcher Date: Tue, 10 Jul 2012 18:14:49 -0400 Subject: [PATCH 047/751] update debug toolbar --- requirements/base.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/base.txt b/requirements/base.txt index 730e35c2..99d7a078 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -12,7 +12,7 @@ pinax-theme-bootstrap==2.0.3 django-forms-bootstrap==2.0.3.post1 metron==0.1 # 0.2.dev3 pinax-utils==1.0b1.dev3 -django-debug-toolbar==0.9.1 +django-debug-toolbar==0.9.4 django-mailer==0.2a1 django-timezones==0.2 pytz==2011n From 2665fd575869fa363a95958474ffd9453c500b7c Mon Sep 17 00:00:00 2001 From: Luke Hatcher Date: Tue, 10 Jul 2012 18:18:48 -0400 Subject: [PATCH 048/751] update cms/boxes to pycon parity --- requirements/base.txt | 2 +- symposion/boxes/__init__.py | 0 symposion/boxes/admin.py | 6 ++ symposion/boxes/authorization.py | 20 ++++++ symposion/boxes/forms.py | 10 +++ symposion/boxes/models.py | 22 +++++++ symposion/boxes/templatetags/__init__.py | 0 symposion/boxes/templatetags/boxes_tags.py | 41 ++++++++++++ symposion/boxes/urls.py | 6 ++ symposion/boxes/utils.py | 19 ++++++ symposion/boxes/views.py | 45 ++++++++++++++ symposion/cms/admin.py | 10 +-- symposion/cms/forms.py | 16 +++++ symposion/cms/managers.py | 9 +++ symposion/cms/models.py | 62 ++++++++----------- symposion/cms/urls.py | 8 +++ symposion/cms/views.py | 60 +++++++++++++++--- symposion/settings.py | 8 ++- symposion/templates/boxes/box.html | 31 ++++++++++ symposion/templates/cms/page_detail.html | 29 +++++---- symposion/templates/cms/page_edit.html | 22 +++++++ symposion/templates/site_base.html | 31 ++++++++-- symposion/templates/sitetree/breadcrumbs.html | 17 +++++ symposion/templates/sitetree/menu.html | 23 ++++--- symposion/templates/sitetree/submenu.html | 8 +++ symposion/templates/sitetree/tree.html | 15 +++++ symposion/urls.py | 6 +- 27 files changed, 443 insertions(+), 83 deletions(-) create mode 100644 symposion/boxes/__init__.py create mode 100644 symposion/boxes/admin.py create mode 100644 symposion/boxes/authorization.py create mode 100644 symposion/boxes/forms.py create mode 100644 symposion/boxes/models.py create mode 100644 symposion/boxes/templatetags/__init__.py create mode 100644 symposion/boxes/templatetags/boxes_tags.py create mode 100644 symposion/boxes/urls.py create mode 100644 symposion/boxes/utils.py create mode 100644 symposion/boxes/views.py create mode 100644 symposion/cms/forms.py create mode 100644 symposion/cms/managers.py create mode 100644 symposion/cms/urls.py create mode 100644 symposion/templates/boxes/box.html create mode 100644 symposion/templates/cms/page_edit.html create mode 100644 symposion/templates/sitetree/breadcrumbs.html create mode 100644 symposion/templates/sitetree/submenu.html create mode 100644 symposion/templates/sitetree/tree.html diff --git a/requirements/base.txt b/requirements/base.txt index 99d7a078..d315f8f7 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -25,7 +25,7 @@ django_compressor==1.2a1 django-mptt==0.5.2 django-taggit==0.9.3 -django-reversion==1.5.1 +django-reversion==1.6.1 django-markitup==1.0.0 markdown==2.1.1 django-sitetree==0.6 diff --git a/symposion/boxes/__init__.py b/symposion/boxes/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/symposion/boxes/admin.py b/symposion/boxes/admin.py new file mode 100644 index 00000000..a6ac989a --- /dev/null +++ b/symposion/boxes/admin.py @@ -0,0 +1,6 @@ +from django.contrib import admin + +from symposion.boxes.models import Box + + +admin.site.register(Box) \ No newline at end of file diff --git a/symposion/boxes/authorization.py b/symposion/boxes/authorization.py new file mode 100644 index 00000000..011e215d --- /dev/null +++ b/symposion/boxes/authorization.py @@ -0,0 +1,20 @@ +from django.conf import settings + +from symposion.boxes.utils import load_path_attr + + +def default_can_edit(request, *args, **kwargs): + """ + This is meant to be overridden in your project per domain specific + requirements. + """ + return request.user.is_staff or request.user.is_superuser + + +def load_can_edit(): + import_path = getattr(settings, "BOXES_CAN_EDIT_CALLABLE", None) + + if import_path is None: + return default_can_edit + + return load_path_attr(import_path) diff --git a/symposion/boxes/forms.py b/symposion/boxes/forms.py new file mode 100644 index 00000000..b5ad3b32 --- /dev/null +++ b/symposion/boxes/forms.py @@ -0,0 +1,10 @@ +from django import forms + +from symposion.boxes.models import Box + + +class BoxForm(forms.ModelForm): + + class Meta: + model = Box + fields = ["content"] diff --git a/symposion/boxes/models.py b/symposion/boxes/models.py new file mode 100644 index 00000000..85500f63 --- /dev/null +++ b/symposion/boxes/models.py @@ -0,0 +1,22 @@ +import datetime + +from django.db import models + +from django.contrib.auth.models import User + +from markitup.fields import MarkupField + + +class Box(models.Model): + + label = models.CharField(max_length=100, db_index=True) + content = MarkupField(blank=True) + + created_by = models.ForeignKey(User, related_name="boxes") + last_updated_by = models.ForeignKey(User, related_name="updated_boxes") + + def __unicode__(self): + return self.label + + class Meta: + verbose_name_plural = "boxes" diff --git a/symposion/boxes/templatetags/__init__.py b/symposion/boxes/templatetags/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/symposion/boxes/templatetags/boxes_tags.py b/symposion/boxes/templatetags/boxes_tags.py new file mode 100644 index 00000000..e300fd3b --- /dev/null +++ b/symposion/boxes/templatetags/boxes_tags.py @@ -0,0 +1,41 @@ +from django import template +from django.core.exceptions import ImproperlyConfigured +from django.core.urlresolvers import reverse +from django.utils.safestring import mark_safe +from django.utils.encoding import smart_str +from django.utils.translation import ugettext_lazy as _ +from django.template.defaulttags import kwarg_re + +from symposion.boxes.models import Box +from symposion.boxes.forms import BoxForm +from symposion.boxes.authorization import load_can_edit + + +register = template.Library() + + +@register.inclusion_tag("boxes/box.html", takes_context=True) +def box(context, label, show_edit=True, *args, **kwargs): + + request = context["request"] + can_edit = load_can_edit()(request, *args, **kwargs) + + try: + box = Box.objects.get(label=label) + except Box.DoesNotExist: + box = None + + if can_edit and show_edit: + form = BoxForm(instance=box, prefix=label) + form_action = reverse("box_edit", args=[label]) + else: + form = None + form_action = None + + return { + "request": request, + "label": label, + "box": box, + "form": form, + "form_action": form_action, + } diff --git a/symposion/boxes/urls.py b/symposion/boxes/urls.py new file mode 100644 index 00000000..dc57fe6b --- /dev/null +++ b/symposion/boxes/urls.py @@ -0,0 +1,6 @@ +from django.conf.urls.defaults import url, patterns + + +urlpatterns = patterns("symposion.boxes.views", + url(r"^([-\w]+)/edit/$", "box_edit", name="box_edit"), +) \ No newline at end of file diff --git a/symposion/boxes/utils.py b/symposion/boxes/utils.py new file mode 100644 index 00000000..24b95c69 --- /dev/null +++ b/symposion/boxes/utils.py @@ -0,0 +1,19 @@ +from django.core.exceptions import ImproperlyConfigured +try: + from django.utils.importlib import import_module +except ImportError: + from importlib import import_module + + +def load_path_attr(path): + i = path.rfind(".") + module, attr = path[:i], path[i+1:] + try: + mod = import_module(module) + except ImportError, e: + raise ImproperlyConfigured("Error importing %s: '%s'" % (module, e)) + try: + attr = getattr(mod, attr) + except AttributeError: + raise ImproperlyConfigured("Module '%s' does not define a '%s'" % (module, attr)) + return attr diff --git a/symposion/boxes/views.py b/symposion/boxes/views.py new file mode 100644 index 00000000..b314b07e --- /dev/null +++ b/symposion/boxes/views.py @@ -0,0 +1,45 @@ +from django.http import HttpResponseForbidden +from django.shortcuts import redirect +from django.views.decorators.http import require_POST + +from symposion.boxes.authorization import load_can_edit +from symposion.boxes.forms import BoxForm +from symposion.boxes.models import Box + + +# @@@ problem with this is that the box_edit.html and box_create.html won't have domain objects in context +def get_auth_vars(request): + auth_vars = {} + if request.method == "POST": + keys = [k for k in request.POST.keys() if k.startswith("boxes_auth_")] + for key in keys: + auth_vars[key.replace("boxes_auth_", "")] = request.POST.get(key) + auth_vars["user"] = request.user + return auth_vars + + +@require_POST +def box_edit(request, label): + + if not load_can_edit()(request, **get_auth_vars(request)): + return HttpResponseForbidden() + + next = request.GET.get("next") + + try: + box = Box.objects.get(label=label) + except Box.DoesNotExist: + box = None + + form = BoxForm(request.POST, instance=box, prefix=label) + + if form.is_valid(): + if box is None: + box = form.save(commit=False) + box.label = label + box.created_by = request.user + box.last_updated_by = request.user + box.save() + else: + form.save() + return redirect(next) diff --git a/symposion/cms/admin.py b/symposion/cms/admin.py index ded416ad..729a50a2 100644 --- a/symposion/cms/admin.py +++ b/symposion/cms/admin.py @@ -1,12 +1,6 @@ from django.contrib import admin -from mptt.admin import MPTTModelAdmin - -from cms.models import Page +from .models import Page -class PageAdmin(MPTTModelAdmin): - prepopulated_fields = {"slug": ("title",)} - list_display = ("title", "published", "status") - -admin.site.register(Page, PageAdmin) +admin.site.register(Page) diff --git a/symposion/cms/forms.py b/symposion/cms/forms.py new file mode 100644 index 00000000..bbcd31ab --- /dev/null +++ b/symposion/cms/forms.py @@ -0,0 +1,16 @@ +from django import forms + +from markitup.widgets import MarkItUpWidget + +from .models import Page + + +class PageForm(forms.ModelForm): + + class Meta: + model = Page + fields = ["title", "body", "path"] + widgets = { + "body": MarkItUpWidget(), + "path": forms.HiddenInput(), + } diff --git a/symposion/cms/managers.py b/symposion/cms/managers.py new file mode 100644 index 00000000..c8381ebd --- /dev/null +++ b/symposion/cms/managers.py @@ -0,0 +1,9 @@ +from datetime import datetime + +from django.db import models + +class PublishedPageManager(models.Manager): + + def get_query_set(self): + qs = super(PublishedPageManager, self).get_query_set() + return qs.filter(publish_date__lte=datetime.now()) diff --git a/symposion/cms/models.py b/symposion/cms/models.py index 5d07b538..d1e9668c 100644 --- a/symposion/cms/models.py +++ b/symposion/cms/models.py @@ -1,5 +1,8 @@ +import re from datetime import datetime +from django.conf import settings +from django.core.exceptions import ValidationError from django.db import models from django.utils.translation import ugettext_lazy as _ @@ -7,59 +10,44 @@ from markitup.fields import MarkupField from taggit.managers import TaggableManager -from mptt.models import MPTTModel, TreeForeignKey -from mptt.utils import drilldown_tree_for_node - import reversion +from .managers import PublishedPageManager -class ContentBase(models.Model): + +class Page(models.Model): STATUS_CHOICES = ( (1, _("Draft")), (2, _("Public")), ) - + title = models.CharField(max_length=100) - slug = models.CharField(max_length=100, blank=True, null=True) + path = models.CharField(max_length=100, unique=True) body = MarkupField() - - tags = TaggableManager(blank=True) - status = models.IntegerField(choices=STATUS_CHOICES, default=2) - published = models.DateTimeField(default=datetime.now) + publish_date = models.DateTimeField(default=datetime.now) created = models.DateTimeField(editable=False, default=datetime.now) updated = models.DateTimeField(editable=False, default=datetime.now) - - class Meta: - abstract = True - - -class Page(MPTTModel, ContentBase): - - parent = TreeForeignKey("self", null=True, blank=True, related_name="children") - ordering = models.PositiveIntegerField(default=1) - path = models.TextField(blank=True, editable=False) - + tags = TaggableManager(blank=True) + + published = PublishedPageManager() + def __unicode__(self): return self.title - - def save(self, calculate_path=True, *args, **kwargs): - super(Page, self).save(*args, **kwargs) - if calculate_path: - self.calculate_path() - def calculate_path(self): - self.path = "" - for page in drilldown_tree_for_node(self): - if page == self: - self.path += page.slug - break - else: - self.path += "%s/" % page.slug - self.save(calculate_path=False) + @models.permalink + def get_absolute_url(self): + return ("cms_page", [self.path]) + + def save(self, *args, **kwargs): + self.updated = datetime.now() + super(Page, self).save(*args, **kwargs) + + def clean_fields(self, exclude=None): + super(Page, self).clean_fields(exclude) + if not re.match(settings.SYMPOSION_PAGE_REGEX, self.path): + raise ValidationError({"path": [_("Path can only contain letters, numbers and hyphens and end with /"),]}) - class MPTTMeta: - order_insertion_by = ["ordering", "title"] reversion.register(Page) diff --git a/symposion/cms/urls.py b/symposion/cms/urls.py new file mode 100644 index 00000000..ecb2ad5e --- /dev/null +++ b/symposion/cms/urls.py @@ -0,0 +1,8 @@ +from django.conf.urls.defaults import url, patterns + +PAGE_RE = r"(([\w-]{1,})(/[\w-]{1,})*)/" + +urlpatterns = patterns("symposion.cms.views", + url(r"^(?P%s)_edit/$" % PAGE_RE, "page_edit", name="cms_page_edit"), + url(r"^(?P%s)$" % PAGE_RE, "page", name="cms_page"), +) diff --git a/symposion/cms/views.py b/symposion/cms/views.py index 478d2cee..5592d429 100644 --- a/symposion/cms/views.py +++ b/symposion/cms/views.py @@ -1,15 +1,59 @@ -from django.shortcuts import render_to_response, get_object_or_404 +from django.http import Http404 +from django.shortcuts import render, redirect from django.template import RequestContext -from cms.models import Page +from .models import Page +from .forms import PageForm -def page(request, slug): +def can_edit(user): + if user.is_staff or user.is_superuser: + return True + if user.has_perm("cms.change_page"): + return True + return False + + +def page(request, path): - page = get_object_or_404(Page, path=slug) - siblings = page.get_siblings(include_self=True) + editable = can_edit(request.user) + try: + page = Page.published.get(path=path) + except Page.DoesNotExist: + if editable: + return redirect("cms_page_edit", path=path) + else: + raise Http404 - return render_to_response("cms/page_detail.html", { + return render(request, "cms/page_detail.html", { "page": page, - "siblings": siblings, - }, context_instance=RequestContext(request)) + "editable": editable, + }) + + +def page_edit(request, path): + + if not can_edit(request.user): + raise Http404 + + try: + page = Page.published.get(path=path) + except Page.DoesNotExist: + page = None + + if request.method == "POST": + form = PageForm(request.POST, instance=page) + if form.is_valid(): + page = form.save(commit=False) + page.path = path + page.save() + return redirect(page) + else: + print form.errors + else: + form = PageForm(instance=page, initial={"path": path}) + + return render(request, "cms/page_edit.html", { + "path": path, + "form": form + }) diff --git a/symposion/settings.py b/symposion/settings.py index 9b3a5b80..f0815a34 100644 --- a/symposion/settings.py +++ b/symposion/settings.py @@ -155,7 +155,6 @@ INSTALLED_APPS = [ "metron", "markitup", "taggit", - "cms", "mptt", "reversion", "easy_thumbnails", @@ -168,6 +167,8 @@ INSTALLED_APPS = [ "symposion.about", "symposion.sponsorship", "symposion.conference", + "symposion.cms", + "symposion.boxes", ] FIXTURE_DIRS = [ @@ -201,8 +202,13 @@ DEBUG_TOOLBAR_CONFIG = { } MARKITUP_FILTER = ("markdown.markdown", {"safe_mode": True}) +MARKITUP_SET = "markitup/sets/markdown" +MARKITUP_SKIN = "markitup/skins/simple" + CONFERENCE_ID = 1 +SYMPOSION_PAGE_REGEX = r"(([\w-]{1,})(/[\w-]{1,})*)/" + # local_settings.py can be used to override environment-specific settings # like database and email that differ between development and production. try: diff --git a/symposion/templates/boxes/box.html b/symposion/templates/boxes/box.html new file mode 100644 index 00000000..e65393ae --- /dev/null +++ b/symposion/templates/boxes/box.html @@ -0,0 +1,31 @@ +{% load markitup_tags %} +{% load i18n %} + +{% if form %} + +{% endif %} + +
+ {% if form %} + + {% endif %} + {{ box.content|safe }} +
diff --git a/symposion/templates/cms/page_detail.html b/symposion/templates/cms/page_detail.html index fb9e336e..cc13647e 100644 --- a/symposion/templates/cms/page_detail.html +++ b/symposion/templates/cms/page_detail.html @@ -1,19 +1,22 @@ -{% extends "subnav_base.html" %} +{% extends "site_base.html" %} -{% block subnav %} - +{% load sitetree %} +{% load i18n %} + +{% block body_class %}cms-page{% endblock %} + +{% block head_title %}{{ page.title }}{% endblock %} + +{% block page_title %} + {{ page.title }} + {% if editable %} +
+ +
+ {% endif %} {% endblock %} +{% block breadcrumbs %}{% sitetree_breadcrumbs from "main" %}{% endblock %} {% block body %} -

{{ page.title }}

{{ page.body }} {% endblock %} \ No newline at end of file diff --git a/symposion/templates/cms/page_edit.html b/symposion/templates/cms/page_edit.html new file mode 100644 index 00000000..e361ecdf --- /dev/null +++ b/symposion/templates/cms/page_edit.html @@ -0,0 +1,22 @@ +{% extends "site_base.html" %} + +{% load sitetree %} +{% load i18n %} +{% load bootstrap_tags %} + +{% block body_class %}cms-page{% endblock %} + +{% block head_title %}Create Page{% endblock %} + +{% block page_title %}{% trans "Edit page at:" %} {{ path }}{% endblock %} +{% block breadcrumbs %}{% sitetree_breadcrumbs from "main" %}{% endblock %} + +{% block body %} +
+ {% csrf_token %} + {{ form|as_bootstrap }} +
+ +
+
+{% endblock %} \ No newline at end of file diff --git a/symposion/templates/site_base.html b/symposion/templates/site_base.html index 5924007d..64b95710 100644 --- a/symposion/templates/site_base.html +++ b/symposion/templates/site_base.html @@ -3,20 +3,43 @@ {% load metron_tags %} {% load i18n %} {% load sitetree %} +{% load markitup_tags %} +{% load static %} {% block extra_head_base %} + + + {% markitup_media "no-jquery" %} {% block extra_head %}{% endblock %} {% endblock %} {% block nav %} - {% sitetree_menu from "root" include "trunk" %} + {% sitetree_menu from "main" include "trunk" %} +{% endblock %} + +{% block body_base %} +
+ {% include "_messages.html" %} + + {% block breadcrumbs %} + {% sitetree_breadcrumbs from "main" %} + {% endblock %} + + {% block page_title %} + {% endblock %} + + {% block body %} + {% endblock %} +
{% endblock %} {% block footer %} {% include "_footer.html" %} {% endblock %} -{% block extra_script %} +{% block script_base %} {% analytics %} - {% block extra_body %}{% endblock %} -{% endblock %} \ No newline at end of file + + + {% block extra_script %}{% endblock %} +{% endblock %} diff --git a/symposion/templates/sitetree/breadcrumbs.html b/symposion/templates/sitetree/breadcrumbs.html new file mode 100644 index 00000000..3f292f2e --- /dev/null +++ b/symposion/templates/sitetree/breadcrumbs.html @@ -0,0 +1,17 @@ +{% load sitetree %} +{% if sitetree_items %} + +{% else %} + +{% endif %} diff --git a/symposion/templates/sitetree/menu.html b/symposion/templates/sitetree/menu.html index 93a50ea8..71e6ac1b 100644 --- a/symposion/templates/sitetree/menu.html +++ b/symposion/templates/sitetree/menu.html @@ -1,11 +1,16 @@ {% load sitetree %} \ No newline at end of file + {% for item in sitetree_items %} +
  • + {% if item.has_children %} + + {{ item.title_resolved }} + + + {% sitetree_children of item for menu template "sitetree/submenu.html" %} + {% else %} + {{ item.title_resolved }} + {% endif %} +
  • + {% endfor %} + diff --git a/symposion/templates/sitetree/submenu.html b/symposion/templates/sitetree/submenu.html new file mode 100644 index 00000000..93684588 --- /dev/null +++ b/symposion/templates/sitetree/submenu.html @@ -0,0 +1,8 @@ +{% load sitetree %} + diff --git a/symposion/templates/sitetree/tree.html b/symposion/templates/sitetree/tree.html new file mode 100644 index 00000000..e7a1001f --- /dev/null +++ b/symposion/templates/sitetree/tree.html @@ -0,0 +1,15 @@ +{% load sitetree %} +{% if sitetree_items %} +
      + {% for item in sitetree_items %} + {% if item.insitetree %} +
    • + {{ item.title_resolved }} + {% if item.has_children %} + {% sitetree_children of item for sitetree template "sitetree/tree.html" %} + {% endif %} +
    • + {% endif %} + {% endfor %} +
    +{% endif %} diff --git a/symposion/urls.py b/symposion/urls.py index 8a8a6f9f..cd922680 100644 --- a/symposion/urls.py +++ b/symposion/urls.py @@ -20,9 +20,11 @@ urlpatterns = patterns("", url(r"^account/", include("account.urls")), # url(r"^openid/", include(PinaxConsumer().urls)), - url(r"^(?P%s)/$" % WIKI_SLUG, "cms.views.page", name="cms_page"), + url(r"^boxes/", include("symposion.boxes.urls")), url(r"^markitup/", include("markitup.urls")), + + url(r"^", include("symposion.cms.urls")), ) -urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) \ No newline at end of file +urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) From bc4ae3b0994bdc310f62b832d035593869d2069e Mon Sep 17 00:00:00 2001 From: Luke Hatcher Date: Tue, 10 Jul 2012 18:19:42 -0400 Subject: [PATCH 049/751] update theme and dua --- requirements/base.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements/base.txt b/requirements/base.txt index d315f8f7..fb407993 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -8,7 +8,7 @@ --extra-index-url=http://dist.pinaxproject.com/fresh-start/ Django==1.4 -pinax-theme-bootstrap==2.0.3 +pinax-theme-bootstrap==2.0.4 django-forms-bootstrap==2.0.3.post1 metron==0.1 # 0.2.dev3 pinax-utils==1.0b1.dev3 @@ -20,8 +20,8 @@ django-openid==0.3a1 python-openid==2.2.5 django_compressor==1.2a1 --e git+git://github.com/pinax/pinax-theme-bootstrap-account.git@70c0be0279d61bd2d0f949698f94b8f938918277#egg=pinax-theme-bootstrap-account --e git+git://github.com/pinax/django-user-accounts.git@3676d2c7ea0e9a5c3f8510ac8e6f8d58175e6b5f#egg=django-user-accounts +-e git+git://github.com/pinax/pinax-theme-bootstrap-account.git@8fc34ba4309fc1edd12e5836cc1398c3f9597e6d#egg=pinax-theme-bootstrap-account +-e git+git://github.com/pinax/django-user-accounts.git@fd08c676ae71d0b9d7353ddee123deb751d6ee15#egg=django-user-accounts django-mptt==0.5.2 django-taggit==0.9.3 From 962dc1eb2340e6c17e171fb63d4a5c9bd2673d44 Mon Sep 17 00:00:00 2001 From: Luke Hatcher Date: Tue, 10 Jul 2012 18:14:49 -0400 Subject: [PATCH 050/751] update debug toolbar --- requirements/base.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/base.txt b/requirements/base.txt index f2e779bb..676ac342 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -11,7 +11,7 @@ Django==1.4 pinax-theme-bootstrap==2.0.3 metron==0.1 # 0.2.dev3 pinax-utils==1.0b1.dev3 -django-debug-toolbar==0.9.1 +django-debug-toolbar==0.9.4 django-mailer==0.2a1 django-timezones==0.2 pytz==2011n From 5e7c15d51c641590d0e0150412a4c618aa823deb Mon Sep 17 00:00:00 2001 From: Luke Hatcher Date: Tue, 10 Jul 2012 18:19:42 -0400 Subject: [PATCH 051/751] update theme and dua Conflicts: requirements/base.txt --- requirements/base.txt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/requirements/base.txt b/requirements/base.txt index 676ac342..8dffaf97 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -8,7 +8,8 @@ --extra-index-url=http://dist.pinaxproject.com/fresh-start/ Django==1.4 -pinax-theme-bootstrap==2.0.3 +pinax-theme-bootstrap==2.0.4 +django-forms-bootstrap==2.0.3.post1 metron==0.1 # 0.2.dev3 pinax-utils==1.0b1.dev3 django-debug-toolbar==0.9.4 @@ -19,7 +20,7 @@ django-openid==0.3a1 python-openid==2.2.5 django_compressor==1.2a1 --e git+git://github.com/pinax/pinax-theme-bootstrap-account.git@70c0be0279d61bd2d0f949698f94b8f938918277#egg=pinax-theme-bootstrap-account --e git+git://github.com/pinax/django-user-accounts.git@3676d2c7ea0e9a5c3f8510ac8e6f8d58175e6b5f#egg=django-user-accounts +-e git+git://github.com/pinax/pinax-theme-bootstrap-account.git@8fc34ba4309fc1edd12e5836cc1398c3f9597e6d#egg=pinax-theme-bootstrap-account +-e git+git://github.com/pinax/django-user-accounts.git@fd08c676ae71d0b9d7353ddee123deb751d6ee15#egg=django-user-accounts django-forms-bootstrap==2.0.3.post1 From 3bed4f03c1b13f77bff8aa752978130ee3b0869a Mon Sep 17 00:00:00 2001 From: Luke Hatcher Date: Wed, 11 Jul 2012 18:40:02 -0400 Subject: [PATCH 052/751] remove old project dir that seemed to come back with the merge commit --- symposion_project/apps/cms/__init__.py | 0 symposion_project/apps/cms/admin.py | 12 ---- symposion_project/apps/cms/models.py | 65 ------------------- symposion_project/apps/cms/views.py | 15 ----- .../templates/cms/page_detail.html | 19 ------ .../templates/sitetree/menu.html | 11 ---- 6 files changed, 122 deletions(-) delete mode 100644 symposion_project/apps/cms/__init__.py delete mode 100644 symposion_project/apps/cms/admin.py delete mode 100644 symposion_project/apps/cms/models.py delete mode 100644 symposion_project/apps/cms/views.py delete mode 100644 symposion_project/templates/cms/page_detail.html delete mode 100644 symposion_project/templates/sitetree/menu.html diff --git a/symposion_project/apps/cms/__init__.py b/symposion_project/apps/cms/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/symposion_project/apps/cms/admin.py b/symposion_project/apps/cms/admin.py deleted file mode 100644 index ded416ad..00000000 --- a/symposion_project/apps/cms/admin.py +++ /dev/null @@ -1,12 +0,0 @@ -from django.contrib import admin - -from mptt.admin import MPTTModelAdmin - -from cms.models import Page - - -class PageAdmin(MPTTModelAdmin): - prepopulated_fields = {"slug": ("title",)} - list_display = ("title", "published", "status") - -admin.site.register(Page, PageAdmin) diff --git a/symposion_project/apps/cms/models.py b/symposion_project/apps/cms/models.py deleted file mode 100644 index 5d07b538..00000000 --- a/symposion_project/apps/cms/models.py +++ /dev/null @@ -1,65 +0,0 @@ -from datetime import datetime - -from django.db import models -from django.utils.translation import ugettext_lazy as _ - -from markitup.fields import MarkupField - -from taggit.managers import TaggableManager - -from mptt.models import MPTTModel, TreeForeignKey -from mptt.utils import drilldown_tree_for_node - -import reversion - - -class ContentBase(models.Model): - - STATUS_CHOICES = ( - (1, _("Draft")), - (2, _("Public")), - ) - - title = models.CharField(max_length=100) - slug = models.CharField(max_length=100, blank=True, null=True) - body = MarkupField() - - tags = TaggableManager(blank=True) - - status = models.IntegerField(choices=STATUS_CHOICES, default=2) - published = models.DateTimeField(default=datetime.now) - created = models.DateTimeField(editable=False, default=datetime.now) - updated = models.DateTimeField(editable=False, default=datetime.now) - - class Meta: - abstract = True - - -class Page(MPTTModel, ContentBase): - - parent = TreeForeignKey("self", null=True, blank=True, related_name="children") - ordering = models.PositiveIntegerField(default=1) - path = models.TextField(blank=True, editable=False) - - def __unicode__(self): - return self.title - - def save(self, calculate_path=True, *args, **kwargs): - super(Page, self).save(*args, **kwargs) - if calculate_path: - self.calculate_path() - - def calculate_path(self): - self.path = "" - for page in drilldown_tree_for_node(self): - if page == self: - self.path += page.slug - break - else: - self.path += "%s/" % page.slug - self.save(calculate_path=False) - - class MPTTMeta: - order_insertion_by = ["ordering", "title"] - -reversion.register(Page) diff --git a/symposion_project/apps/cms/views.py b/symposion_project/apps/cms/views.py deleted file mode 100644 index 478d2cee..00000000 --- a/symposion_project/apps/cms/views.py +++ /dev/null @@ -1,15 +0,0 @@ -from django.shortcuts import render_to_response, get_object_or_404 -from django.template import RequestContext - -from cms.models import Page - - -def page(request, slug): - - page = get_object_or_404(Page, path=slug) - siblings = page.get_siblings(include_self=True) - - return render_to_response("cms/page_detail.html", { - "page": page, - "siblings": siblings, - }, context_instance=RequestContext(request)) diff --git a/symposion_project/templates/cms/page_detail.html b/symposion_project/templates/cms/page_detail.html deleted file mode 100644 index fb9e336e..00000000 --- a/symposion_project/templates/cms/page_detail.html +++ /dev/null @@ -1,19 +0,0 @@ -{% extends "subnav_base.html" %} - -{% block subnav %} - -{% endblock %} - -{% block body %} -

    {{ page.title }}

    - {{ page.body }} -{% endblock %} \ No newline at end of file diff --git a/symposion_project/templates/sitetree/menu.html b/symposion_project/templates/sitetree/menu.html deleted file mode 100644 index 93a50ea8..00000000 --- a/symposion_project/templates/sitetree/menu.html +++ /dev/null @@ -1,11 +0,0 @@ -{% load sitetree %} - \ No newline at end of file From 1f59ffa4894598a176b4ba4e6fe71381d65bb72d Mon Sep 17 00:00:00 2001 From: Luke Hatcher Date: Wed, 11 Jul 2012 18:51:09 -0400 Subject: [PATCH 053/751] update project layout --- manage.py | 2 +- symposion/__init__.py | 8 -------- symposion/about/__init__.py | 0 symposion/about/models.py | 3 --- symposion/about/urls.py | 11 ----------- symposion/about/views.py | 1 - symposion_project/__init__.py | 8 ++++++++ {symposion => symposion_project}/settings.py | 7 ++----- {symposion => symposion_project}/static/README | 0 .../templates/_footer.html | 0 .../templates/about/what_next.html | 0 .../templates/boxes/box.html | 0 .../templates/cms/page_detail.html | 0 .../templates/cms/page_edit.html | 0 .../templates/homepage.html | 0 .../templates/site_base.html | 0 .../templates/sitetree/breadcrumbs.html | 0 .../templates/sitetree/menu.html | 0 .../templates/sitetree/submenu.html | 0 .../templates/sitetree/tree.html | 0 .../templates/sponsorship/_horizontal_by_level.html | 0 .../templates/sponsorship/_sponsor_link.html | 0 .../templates/sponsorship/_vertical_by_level.html | 0 .../templates/sponsorship/_wall.html | 0 {symposion => symposion_project}/urls.py | 1 - {symposion => symposion_project}/wsgi.py | 0 26 files changed, 11 insertions(+), 30 deletions(-) delete mode 100644 symposion/about/__init__.py delete mode 100644 symposion/about/models.py delete mode 100644 symposion/about/urls.py delete mode 100644 symposion/about/views.py create mode 100644 symposion_project/__init__.py rename {symposion => symposion_project}/settings.py (98%) rename {symposion => symposion_project}/static/README (100%) rename {symposion => symposion_project}/templates/_footer.html (100%) rename {symposion => symposion_project}/templates/about/what_next.html (100%) rename {symposion => symposion_project}/templates/boxes/box.html (100%) rename {symposion => symposion_project}/templates/cms/page_detail.html (100%) rename {symposion => symposion_project}/templates/cms/page_edit.html (100%) rename {symposion => symposion_project}/templates/homepage.html (100%) rename {symposion => symposion_project}/templates/site_base.html (100%) rename {symposion => symposion_project}/templates/sitetree/breadcrumbs.html (100%) rename {symposion => symposion_project}/templates/sitetree/menu.html (100%) rename {symposion => symposion_project}/templates/sitetree/submenu.html (100%) rename {symposion => symposion_project}/templates/sitetree/tree.html (100%) rename {symposion => symposion_project}/templates/sponsorship/_horizontal_by_level.html (100%) rename {symposion => symposion_project}/templates/sponsorship/_sponsor_link.html (100%) rename {symposion => symposion_project}/templates/sponsorship/_vertical_by_level.html (100%) rename {symposion => symposion_project}/templates/sponsorship/_wall.html (100%) rename {symposion => symposion_project}/urls.py (94%) rename {symposion => symposion_project}/wsgi.py (100%) diff --git a/manage.py b/manage.py index 2587018b..6cb95086 100755 --- a/manage.py +++ b/manage.py @@ -2,7 +2,7 @@ import os, sys if __name__ == "__main__": - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "symposion.settings") + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "symposion_project.settings") from django.core.management import execute_from_command_line diff --git a/symposion/__init__.py b/symposion/__init__.py index ccd713a2..e69de29b 100644 --- a/symposion/__init__.py +++ b/symposion/__init__.py @@ -1,8 +0,0 @@ -# -*- coding: utf-8 -*- - -__about__ = """ -This project takes the zero_project and adds basic account management -functionality such as sign up, log in, password change/reset and email -confirmation. It is a foundation suitable for most sites that have user -accounts. -""" diff --git a/symposion/about/__init__.py b/symposion/about/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/symposion/about/models.py b/symposion/about/models.py deleted file mode 100644 index 71a83623..00000000 --- a/symposion/about/models.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.db import models - -# Create your models here. diff --git a/symposion/about/urls.py b/symposion/about/urls.py deleted file mode 100644 index 3b5c17ea..00000000 --- a/symposion/about/urls.py +++ /dev/null @@ -1,11 +0,0 @@ -from django.conf.urls.defaults import * -from django.views.generic.simple import direct_to_template - - -urlpatterns = patterns("", - url(r"^$", direct_to_template, {"template": "about/about.html"}, name="about"), - url(r"^terms/$", direct_to_template, {"template": "about/terms.html"}, name="terms"), - url(r"^privacy/$", direct_to_template, {"template": "about/privacy.html"}, name="privacy"), - url(r"^dmca/$", direct_to_template, {"template": "about/dmca.html"}, name="dmca"), - url(r"^what_next/$", direct_to_template, {"template": "about/what_next.html"}, name="what_next"), -) diff --git a/symposion/about/views.py b/symposion/about/views.py deleted file mode 100644 index 60f00ef0..00000000 --- a/symposion/about/views.py +++ /dev/null @@ -1 +0,0 @@ -# Create your views here. diff --git a/symposion_project/__init__.py b/symposion_project/__init__.py new file mode 100644 index 00000000..ccd713a2 --- /dev/null +++ b/symposion_project/__init__.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- + +__about__ = """ +This project takes the zero_project and adds basic account management +functionality such as sign up, log in, password change/reset and email +confirmation. It is a foundation suitable for most sites that have user +accounts. +""" diff --git a/symposion/settings.py b/symposion_project/settings.py similarity index 98% rename from symposion/settings.py rename to symposion_project/settings.py index f0815a34..60ce8b58 100644 --- a/symposion/settings.py +++ b/symposion_project/settings.py @@ -111,7 +111,7 @@ MIDDLEWARE_CLASSES = [ "debug_toolbar.middleware.DebugToolbarMiddleware", ] -ROOT_URLCONF = "symposion.urls" +ROOT_URLCONF = "symposion_project.urls" TEMPLATE_DIRS = [ os.path.join(PROJECT_ROOT, "templates"), @@ -159,12 +159,9 @@ INSTALLED_APPS = [ "reversion", "easy_thumbnails", "sitetree", - - # Pinax "account", - # project - "symposion.about", + # symposion "symposion.sponsorship", "symposion.conference", "symposion.cms", diff --git a/symposion/static/README b/symposion_project/static/README similarity index 100% rename from symposion/static/README rename to symposion_project/static/README diff --git a/symposion/templates/_footer.html b/symposion_project/templates/_footer.html similarity index 100% rename from symposion/templates/_footer.html rename to symposion_project/templates/_footer.html diff --git a/symposion/templates/about/what_next.html b/symposion_project/templates/about/what_next.html similarity index 100% rename from symposion/templates/about/what_next.html rename to symposion_project/templates/about/what_next.html diff --git a/symposion/templates/boxes/box.html b/symposion_project/templates/boxes/box.html similarity index 100% rename from symposion/templates/boxes/box.html rename to symposion_project/templates/boxes/box.html diff --git a/symposion/templates/cms/page_detail.html b/symposion_project/templates/cms/page_detail.html similarity index 100% rename from symposion/templates/cms/page_detail.html rename to symposion_project/templates/cms/page_detail.html diff --git a/symposion/templates/cms/page_edit.html b/symposion_project/templates/cms/page_edit.html similarity index 100% rename from symposion/templates/cms/page_edit.html rename to symposion_project/templates/cms/page_edit.html diff --git a/symposion/templates/homepage.html b/symposion_project/templates/homepage.html similarity index 100% rename from symposion/templates/homepage.html rename to symposion_project/templates/homepage.html diff --git a/symposion/templates/site_base.html b/symposion_project/templates/site_base.html similarity index 100% rename from symposion/templates/site_base.html rename to symposion_project/templates/site_base.html diff --git a/symposion/templates/sitetree/breadcrumbs.html b/symposion_project/templates/sitetree/breadcrumbs.html similarity index 100% rename from symposion/templates/sitetree/breadcrumbs.html rename to symposion_project/templates/sitetree/breadcrumbs.html diff --git a/symposion/templates/sitetree/menu.html b/symposion_project/templates/sitetree/menu.html similarity index 100% rename from symposion/templates/sitetree/menu.html rename to symposion_project/templates/sitetree/menu.html diff --git a/symposion/templates/sitetree/submenu.html b/symposion_project/templates/sitetree/submenu.html similarity index 100% rename from symposion/templates/sitetree/submenu.html rename to symposion_project/templates/sitetree/submenu.html diff --git a/symposion/templates/sitetree/tree.html b/symposion_project/templates/sitetree/tree.html similarity index 100% rename from symposion/templates/sitetree/tree.html rename to symposion_project/templates/sitetree/tree.html diff --git a/symposion/templates/sponsorship/_horizontal_by_level.html b/symposion_project/templates/sponsorship/_horizontal_by_level.html similarity index 100% rename from symposion/templates/sponsorship/_horizontal_by_level.html rename to symposion_project/templates/sponsorship/_horizontal_by_level.html diff --git a/symposion/templates/sponsorship/_sponsor_link.html b/symposion_project/templates/sponsorship/_sponsor_link.html similarity index 100% rename from symposion/templates/sponsorship/_sponsor_link.html rename to symposion_project/templates/sponsorship/_sponsor_link.html diff --git a/symposion/templates/sponsorship/_vertical_by_level.html b/symposion_project/templates/sponsorship/_vertical_by_level.html similarity index 100% rename from symposion/templates/sponsorship/_vertical_by_level.html rename to symposion_project/templates/sponsorship/_vertical_by_level.html diff --git a/symposion/templates/sponsorship/_wall.html b/symposion_project/templates/sponsorship/_wall.html similarity index 100% rename from symposion/templates/sponsorship/_wall.html rename to symposion_project/templates/sponsorship/_wall.html diff --git a/symposion/urls.py b/symposion_project/urls.py similarity index 94% rename from symposion/urls.py rename to symposion_project/urls.py index cd922680..4327ef0c 100644 --- a/symposion/urls.py +++ b/symposion_project/urls.py @@ -16,7 +16,6 @@ urlpatterns = patterns("", "template": "homepage.html", }, name="home"), url(r"^admin/", include(admin.site.urls)), - url(r"^about/", include("symposion.about.urls")), url(r"^account/", include("account.urls")), # url(r"^openid/", include(PinaxConsumer().urls)), diff --git a/symposion/wsgi.py b/symposion_project/wsgi.py similarity index 100% rename from symposion/wsgi.py rename to symposion_project/wsgi.py From 76c4a7b79c5d8e5f5270f0a386a0fbe0c61c5bf8 Mon Sep 17 00:00:00 2001 From: Luke Hatcher Date: Thu, 12 Jul 2012 00:38:01 -0400 Subject: [PATCH 054/751] update conferences app --- symposion/conference/admin.py | 6 +++++- symposion/conference/models.py | 5 +++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/symposion/conference/admin.py b/symposion/conference/admin.py index 3d2bccaa..7f0b55c2 100644 --- a/symposion/conference/admin.py +++ b/symposion/conference/admin.py @@ -4,4 +4,8 @@ from symposion.conference.models import Conference, Section admin.site.register(Conference, list_display=("title", "start_date", "end_date")) -admin.site.register(Section, list_display=("name", "conference", "start_date", "end_date")) +admin.site.register( + Section, + prepopulated_fields = {"slug": ("name",)}, + list_display = ("name", "conference", "start_date", "end_date") +) diff --git a/symposion/conference/models.py b/symposion/conference/models.py index a8e3f9c4..66a83054 100644 --- a/symposion/conference/models.py +++ b/symposion/conference/models.py @@ -52,13 +52,14 @@ class Section(models.Model): conference = models.ForeignKey(Conference, verbose_name=_("conference")) name = models.CharField(_("name"), max_length=100) - + slug = models.SlugField() + # when the section runs start_date = models.DateField(_("start date"), null=True, blank=True) end_date = models.DateField(_("end date"), null=True, blank=True) def __unicode__(self): - return self.name + return "%s %s" % (self.conference, self.name) class Meta(object): verbose_name = _("section") From 2b7f5546a0946c9d812096131e3e3693a05cc168 Mon Sep 17 00:00:00 2001 From: Luke Hatcher Date: Thu, 12 Jul 2012 00:38:24 -0400 Subject: [PATCH 055/751] add speakers app from pycon --- symposion/speakers/__init__.py | 0 symposion/speakers/admin.py | 9 ++ symposion/speakers/fixture_gen.py | 31 +++++ symposion/speakers/forms.py | 63 +++++++++ symposion/speakers/management/__init__.py | 0 .../speakers/management/commands/__init__.py | 0 .../commands/export_speaker_data.py | 19 +++ symposion/speakers/models.py | 55 ++++++++ symposion/speakers/urls.py | 9 ++ symposion/speakers/views.py | 121 ++++++++++++++++++ 10 files changed, 307 insertions(+) create mode 100644 symposion/speakers/__init__.py create mode 100644 symposion/speakers/admin.py create mode 100644 symposion/speakers/fixture_gen.py create mode 100644 symposion/speakers/forms.py create mode 100644 symposion/speakers/management/__init__.py create mode 100644 symposion/speakers/management/commands/__init__.py create mode 100644 symposion/speakers/management/commands/export_speaker_data.py create mode 100644 symposion/speakers/models.py create mode 100644 symposion/speakers/urls.py create mode 100644 symposion/speakers/views.py diff --git a/symposion/speakers/__init__.py b/symposion/speakers/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/symposion/speakers/admin.py b/symposion/speakers/admin.py new file mode 100644 index 00000000..9c1174b1 --- /dev/null +++ b/symposion/speakers/admin.py @@ -0,0 +1,9 @@ +from django.contrib import admin + +from symposion.speakers.models import Speaker + + +admin.site.register(Speaker, + list_display = ["name", "email", "twitter_username", "sessions_preference", "created"], + search_fields = ["name"], +) \ No newline at end of file diff --git a/symposion/speakers/fixture_gen.py b/symposion/speakers/fixture_gen.py new file mode 100644 index 00000000..26993fcb --- /dev/null +++ b/symposion/speakers/fixture_gen.py @@ -0,0 +1,31 @@ +from django.contrib.auth.models import User + +from fixture_generator import fixture_generator + +from symposion.speakers.models import Speaker + + +@fixture_generator(Speaker, User) +def speakers(): + guido = User.objects.create_user("guido", "guido@python.org", "pythonisawesome") + matz = User.objects.create_user("matz", "matz@ruby.org", "pythonsucks") + larry = User.objects.create_user("larryw", "larry@perl.org", "linenoisehere") + + Speaker.objects.create( + user=guido, + name="Guido van Rossum", + biography="I wrote Python, and named it after Monty Python", + twitter_username="gvanrossum", + ) + Speaker.objects.create( + user=matz, + name="Yukihiro Matsumoto", + biography="I wrote Ruby, and named it after the rare gem Ruby, a pun " + "on Perl/pearl.", + twitter_username="yukihiro_matz" + ) + Speaker.objects.create( + user=larry, + name="Larry Wall", + biography="I wrote Perl, and named it after the Parable of the Pearl", + ) diff --git a/symposion/speakers/forms.py b/symposion/speakers/forms.py new file mode 100644 index 00000000..c0dd1257 --- /dev/null +++ b/symposion/speakers/forms.py @@ -0,0 +1,63 @@ +from django import forms + +from django.contrib import messages + +from markitup.widgets import MarkItUpWidget + +from symposion.speakers.models import Speaker + + +class SpeakerForm(forms.ModelForm): + + sessions_preference = forms.ChoiceField( + widget=forms.RadioSelect(), + choices=Speaker.SESSION_COUNT_CHOICES, + required=False, + help_text="If you've submitted multiple proposals, please let us know if you only want to give one or if you'd like to give two talks." + ) + + class Meta: + model = Speaker + fields = [ + "name", + "biography", + "photo", + "twitter_username", + "sessions_preference" + ] + widgets = { + "biography": MarkItUpWidget(), + } + + def clean_twitter_username(self): + value = self.cleaned_data["twitter_username"] + if value.startswith("@"): + value = value[1:] + return value + + def clean_sessions_preference(self): + value = self.cleaned_data["sessions_preference"] + if not value: + return None + return int(value) + + +# class SignupForm(PinaxSignupForm): + +# def save(self, speaker, request=None): +# # don't assume a username is available. it is a common removal if +# # site developer wants to use email authentication. +# username = self.cleaned_data.get("username") +# email = self.cleaned_data["email"] +# new_user = self.create_user(username) +# if speaker.invite_email == new_user.email: +# # already verified so can just create +# EmailAddress(user=new_user, email=email, verified=True, primary=True).save() +# else: +# if request: +# messages.info(request, u"Confirmation email sent to %(email)s" % {"email": email}) +# EmailAddress.objects.add_email(new_user, email) +# new_user.is_active = False +# new_user.save() +# self.after_signup(new_user) +# return new_user diff --git a/symposion/speakers/management/__init__.py b/symposion/speakers/management/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/symposion/speakers/management/commands/__init__.py b/symposion/speakers/management/commands/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/symposion/speakers/management/commands/export_speaker_data.py b/symposion/speakers/management/commands/export_speaker_data.py new file mode 100644 index 00000000..5f9f565d --- /dev/null +++ b/symposion/speakers/management/commands/export_speaker_data.py @@ -0,0 +1,19 @@ +import csv +import os + +from django.core.management.base import BaseCommand, CommandError + +from symposion.speakers.models import Speaker + + +class Command(BaseCommand): + + def handle(self, *args, **options): + csv_file = csv.writer(open(os.path.join(os.getcwd(), "speakers.csv"), "wb")) + csv_file.writerow(["Name", "Bio"]) + + for speaker in Speaker.objects.all(): + csv_file.writerow([ + speaker.name.encode("utf-8"), + speaker.biography.encode("utf-8"), + ]) diff --git a/symposion/speakers/models.py b/symposion/speakers/models.py new file mode 100644 index 00000000..8cc4d16d --- /dev/null +++ b/symposion/speakers/models.py @@ -0,0 +1,55 @@ +import datetime + +from django.db import models +from django.core.urlresolvers import reverse + +from django.contrib.auth.models import User + +from markitup.fields import MarkupField + + +class Speaker(models.Model): + + SESSION_COUNT_CHOICES = [ + (1, "One"), + (2, "Two") + ] + + user = models.OneToOneField(User, null=True, related_name="speaker_profile") + name = models.CharField(max_length=100, help_text="As you would like it to appear in the conference program.") + biography = MarkupField(help_text="A little bit about you. Edit using Markdown.") + photo = models.ImageField(upload_to="speaker_photos", blank=True) + twitter_username = models.CharField( + max_length = 15, + blank = True, + help_text = "Your Twitter account" + ) + annotation = models.TextField() # staff only + invite_email = models.CharField(max_length=200, unique=True, null=True, db_index=True) + invite_token = models.CharField(max_length=40, db_index=True) + created = models.DateTimeField( + default = datetime.datetime.now, + editable = False + ) + sessions_preference = models.IntegerField( + choices=SESSION_COUNT_CHOICES, + null=True, + blank=True, + help_text="If you've submitted multiple proposals, please let us know if you only want to give one or if you'd like to give two talks. You may submit more than two proposals." + ) + + def __unicode__(self): + if self.user: + return self.name + else: + return "?" + + def get_absolute_url(self): + return reverse("speaker_edit") + + @property + def email(self): + if self.user is not None: + return self.user.email + else: + return self.invite_email diff --git a/symposion/speakers/urls.py b/symposion/speakers/urls.py new file mode 100644 index 00000000..2dc083a8 --- /dev/null +++ b/symposion/speakers/urls.py @@ -0,0 +1,9 @@ +from django.conf.urls.defaults import * + + +urlpatterns = patterns("symposion.speakers.views", + url(r"^create/$", "speaker_create", name="speaker_create"), + url(r"^create/(\w+)/$", "speaker_create_token", name="speaker_create_token"), + url(r"^edit/(?:(?P\d+)/)?$", "speaker_edit", name="speaker_edit"), + url(r"^profile/(?P\d+)/$", "speaker_profile", name="speaker_profile"), +) diff --git a/symposion/speakers/views.py b/symposion/speakers/views.py new file mode 100644 index 00000000..54716e88 --- /dev/null +++ b/symposion/speakers/views.py @@ -0,0 +1,121 @@ +from django.conf import settings +from django.core.exceptions import ObjectDoesNotExist +from django.db.models import Q +from django.http import Http404, HttpResponse +from django.shortcuts import render, redirect, get_object_or_404 +from django.template import RequestContext + +from django.contrib import messages +from django.contrib.auth.decorators import login_required + +from symposion.proposals.models import ProposalBase +from symposion.speakers.forms import SpeakerForm #, SignupForm +from symposion.speakers.models import Speaker + + +@login_required +def speaker_create(request): + try: + return redirect(request.user.speaker_profile) + except ObjectDoesNotExist: + pass + + if request.method == "POST": + try: + speaker = Speaker.objects.get(invite_email=request.user.email) + found = True + except Speaker.DoesNotExist: + speaker = None + found = False + form = SpeakerForm(request.POST, request.FILES, instance=speaker) + + if form.is_valid(): + speaker = form.save(commit=False) + speaker.user = request.user + if not found: + speaker.invite_email = None + speaker.save() + messages.success(request, "Speaker profile created.") + return redirect("dashboard") + else: + form = SpeakerForm(initial = {"name": request.user.get_full_name()}) + + return render(request, "speakers/speaker_create.html", { + "form": form, + }) + + +def speaker_create_token(request, token): + speaker = get_object_or_404(Speaker, invite_token=token) + request.session["pending-token"] = token + if request.user.is_authenticated(): + # check for speaker profile + try: + existing_speaker = request.user.speaker_profile + except ObjectDoesNotExist: + pass + else: + del request.session["pending-token"] + additional_speakers = ProposalBase.additional_speakers.through + additional_speakers._default_manager.filter( + speaker = speaker + ).update( + speaker = existing_speaker + ) + messages.info(request, "You have been associated with all pending " + "talk proposals") + return redirect("dashboard") + else: + if not request.user.is_authenticated(): + return redirect("account_login") + return redirect("speaker_create") + + +@login_required +def speaker_edit(request, pk=None): + if pk is None: + try: + speaker = request.user.speaker_profile + except Speaker.DoesNotExist: + return redirect("speaker_create") + else: + if request.user.groups.filter(name="organizer").exists(): # @@@ + speaker = get_object_or_404(Speaker, pk=pk) + else: + raise Http404() + + if request.method == "POST": + form = SpeakerForm(request.POST, request.FILES, instance=speaker) + if form.is_valid(): + form.save() + messages.success(request, "Speaker profile updated.") + return redirect("dashboard") + else: + form = SpeakerForm(instance=speaker) + + return render(request, "speakers/speaker_edit.html", { + "form": form, + }) + + +def speaker_profile(request, pk, template_name="speakers/speaker_profile.html", extra_context=None): + + if extra_context is None: + extra_context = {} + + speaker = get_object_or_404(Speaker, pk=pk) + + # schedule may not be installed so we need to check for sessions + if hasattr(speaker, "sessions"): + sessions = speaker.sessions.exclude(slot=None).order_by("slot__start") + else: + sessions = [] + + if not sessions: + raise Http404() + + return render_to_response(template_name, dict({ + "speaker": speaker, + "sessions": sessions, + "timezone": settings.SCHEDULE_TIMEZONE, + }, **extra_context), context_instance=RequestContext(request)) From 7596922f4d68b64f39e9301c1fdafb97992e309e Mon Sep 17 00:00:00 2001 From: Luke Hatcher Date: Thu, 12 Jul 2012 00:38:39 -0400 Subject: [PATCH 056/751] add proposals app from pycon --- symposion/proposals/__init__.py | 0 symposion/proposals/actions.py | 36 ++ symposion/proposals/admin.py | 32 ++ symposion/proposals/forms.py | 41 +++ symposion/proposals/managers.py | 27 ++ symposion/proposals/models.py | 146 ++++++++ symposion/proposals/templatetags/__init__.py | 0 .../proposals/templatetags/proposal_tags.py | 73 ++++ symposion/proposals/urls.py | 18 + symposion/proposals/views.py | 324 ++++++++++++++++++ 10 files changed, 697 insertions(+) create mode 100644 symposion/proposals/__init__.py create mode 100644 symposion/proposals/actions.py create mode 100644 symposion/proposals/admin.py create mode 100644 symposion/proposals/forms.py create mode 100644 symposion/proposals/managers.py create mode 100644 symposion/proposals/models.py create mode 100644 symposion/proposals/templatetags/__init__.py create mode 100644 symposion/proposals/templatetags/proposal_tags.py create mode 100644 symposion/proposals/urls.py create mode 100644 symposion/proposals/views.py diff --git a/symposion/proposals/__init__.py b/symposion/proposals/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/symposion/proposals/actions.py b/symposion/proposals/actions.py new file mode 100644 index 00000000..b48ac34f --- /dev/null +++ b/symposion/proposals/actions.py @@ -0,0 +1,36 @@ +import csv + +from django.http import HttpResponse + + +def export_as_csv_action(description="Export selected objects as CSV file", + fields=None, exclude=None, header=True): + """ + This function returns an export csv action + 'fields' and 'exclude' work like in django ModelForm + 'header' is whether or not to output the column names as the first row + """ + def export_as_csv(modeladmin, request, queryset): + """ + Generic csv export admin action. + based on http://djangosnippets.org/snippets/1697/ + """ + opts = modeladmin.model._meta + if fields: + fieldset = set(fields) + field_names = fieldset + elif exclude: + excludeset = set(exclude) + field_names = field_names - excludeset + + response = HttpResponse(mimetype='text/csv') + response['Content-Disposition'] = 'attachment; filename=%s.csv' % unicode(opts).replace('.', '_') + + writer = csv.writer(response) + if header: + writer.writerow(list(field_names)) + for obj in queryset: + writer.writerow([unicode(getattr(obj, field)).encode("utf-8", "replace") for field in field_names]) + return response + export_as_csv.short_description = description + return export_as_csv diff --git a/symposion/proposals/admin.py b/symposion/proposals/admin.py new file mode 100644 index 00000000..af274aaa --- /dev/null +++ b/symposion/proposals/admin.py @@ -0,0 +1,32 @@ +from django.contrib import admin + +# from symposion.proposals.actions import export_as_csv_action +from symposion.proposals.models import ProposalSection, ProposalKind + + +# admin.site.register(Proposal, +# list_display = [ +# "id", +# "title", +# "speaker", +# "speaker_email", +# "kind", +# "audience_level", +# "cancelled", +# ], +# list_filter = [ +# "kind__name", +# "result__accepted", +# ], +# actions = [export_as_csv_action("CSV Export", fields=[ +# "id", +# "title", +# "speaker", +# "speaker_email", +# "kind", +# ])] +# ) + + +admin.site.register(ProposalSection) +admin.site.register(ProposalKind) diff --git a/symposion/proposals/forms.py b/symposion/proposals/forms.py new file mode 100644 index 00000000..3f3fe115 --- /dev/null +++ b/symposion/proposals/forms.py @@ -0,0 +1,41 @@ +from django import forms +from django.db.models import Q + +from symposion.proposals.models import SupportingDocument +# from markitup.widgets import MarkItUpWidget + + +# @@@ generic proposal form + + +class AddSpeakerForm(forms.Form): + + email = forms.EmailField( + label="Email address of new speaker (use their email address, not yours)" + ) + + def __init__(self, *args, **kwargs): + self.proposal = kwargs.pop("proposal") + super(AddSpeakerForm, self).__init__(*args, **kwargs) + + def clean_email(self): + value = self.cleaned_data["email"] + exists = self.proposal.additional_speakers.filter( + Q(user=None, invite_email=value) | + Q(user__email=value) + ).exists() + if exists: + raise forms.ValidationError( + "This email address has already been invited to your talk proposal" + ) + return value + + +class SupportingDocumentCreateForm(forms.ModelForm): + + class Meta: + model = SupportingDocument + fields = [ + "file", + "description", + ] diff --git a/symposion/proposals/managers.py b/symposion/proposals/managers.py new file mode 100644 index 00000000..b908c7b6 --- /dev/null +++ b/symposion/proposals/managers.py @@ -0,0 +1,27 @@ +from django.db import models +from django.db.models.query import QuerySet + + +class CachingM2MQuerySet(QuerySet): + + def __init__(self, *args, **kwargs): + super(CachingM2MQuerySet, self).__init__(*args, **kwargs) + self.cached_m2m_field = kwargs["m2m_field"] + + def iterator(self): + parent_iter = super(CachingM2MQuerySet, self).iterator() + m2m_model = getattr(self.model, self.cached_m2m_field).through + + for obj in parent_iter: + if obj.id in cached_objects: + setattr(obj, "_cached_m2m_%s" % self.cached_m2m_field) + yield obj + + +class ProposalManager(models.Manager): + def cache_m2m(self, m2m_field): + return CachingM2MQuerySet(self.model, using=self._db, m2m_field=m2m_field) + AdditionalSpeaker = queryset.model.additional_speakers.through + additional_speakers = collections.defaultdict(set) + for additional_speaker in AdditionalSpeaker._default_manager.filter(proposal__in=queryset).select_related("speaker__user"): + additional_speakers[additional_speaker.proposal_id].add(additional_speaker.speaker) \ No newline at end of file diff --git a/symposion/proposals/models.py b/symposion/proposals/models.py new file mode 100644 index 00000000..f602147d --- /dev/null +++ b/symposion/proposals/models.py @@ -0,0 +1,146 @@ +import datetime +import os +import uuid + +from django.core.urlresolvers import reverse +from django.db import models +from django.db.models import Q + +from django.contrib.auth.models import User + +from markitup.fields import MarkupField + +from model_utils.managers import InheritanceManager + +from symposion.conference.models import Section + + +class ProposalSection(models.Model): + """ + configuration of proposal submissions for a specific Section. + + a section is available for proposals iff: + * it is after start (if there is one) and + * it is before end (if there is one) and + * closed is NULL or False + """ + + section = models.OneToOneField(Section) + + start = models.DateTimeField(null=True, blank=True) + end = models.DateTimeField(null=True, blank=True) + closed = models.NullBooleanField() + published = models.NullBooleanField() + + @classmethod + def available(cls): + now = datetime.datetime.now() + return cls._default_manager.filter( + Q(start__lt=now) | Q(start=None), + Q(end__gt=now) | Q(end=None), + Q(closed=False) | Q(closed=None), + ) + + def __unicode__(self): + return self.section.name + + +class ProposalKind(models.Model): + """ + e.g. talk vs panel vs tutorial vs poster + + Note that if you have different deadlines, reviewers, etc. you'll want + to distinguish the section as well as the kind. + """ + + section = models.ForeignKey(Section, related_name="proposal_kinds") + + name = models.CharField("name", max_length=100) + slug = models.SlugField() + + def __unicode__(self): + return self.name + + +class ProposalBase(models.Model): + + objects = InheritanceManager() + + kind = models.ForeignKey(ProposalKind) + + title = models.CharField(max_length=100) + description = models.TextField( + max_length=400, # @@@ need to enforce 400 in UI + help_text="If your talk is accepted this will be made public and printed in the program. Should be one paragraph, maximum 400 characters." + ) + abstract = MarkupField( + help_text="Detailed description and outline. Will be made public if your talk is accepted. Edit using Markdown." + ) + additional_notes = MarkupField( + blank=True, + help_text="Anything else you'd like the program committee to know when making their selection: your past speaking experience, open source community experience, etc. Edit using Markdown." + ) + submitted = models.DateTimeField( + default=datetime.datetime.now, + editable=False, + ) + speaker = models.ForeignKey("speakers.Speaker", related_name="proposals") + additional_speakers = models.ManyToManyField("speakers.Speaker", through="AdditionalSpeaker", blank=True) + cancelled = models.BooleanField(default=False) + + def can_edit(self): + return True + + @property + def speaker_email(self): + return self.speaker.email + + @property + def number(self): + return str(self.pk).zfill(3) + + def speakers(self): + yield self.speaker + for speaker in self.additional_speakers.exclude(additionalspeaker__status=AdditionalSpeaker.SPEAKING_STATUS_DECLINED): + yield speaker + + +class AdditionalSpeaker(models.Model): + + SPEAKING_STATUS_PENDING = 1 + SPEAKING_STATUS_ACCEPTED = 2 + SPEAKING_STATUS_DECLINED = 3 + + SPEAKING_STATUS = [ + (SPEAKING_STATUS_PENDING, "Pending"), + (SPEAKING_STATUS_ACCEPTED, "Accepted"), + (SPEAKING_STATUS_DECLINED, "Declined"), + ] + + speaker = models.ForeignKey("speakers.Speaker") + proposalbase = models.ForeignKey(ProposalBase) + status = models.IntegerField(choices=SPEAKING_STATUS, default=SPEAKING_STATUS_PENDING) + + class Meta: + db_table = "proposals_proposalbase_additional_speakers" + unique_together = ("speaker", "proposalbase") + + +def uuid_filename(instance, filename): + ext = filename.split(".")[-1] + filename = "%s.%s" % (uuid.uuid4(), ext) + return os.path.join("document", filename) + + +class SupportingDocument(models.Model): + + proposal = models.ForeignKey(ProposalBase, related_name="supporting_documents") + + uploaded_by = models.ForeignKey(User) + created_at = models.DateTimeField(default=datetime.datetime.now) + + file = models.FileField(upload_to=uuid_filename) + description = models.CharField(max_length=140) + + def download_url(self): + return reverse("proposal_document_download", args=[self.pk, os.path.basename(self.file.name).lower()]) diff --git a/symposion/proposals/templatetags/__init__.py b/symposion/proposals/templatetags/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/symposion/proposals/templatetags/proposal_tags.py b/symposion/proposals/templatetags/proposal_tags.py new file mode 100644 index 00000000..2f728847 --- /dev/null +++ b/symposion/proposals/templatetags/proposal_tags.py @@ -0,0 +1,73 @@ +from django import template + +from symposion.proposals.models import AdditionalSpeaker + + +register = template.Library() + + +class AssociatedProposalsNode(template.Node): + + @classmethod + def handle_token(cls, parser, token): + bits = token.split_contents() + if len(bits) == 3 and bits[1] == "as": + return cls(bits[2]) + else: + raise template.TemplateSyntaxError("%r takes 'as var'" % bits[0]) + + def __init__(self, context_var): + self.context_var = context_var + + def render(self, context): + request = context["request"] + if request.user.speaker_profile: + pending = AdditionalSpeaker.SPEAKING_STATUS_ACCEPTED + speaker = request.user.speaker_profile + queryset = AdditionalSpeaker.objects.filter(speaker=speaker, status=pending) + context[self.context_var] = [item.proposalbase for item in queryset] + else: + context[self.context_var] = None + return u"" + + +class PendingProposalsNode(template.Node): + + @classmethod + def handle_token(cls, parser, token): + bits = token.split_contents() + if len(bits) == 3 and bits[1] == "as": + return cls(bits[2]) + else: + raise template.TemplateSyntaxError("%r takes 'as var'" % bits[0]) + + def __init__(self, context_var): + self.context_var = context_var + + def render(self, context): + request = context["request"] + if request.user.speaker_profile: + pending = AdditionalSpeaker.SPEAKING_STATUS_PENDING + speaker = request.user.speaker_profile + queryset = AdditionalSpeaker.objects.filter(speaker=speaker, status=pending) + context[self.context_var] = [item.proposalbase for item in queryset] + else: + context[self.context_var] = None + return u"" + + +@register.tag +def pending_proposals(parser, token): + """ + {% pending_proposals as pending_proposals %} + """ + return PendingProposalsNode.handle_token(parser, token) + + +@register.tag +def associated_proposals(parser, token): + """ + {% associated_proposals as associated_proposals %} + """ + return AssociatedProposalsNode.handle_token(parser, token) + diff --git a/symposion/proposals/urls.py b/symposion/proposals/urls.py new file mode 100644 index 00000000..e317dbe5 --- /dev/null +++ b/symposion/proposals/urls.py @@ -0,0 +1,18 @@ +from django.conf.urls.defaults import * + + +urlpatterns = patterns("symposion.proposals.views", + url(r"^submit/$", "proposal_submit", name="proposal_submit"), + url(r"^submit/(\w+)/$", "proposal_submit_kind", name="proposal_submit_kind"), + url(r"^(\d+)/$", "proposal_detail", name="proposal_detail"), + url(r"^(\d+)/edit/$", "proposal_edit", name="proposal_edit"), + url(r"^(\d+)/speakers/$", "proposal_speaker_manage", name="proposal_speaker_manage"), + url(r"^(\d+)/cancel/$", "proposal_cancel", name="proposal_cancel"), + url(r"^(\d+)/leave/$", "proposal_leave", name="proposal_leave"), + url(r"^(\d+)/join/$", "proposal_pending_join", name="proposal_pending_join"), + url(r"^(\d+)/decline/$", "proposal_pending_decline", name="proposal_pending_decline"), + + url(r"^(\d+)/document/create/$", "document_create", name="proposal_document_create"), + url(r"^document/(\d+)/delete/$", "document_delete", name="proposal_document_delete"), + url(r"^document/(\d+)/([^/]+)$", "document_download", name="proposal_document_download"), +) diff --git a/symposion/proposals/views.py b/symposion/proposals/views.py new file mode 100644 index 00000000..380098f8 --- /dev/null +++ b/symposion/proposals/views.py @@ -0,0 +1,324 @@ +import random +import sys + +from django.conf import settings +from django.core.exceptions import ObjectDoesNotExist +from django.db.models import Q +from django.http import Http404, HttpResponse, HttpResponseForbidden +from django.shortcuts import render, redirect, get_object_or_404 +from django.utils.hashcompat import sha_constructor +from django.views import static + +from django.contrib import messages +from django.contrib.auth.decorators import login_required + +from account.models import EmailAddress +from symposion.proposals.models import ProposalBase, ProposalSection, ProposalKind +from symposion.proposals.models import SupportingDocument, AdditionalSpeaker +from symposion.speakers.models import Speaker +from symposion.utils.mail import send_email + +from symposion.proposals.forms import AddSpeakerForm, SupportingDocumentCreateForm + + +def get_form(name): + dot = name.rindex('.') + mod_name, form_name = name[:dot], name[dot + 1:] + __import__(mod_name) + return getattr(sys.modules[mod_name], form_name) + + +def proposal_submit(request): + if not request.user.is_authenticated(): + return redirect("home") # @@@ unauth'd speaker info page? + else: + try: + request.user.speaker_profile + except ObjectDoesNotExist: + return redirect("dashboard") + + kinds = [] + for proposal_section in ProposalSection.available(): + for kind in proposal_section.section.proposal_kinds.all(): + kinds.append(kind) + + return render(request, "proposals/proposal_submit.html", { + "kinds": kinds, + }) + + +def proposal_submit_kind(request, kind_slug): + + kind = get_object_or_404(ProposalKind, slug=kind_slug) + + if not request.user.is_authenticated(): + return redirect("home") # @@@ unauth'd speaker info page? + else: + try: + speaker_profile = request.user.speaker_profile + except ObjectDoesNotExist: + return redirect("dashboard") + + form_class = get_form(settings.PROPOSAL_FORMS[kind_slug]) + + if request.method == "POST": + form = form_class(request.POST) + if form.is_valid(): + proposal = form.save(commit=False) + proposal.kind = kind + proposal.speaker = speaker_profile + proposal.save() + form.save_m2m() + messages.success(request, "Proposal submitted.") + if "add-speakers" in request.POST: + return redirect("proposal_speaker_manage", proposal.pk) + return redirect("dashboard") + else: + form = form_class() + + return render(request, "proposals/proposal_submit_kind.html", { + "kind": kind, + "form": form, + }) + + +@login_required +def proposal_speaker_manage(request, pk): + queryset = ProposalBase.objects.select_related("speaker") + proposal = get_object_or_404(queryset, pk=pk) + proposal = ProposalBase.objects.get_subclass(pk=proposal.pk) + + if proposal.speaker != request.user.speaker_profile: + raise Http404() + + if request.method == "POST": + add_speaker_form = AddSpeakerForm(request.POST, proposal=proposal) + if add_speaker_form.is_valid(): + message_ctx = { + "proposal": proposal, + } + + def create_speaker_token(email_address): + # create token and look for an existing speaker to prevent + # duplicate tokens and confusing the pending speaker + try: + pending = Speaker.objects.get( + Q(user=None, invite_email=email_address) + ) + except Speaker.DoesNotExist: + salt = sha_constructor(str(random.random())).hexdigest()[:5] + token = sha_constructor(salt + email_address).hexdigest() + pending = Speaker.objects.create( + invite_email=email_address, + invite_token=token, + ) + else: + token = pending.invite_token + return pending, token + email_address = add_speaker_form.cleaned_data["email"] + # check if email is on the site now + users = EmailAddress.objects.get_users_for(email_address) + if users: + # should only be one since we enforce unique email + user = users[0] + message_ctx["user"] = user + # look for speaker profile + try: + speaker = user.speaker_profile + except ObjectDoesNotExist: + speaker, token = create_speaker_token(email_address) + message_ctx["token"] = token + # fire off email to user to create profile + send_email( + [email_address], "speaker_no_profile", + context = message_ctx + ) + else: + # fire off email to user letting them they are loved. + send_email( + [email_address], "speaker_addition", + context = message_ctx + ) + else: + speaker, token = create_speaker_token(email_address) + message_ctx["token"] = token + # fire off email letting user know about site and to create + # account and speaker profile + send_email( + [email_address], "speaker_invite", + context = message_ctx + ) + invitation, created = AdditionalSpeaker.objects.get_or_create(proposalbase=proposal.proposalbase_ptr, speaker=speaker) + messages.success(request, "Speaker invited to proposal.") + return redirect("proposal_speaker_manage", proposal.pk) + else: + add_speaker_form = AddSpeakerForm(proposal=proposal) + ctx = { + "proposal": proposal, + "speakers": proposal.speakers(), + "add_speaker_form": add_speaker_form, + } + return render(request, "proposals/proposal_speaker_manage.html", ctx) + + +@login_required +def proposal_edit(request, pk): + queryset = ProposalBase.objects.select_related("speaker") + proposal = get_object_or_404(queryset, pk=pk) + proposal = ProposalBase.objects.get_subclass(pk=proposal.pk) + + if request.user != proposal.speaker.user: + raise Http404() + + if not proposal.can_edit(): + ctx = { + "title": "Proposal editing closed", + "body": "Proposal editing is closed for this session type." + } + return render(request, "proposals/proposal_error.html", ctx) + + form_class = get_form(settings.PROPOSAL_FORMS[proposal.kind.slug]) + + if request.method == "POST": + form = form_class(request.POST, instance=proposal) + if form.is_valid(): + form.save() + messages.success(request, "Proposal updated.") + return redirect("proposal_detail", proposal.pk) + else: + form = form_class(instance=proposal) + + return render(request, "proposals/proposal_edit.html", { + "proposal": proposal, + "form": form, + }) + + +@login_required +def proposal_detail(request, pk): + queryset = ProposalBase.objects.select_related("speaker", "speaker__user") + proposal = get_object_or_404(queryset, pk=pk) + proposal = ProposalBase.objects.get_subclass(pk=proposal.pk) + + if request.user not in [p.user for p in proposal.speakers()]: + raise Http404() + + return render(request, "proposals/proposal_detail.html", { + "proposal": proposal, + }) + + +@login_required +def proposal_cancel(request, pk): + queryset = ProposalBase.objects.select_related("speaker") + proposal = get_object_or_404(queryset, pk=pk) + proposal = ProposalBase.objects.get_subclass(pk=proposal.pk) + + if proposal.speaker.user != request.user: + return HttpResponseForbidden() + + if request.method == "POST": + proposal.cancelled = True + proposal.save() + # @@@ fire off email to submitter and other speakers + messages.success(request, "%s has been cancelled" % proposal.title) + return redirect("dashboard") + + return render(request, "proposals/proposal_cancel.html", { + "proposal": proposal, + }) + + +@login_required +def proposal_leave(request, pk): + queryset = ProposalBase.objects.select_related("speaker") + proposal = get_object_or_404(queryset, pk=pk) + proposal = ProposalBase.objects.get_subclass(pk=proposal.pk) + + try: + speaker = proposal.additional_speakers.get(user=request.user) + except ObjectDoesNotExist: + return HttpResponseForbidden() + if request.method == "POST": + proposal.additional_speakers.remove(speaker) + # @@@ fire off email to submitter and other speakers + messages.success(request, "You are no longer speaking on %s" % proposal.title) + return redirect("speaker_dashboard") + ctx = { + "proposal": proposal, + } + return render(request, "proposals/proposal_leave.html", ctx) + + +@login_required +def proposal_pending_join(request, pk): + proposal = get_object_or_404(ProposalBase, pk=pk) + speaking = get_object_or_404(AdditionalSpeaker, speaker=request.user.speaker_profile, proposalbase=proposal) + if speaking.status == AdditionalSpeaker.SPEAKING_STATUS_PENDING: + speaking.status = AdditionalSpeaker.SPEAKING_STATUS_ACCEPTED + speaking.save() + messages.success(request, "You have accepted the invitation to join %s" % proposal.title) + return redirect("dashboard") + else: + return redirect("dashboard") + + +@login_required +def proposal_pending_decline(request, pk): + proposal = get_object_or_404(ProposalBase, pk=pk) + speaking = get_object_or_404(AdditionalSpeaker, speaker=request.user.speaker_profile, proposalbase=proposal) + if speaking.status == AdditionalSpeaker.SPEAKING_STATUS_PENDING: + speaking.status = AdditionalSpeaker.SPEAKING_STATUS_DECLINED + speaking.save() + messages.success(request, "You have declined to speak on %s" % proposal.title) + return redirect("dashboard") + else: + return redirect("dashboard") + + +@login_required +def document_create(request, proposal_pk): + queryset = ProposalBase.objects.select_related("speaker") + proposal = get_object_or_404(queryset, pk=proposal_pk) + proposal = ProposalBase.objects.get_subclass(pk=proposal.pk) + + if request.method == "POST": + form = SupportingDocumentCreateForm(request.POST, request.FILES) + if form.is_valid(): + document = form.save(commit=False) + document.proposal = proposal + document.uploaded_by = request.user + document.save() + return redirect("proposal_detail", proposal.pk) + else: + form = SupportingDocumentCreateForm() + + return render(request, "proposals/document_create.html", { + "proposal": proposal, + "form": form, + }) + + +@login_required +def document_download(request, pk, *args): + document = get_object_or_404(SupportingDocument, pk=pk) + if settings.USE_X_ACCEL_REDIRECT: + response = HttpResponse() + response["X-Accel-Redirect"] = document.file.url + # delete content-type to allow Gondor to determine the filetype and + # we definitely don't want Django's crappy default :-) + del response["content-type"] + else: + response = static.serve(request, document.file.name, document_root=settings.MEDIA_ROOT) + return response + + +@login_required +def document_delete(request, pk): + document = get_object_or_404(SupportingDocument, pk=pk, uploaded_by=request.user) + proposal_pk = document.proposal.pk + + if request.method == "POST": + document.delete() + + return redirect("proposal_detail", proposal_pk) From 6d9c1c2e5f49b3ea2f1cf9489fa3f56db5227333 Mon Sep 17 00:00:00 2001 From: Luke Hatcher Date: Thu, 12 Jul 2012 00:39:15 -0400 Subject: [PATCH 057/751] temporarily include pycon module containing advanced sponsorship app --- pycon/__init__.py | 0 pycon/admin.py | 8 + pycon/forms.py | 84 ++++++ pycon/models.py | 68 +++++ pycon/sponsorship/__init__.py | 5 + pycon/sponsorship/admin.py | 69 +++++ pycon/sponsorship/forms.py | 72 +++++ pycon/sponsorship/management/__init__.py | 0 .../management/commands/__init__.py | 0 .../commands/create_sponsors_groups.py | 12 + .../commands/export_sponsors_data.py | 80 ++++++ .../commands/reset_sponsor_benefits.py | 38 +++ pycon/sponsorship/managers.py | 38 +++ pycon/sponsorship/models.py | 266 ++++++++++++++++++ pycon/sponsorship/templatetags/__init__.py | 0 .../templatetags/sponsorship_tags.py | 74 +++++ pycon/sponsorship/urls.py | 10 + pycon/sponsorship/views.py | 115 ++++++++ 18 files changed, 939 insertions(+) create mode 100644 pycon/__init__.py create mode 100644 pycon/admin.py create mode 100644 pycon/forms.py create mode 100644 pycon/models.py create mode 100644 pycon/sponsorship/__init__.py create mode 100644 pycon/sponsorship/admin.py create mode 100644 pycon/sponsorship/forms.py create mode 100644 pycon/sponsorship/management/__init__.py create mode 100644 pycon/sponsorship/management/commands/__init__.py create mode 100644 pycon/sponsorship/management/commands/create_sponsors_groups.py create mode 100644 pycon/sponsorship/management/commands/export_sponsors_data.py create mode 100644 pycon/sponsorship/management/commands/reset_sponsor_benefits.py create mode 100644 pycon/sponsorship/managers.py create mode 100644 pycon/sponsorship/models.py create mode 100644 pycon/sponsorship/templatetags/__init__.py create mode 100644 pycon/sponsorship/templatetags/sponsorship_tags.py create mode 100644 pycon/sponsorship/urls.py create mode 100644 pycon/sponsorship/views.py diff --git a/pycon/__init__.py b/pycon/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pycon/admin.py b/pycon/admin.py new file mode 100644 index 00000000..66933a2e --- /dev/null +++ b/pycon/admin.py @@ -0,0 +1,8 @@ +from django.contrib import admin + +from pycon.models import PyConProposalCategory, PyConTalkProposal, PyConTutorialProposal, PyConPosterProposal + +admin.site.register(PyConProposalCategory) +admin.site.register(PyConTalkProposal) +admin.site.register(PyConTutorialProposal) +admin.site.register(PyConPosterProposal) \ No newline at end of file diff --git a/pycon/forms.py b/pycon/forms.py new file mode 100644 index 00000000..74c1b0b3 --- /dev/null +++ b/pycon/forms.py @@ -0,0 +1,84 @@ +from django import forms + +from markitup.widgets import MarkItUpWidget + +from pycon.models import PyConProposalCategory, PyConTalkProposal, PyConTutorialProposal, PyConPosterProposal + + +class PyConProposalForm(forms.ModelForm): + + def __init__(self, *args, **kwargs): + super(PyConProposalForm, self).__init__(*args, **kwargs) + self.fields["category"] = forms.ModelChoiceField( + queryset = PyConProposalCategory.objects.order_by("name") + ) + + def clean_description(self): + value = self.cleaned_data["description"] + if len(value) > 400: + raise forms.ValidationError( + u"The description must be less than 400 characters" + ) + return value + + +class PyConTalkProposalForm(PyConProposalForm): + + class Meta: + model = PyConTalkProposal + fields = [ + "title", + "category", + "audience_level", + "extreme", + "duration", + "description", + "abstract", + "additional_notes", + "recording_release", + ] + widgets = { + "abstract": MarkItUpWidget(), + "additional_notes": MarkItUpWidget(), + } + + +class PyConTutorialProposalForm(PyConProposalForm): + + class Meta: + model = PyConTutorialProposal + fields = [ + "title", + "category", + "audience_level", + "description", + "abstract", + "additional_notes", + "recording_release", + + ] + widgets = { + "abstract": MarkItUpWidget(), + "additional_notes": MarkItUpWidget(), + } + + +class PyConPosterProposalForm(PyConProposalForm): + + class Meta: + model = PyConPosterProposal + fields = [ + "title", + "category", + "audience_level", + "description", + "abstract", + "additional_notes", + "recording_release", + + ] + widgets = { + "abstract": MarkItUpWidget(), + "additional_notes": MarkItUpWidget(), + } + diff --git a/pycon/models.py b/pycon/models.py new file mode 100644 index 00000000..9e2299ea --- /dev/null +++ b/pycon/models.py @@ -0,0 +1,68 @@ +from django.db import models + +from symposion.proposals.models import ProposalBase + + +class PyConProposalCategory(models.Model): + + name = models.CharField(max_length=100) + slug = models.SlugField() + + def __unicode__(self): + return self.name + + class Meta: + verbose_name = "PyCon proposal category" + verbose_name_plural = "PyCon proposal categories" + + +class PyConProposal(ProposalBase): + + AUDIENCE_LEVEL_NOVICE = 1 + AUDIENCE_LEVEL_EXPERIENCED = 2 + AUDIENCE_LEVEL_INTERMEDIATE = 3 + + AUDIENCE_LEVELS = [ + (AUDIENCE_LEVEL_NOVICE, "Novice"), + (AUDIENCE_LEVEL_INTERMEDIATE, "Intermediate"), + (AUDIENCE_LEVEL_EXPERIENCED, "Experienced"), + ] + + category = models.ForeignKey(PyConProposalCategory) + audience_level = models.IntegerField(choices=AUDIENCE_LEVELS) + + recording_release = models.BooleanField( + default=True, + help_text="By submitting your talk proposal, you agree to give permission to the Python Software Foundation to record, edit, and release audio and/or video of your presentation. If you do not agree to this, please uncheck this box. See PyCon 2013 Recording Release for details." + ) + + class Meta: + abstract = True + + +class PyConTalkProposal(PyConProposal): + + DURATION_CHOICES = [ + (0, "No preference"), + (1, "I prefer a 30 minute slot"), + (2, "I prefer a 45 minute slot"), + ] + + extreme = models.BooleanField( + default=False, + help_text="'Extreme' talks are advanced talks with little or no introductory material. See Extreme Talks for details." + ) + duration = models.IntegerField(choices=DURATION_CHOICES) + + class Meta: + verbose_name = "PyCon talk proposal" + + +class PyConTutorialProposal(PyConProposal): + class Meta: + verbose_name = "PyCon tutorial proposal" + + +class PyConPosterProposal(PyConProposal): + class Meta: + verbose_name = "PyCon poster proposal" diff --git a/pycon/sponsorship/__init__.py b/pycon/sponsorship/__init__.py new file mode 100644 index 00000000..3cb4bc97 --- /dev/null +++ b/pycon/sponsorship/__init__.py @@ -0,0 +1,5 @@ +SPONSOR_COORDINATORS = "sponsor-coordinators" + +AUTH_GROUPS = [ + SPONSOR_COORDINATORS +] diff --git a/pycon/sponsorship/admin.py b/pycon/sponsorship/admin.py new file mode 100644 index 00000000..22096c77 --- /dev/null +++ b/pycon/sponsorship/admin.py @@ -0,0 +1,69 @@ +from django.contrib import admin + +from pycon.sponsorship.models import SponsorLevel, Sponsor, Benefit, BenefitLevel, SponsorBenefit + + +class BenefitLevelInline(admin.TabularInline): + model = BenefitLevel + extra = 0 + + +class SponsorBenefitInline(admin.StackedInline): + model = SponsorBenefit + extra = 0 + fieldsets = [ + (None, { + "fields": [ + ("benefit", "active"), + ("max_words", "other_limits"), + "text", + "upload", + ] + }) + ] + + +class SponsorAdmin(admin.ModelAdmin): + + save_on_top = True + fieldsets = [ + (None, { + "fields": [ + ("name", "applicant"), + ("level", "active"), + "external_url", + "annotation", + ("contact_name", "contact_email") + ] + }), + ("Metadata", { + "fields": ["added"], + "classes": ["collapse"] + }) + ] + inlines = [SponsorBenefitInline] + + def get_form(self, *args, **kwargs): + # @@@ kinda ugly but using choices= on NullBooleanField is broken + form = super(SponsorAdmin, self).get_form(*args, **kwargs) + form.base_fields["active"].widget.choices = [ + (u"1", "unreviewed"), + (u"2", "approved"), + (u"3", "rejected") + ] + return form + + +class BenefitAdmin(admin.ModelAdmin): + + inlines = [BenefitLevelInline] + + +class SponsorLevelAdmin(admin.ModelAdmin): + + inlines = [BenefitLevelInline] + + +admin.site.register(SponsorLevel, SponsorLevelAdmin) +admin.site.register(Sponsor, SponsorAdmin) +admin.site.register(Benefit, BenefitAdmin) \ No newline at end of file diff --git a/pycon/sponsorship/forms.py b/pycon/sponsorship/forms.py new file mode 100644 index 00000000..70866ccf --- /dev/null +++ b/pycon/sponsorship/forms.py @@ -0,0 +1,72 @@ +from django import forms +from django.forms.models import inlineformset_factory, BaseInlineFormSet + +from django.contrib.admin.widgets import AdminFileWidget + +from pycon.sponsorship.models import Sponsor, SponsorBenefit + + +class SponsorApplicationForm(forms.ModelForm): + def __init__(self, *args, **kwargs): + self.user = kwargs.pop("user") + kwargs.update({ + "initial": { + "contact_name": self.user.get_full_name, + "contact_email": self.user.email, + } + }) + super(SponsorApplicationForm, self).__init__(*args, **kwargs) + + class Meta: + model = Sponsor + fields = ["name", "contact_name", "contact_email", "level"] + + def save(self, commit=True): + obj = super(SponsorApplicationForm, self).save(commit=False) + obj.applicant = self.user + if commit: + obj.save() + return obj + + +class SponsorDetailsForm(forms.ModelForm): + class Meta: + model = Sponsor + fields = [ + "name", + "external_url", + "contact_name", + "contact_email" + ] + + +class SponsorBenefitsInlineFormSet(BaseInlineFormSet): + + def _construct_form(self, i, **kwargs): + form = super(SponsorBenefitsInlineFormSet, self)._construct_form(i, **kwargs) + + # only include the relevant data fields for this benefit type + fields = form.instance.data_fields() + form.fields = dict((k, v) for (k, v) in form.fields.items() if k in fields + ["id"]) + + for field in fields: + # don't need a label, the form template will label it with the benefit name + form.fields[field].label = "" + + # provide word limit as help_text + if form.instance.benefit.type == "text" and form.instance.max_words: + form.fields[field].help_text = u"maximum %s words" % form.instance.max_words + + # use admin file widget that shows currently uploaded file + if field == "upload": + form.fields[field].widget = AdminFileWidget() + + return form + + +SponsorBenefitsFormSet = inlineformset_factory( + Sponsor, SponsorBenefit, + formset=SponsorBenefitsInlineFormSet, + can_delete=False, extra=0, + fields=["text", "upload"] +) diff --git a/pycon/sponsorship/management/__init__.py b/pycon/sponsorship/management/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pycon/sponsorship/management/commands/__init__.py b/pycon/sponsorship/management/commands/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pycon/sponsorship/management/commands/create_sponsors_groups.py b/pycon/sponsorship/management/commands/create_sponsors_groups.py new file mode 100644 index 00000000..2a389e6a --- /dev/null +++ b/pycon/sponsorship/management/commands/create_sponsors_groups.py @@ -0,0 +1,12 @@ +from django.core.management.base import BaseCommand + +from django.contrib.auth.models import Group + +from pycon.sponsorship import AUTH_GROUPS + + +class Command(BaseCommand): + + def handle(self, *args, **options): + for group in AUTH_GROUPS: + Group.objects.get_or_create(name=group) diff --git a/pycon/sponsorship/management/commands/export_sponsors_data.py b/pycon/sponsorship/management/commands/export_sponsors_data.py new file mode 100644 index 00000000..f7df698c --- /dev/null +++ b/pycon/sponsorship/management/commands/export_sponsors_data.py @@ -0,0 +1,80 @@ +import csv +import os +import shutil +import zipfile + +from contextlib import closing + +from django.core.management.base import BaseCommand, CommandError +from django.template.defaultfilters import slugify + +from pycon.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/pycon/sponsorship/management/commands/reset_sponsor_benefits.py b/pycon/sponsorship/management/commands/reset_sponsor_benefits.py new file mode 100644 index 00000000..cb2e833f --- /dev/null +++ b/pycon/sponsorship/management/commands/reset_sponsor_benefits.py @@ -0,0 +1,38 @@ +from django.core.management.base import BaseCommand + +from django.contrib.auth.models import Group + +from pycon.sponsorship.models import Sponsor, SponsorBenefit + + +class Command(BaseCommand): + + def handle(self, *args, **options): + for sponsor in Sponsor.objects.all(): + level = None + try: + level = sponsor.level + except SponsorLevel.DoesNotExist: + pass + if level: + for benefit_level in level.benefit_levels.all(): + # Create all needed benefits if they don't exist already + sponsor_benefit, created = SponsorBenefit.objects.get_or_create( + sponsor=sponsor, benefit=benefit_level.benefit) + + if created: + print "created", sponsor_benefit, "for", sponsor + + # and set to default limits for this level. + sponsor_benefit.max_words = benefit_level.max_words + sponsor_benefit.other_limits = benefit_level.other_limits + + # and set to active + sponsor_benefit.active = True + + # @@@ We don't call sponsor_benefit.clean here. This means + # that if the sponsorship level for a sponsor is adjusted + # downwards, an existing too-long text entry can remain, + # and won't raise a validation error until it's next + # edited. + sponsor_benefit.save() diff --git a/pycon/sponsorship/managers.py b/pycon/sponsorship/managers.py new file mode 100644 index 00000000..637c311b --- /dev/null +++ b/pycon/sponsorship/managers.py @@ -0,0 +1,38 @@ +from django.db import models + + +class SponsorManager(models.Manager): + + def active(self): + return self.get_query_set().filter(active=True).order_by("level") + + def with_weblogo(self): + queryset = self.raw(""" + SELECT DISTINCT + "sponsorship_sponsor"."id", + "sponsorship_sponsor"."applicant_id", + "sponsorship_sponsor"."name", + "sponsorship_sponsor"."external_url", + "sponsorship_sponsor"."annotation", + "sponsorship_sponsor"."contact_name", + "sponsorship_sponsor"."contact_email", + "sponsorship_sponsor"."level_id", + "sponsorship_sponsor"."added", + "sponsorship_sponsor"."active", + "sponsorship_sponsorlevel"."order" + FROM + "sponsorship_sponsor" + INNER JOIN + "sponsorship_sponsorbenefit" ON ("sponsorship_sponsor"."id" = "sponsorship_sponsorbenefit"."sponsor_id") + INNER JOIN + "sponsorship_benefit" ON ("sponsorship_sponsorbenefit"."benefit_id" = "sponsorship_benefit"."id") + LEFT OUTER JOIN + "sponsorship_sponsorlevel" ON ("sponsorship_sponsor"."level_id" = "sponsorship_sponsorlevel"."id") + WHERE ( + "sponsorship_sponsor"."active" = 't' AND + "sponsorship_benefit"."type" = 'weblogo' AND + "sponsorship_sponsorbenefit"."upload" != '' + ) + ORDER BY "sponsorship_sponsorlevel"."order" ASC, "sponsorship_sponsor"."added" ASC + """) + return queryset diff --git a/pycon/sponsorship/models.py b/pycon/sponsorship/models.py new file mode 100644 index 00000000..8320b049 --- /dev/null +++ b/pycon/sponsorship/models.py @@ -0,0 +1,266 @@ +import datetime + +from django.core.exceptions import ValidationError +from django.core.urlresolvers import reverse +from django.db import models +from django.db.models.signals import post_init, post_save +from django.utils.translation import ugettext_lazy as _ + +from django.contrib.auth.models import User + +from symposion.conference.models import Conference + +from pycon.sponsorship import SPONSOR_COORDINATORS +from pycon.sponsorship.managers import SponsorManager +# from symposion.utils.mail import send_email + + +class SponsorLevel(models.Model): + + conference = models.ForeignKey(Conference, verbose_name=_("conference")) + name = models.CharField(_("name"), max_length=100) + order = models.IntegerField(_("order"), default=0) + cost = models.PositiveIntegerField(_("cost")) + description = models.TextField(_("description"), blank=True, help_text=_("This is private.")) + + class Meta: + ordering = ["conference", "order"] + verbose_name = _("sponsor level") + verbose_name_plural = _("sponsor levels") + + def __unicode__(self): + return u"%s %s" % (self.conference, self.name) + + def sponsors(self): + return self.sponsor_set.filter(active=True).order_by("added") + + +class Sponsor(models.Model): + + applicant = models.ForeignKey(User, related_name="sponsorships", verbose_name=_("applicant"), null=True) + + name = models.CharField(_("Sponsor Name"), max_length=100) + external_url = models.URLField(_("external URL")) + annotation = models.TextField(_("annotation"), blank=True) + contact_name = models.CharField(_("Contact Name"), max_length=100) + contact_email = models.EmailField(_(u"Contact Email")) + level = models.ForeignKey(SponsorLevel, verbose_name=_("level")) + added = models.DateTimeField(_("added"), default=datetime.datetime.now) + active = models.BooleanField(_("active"), default=False) + + # Denormalization (this assumes only one logo) + sponsor_logo = models.ForeignKey("SponsorBenefit", related_name="+", null=True, blank=True, editable=False) + + objects = SponsorManager() + + def __unicode__(self): + return self.name + + class Meta: + verbose_name = _("sponsor") + verbose_name_plural = _("sponsors") + + def get_absolute_url(self): + if self.active: + return reverse("sponsor_detail", kwargs={"pk": self.pk}) + return reverse("sponsor_list") + + @property + def website_logo_url(self): + if not hasattr(self, "_website_logo_url"): + self._website_logo_url = None + benefits = self.sponsor_benefits.filter(benefit__type="weblogo", upload__isnull=False) + if benefits.exists(): + # @@@ smarter handling of multiple weblogo benefits? + # shouldn't happen + if benefits[0].upload: + self._website_logo_url = benefits[0].upload.url + return self._website_logo_url + + @property + def listing_text(self): + if not hasattr(self, "_listing_text"): + self._listing_text = None + benefits = self.sponsor_benefits.filter(benefit__id=7) + if benefits.count(): + self._listing_text = benefits[0].text + return self._listing_text + + @property + def joblisting_text(self): + if not hasattr(self, "_joblisting_text"): + self._joblisting_text = None + benefits = self.sponsor_benefits.filter(benefit__id=21) + if benefits.count(): + self._joblisting_text = benefits[0].text + return self._joblisting_text + + @property + def website_logo(self): + if self.sponsor_logo is None: + benefits = self.sponsor_benefits.filter(benefit__type="weblogo", upload__isnull=False)[:1] + if benefits.count(): + if benefits[0].upload: + self.sponsor_logo = benefits[0] + self.save() + return self.sponsor_logo.upload + + def reset_benefits(self): + """ + Reset all benefits for this sponsor to the defaults for their + sponsorship level. + """ + level = None + + try: + level = self.level + except SponsorLevel.DoesNotExist: + pass + + allowed_benefits = [] + if level: + for benefit_level in level.benefit_levels.all(): + # Create all needed benefits if they don't exist already + sponsor_benefit, created = SponsorBenefit.objects.get_or_create( + sponsor=self, benefit=benefit_level.benefit) + + # and set to default limits for this level. + sponsor_benefit.max_words = benefit_level.max_words + sponsor_benefit.other_limits = benefit_level.other_limits + + # and set to active + sponsor_benefit.active = True + + # @@@ We don't call sponsor_benefit.clean here. This means + # that if the sponsorship level for a sponsor is adjusted + # downwards, an existing too-long text entry can remain, + # and won't raise a validation error until it's next + # edited. + sponsor_benefit.save() + + allowed_benefits.append(sponsor_benefit.pk) + + # Any remaining sponsor benefits that don't normally belong to + # this level are set to inactive + self.sponsor_benefits.exclude(pk__in=allowed_benefits).update(active=False, max_words=None, other_limits="") + + # @@@ should this just be done centrally? + def send_coordinator_emails(self): + for user in User.objects.filter(groups__name=SPONSOR_COORDINATORS): + send_email( + [user.email], "sponsor_signup", + context = {"sponsor": self} + ) + + +def _store_initial_level(sender, instance, **kwargs): + if instance: + instance._initial_level_id = instance.level_id +post_init.connect(_store_initial_level, sender=Sponsor) + + +def _check_level_change(sender, instance, created, **kwargs): + if instance and (created or instance.level_id != instance._initial_level_id): + instance.reset_benefits() +post_save.connect(_check_level_change, sender=Sponsor) + + +def _send_sponsor_notification_emails(sender, instance, created, **kwargs): + if instance and created: + instance.send_coordinator_emails() +post_save.connect(_send_sponsor_notification_emails, sender=Sponsor) + + +class Benefit(models.Model): + + name = models.CharField(_("name"), max_length=100) + description = models.TextField(_("description"), blank=True) + type = models.CharField( + _("type"), + choices=[ + ("text", "Text"), + ("file", "File"), + ("weblogo", "Web Logo"), + ("simple", "Simple") + ], + max_length=10, + default="simple" + ) + + def __unicode__(self): + return self.name + + +class BenefitLevel(models.Model): + + benefit = models.ForeignKey( + Benefit, + related_name="benefit_levels", + verbose_name=_("benefit") + ) + level = models.ForeignKey( + SponsorLevel, + related_name="benefit_levels", + verbose_name=_("level") + ) + max_words = models.PositiveIntegerField(_("max words"), blank=True, null=True) + other_limits = models.CharField(_("other limits"), max_length=200, blank=True) + + class Meta: + ordering = ["level"] + + def __unicode__(self): + return u"%s - %s" % (self.level, self.benefit) + + +class SponsorBenefit(models.Model): + + sponsor = models.ForeignKey( + Sponsor, + related_name="sponsor_benefits", + verbose_name=_("sponsor") + ) + benefit = models.ForeignKey(Benefit, + related_name="sponsor_benefits", + verbose_name=_("benefit") + ) + active = models.BooleanField(default=True) + + # Limits: will initially be set to defaults from corresponding BenefitLevel + max_words = models.PositiveIntegerField(_("max words"), blank=True, null=True) + other_limits = models.CharField(_("other limits"), max_length=200, blank=True) + + # Data: zero or one of these fields will be used, depending on the + # type of the Benefit (text, file, or simple) + text = models.TextField(_("text"), blank=True) + upload = models.FileField(_("file"), blank=True, upload_to="sponsor_files") + + class Meta: + ordering = ['-active'] + + def __unicode__(self): + return u"%s - %s" % (self.sponsor, self.benefit) + + def clean(self): + if self.max_words and len(self.text.split()) > self.max_words: + raise ValidationError("Sponsorship level only allows for %s words." % self.max_words) + + def data_fields(self): + """ + Return list of data field names which should be editable for + this ``SponsorBenefit``, depending on its ``Benefit`` type. + """ + if self.benefit.type == "file" or self.benefit.type == "weblogo": + return ["upload"] + elif self.benefit.type == "text": + return ["text"] + return [] + + +def _denorm_weblogo(sender, instance, created, **kwargs): + if instance: + if instance.benefit.type == "weblogo" and instance.upload: + sponsor = instance.sponsor + sponsor.sponsor_logo = instance + sponsor.save() +post_save.connect(_denorm_weblogo, sender=SponsorBenefit) \ No newline at end of file diff --git a/pycon/sponsorship/templatetags/__init__.py b/pycon/sponsorship/templatetags/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pycon/sponsorship/templatetags/sponsorship_tags.py b/pycon/sponsorship/templatetags/sponsorship_tags.py new file mode 100644 index 00000000..9a4ba982 --- /dev/null +++ b/pycon/sponsorship/templatetags/sponsorship_tags.py @@ -0,0 +1,74 @@ +from django import template + +from symposion.conference.models import current_conference +from pycon.sponsorship.models import Sponsor, SponsorLevel + + +register = template.Library() + + +class SponsorsNode(template.Node): + + @classmethod + def handle_token(cls, parser, token): + bits = token.split_contents() + if len(bits) == 3 and bits[1] == "as": + return cls(bits[2]) + elif len(bits) == 4 and bits[2] == "as": + return cls(bits[3], bits[1]) + else: + raise template.TemplateSyntaxError("%r takes 'as var' or 'level as var'" % bits[0]) + + def __init__(self, context_var, level=None): + if level: + self.level = template.Variable(level) + else: + self.level = None + self.context_var = context_var + + def render(self, context): + conference = current_conference() + if self.level: + level = self.level.resolve(context) + queryset = Sponsor.objects.filter(level__conference = conference, level__name__iexact = level, active = True).order_by("added") + else: + queryset = Sponsor.objects.filter(level__conference = conference, active = True).order_by("level__order", "added") + context[self.context_var] = queryset + return u"" + + +class SponsorLevelNode(template.Node): + + @classmethod + def handle_token(cls, parser, token): + bits = token.split_contents() + if len(bits) == 3 and bits[1] == "as": + return cls(bits[2]) + else: + raise template.TemplateSyntaxError("%r takes 'as var'" % bits[0]) + + def __init__(self, context_var): + self.context_var = context_var + + def render(self, context): + conference = current_conference() + context[self.context_var] = SponsorLevel.objects.filter(conference=conference) + return u"" + + +@register.tag +def sponsors(parser, token): + """ + {% sponsors as all_sponsors %} + or + {% sponsors "gold" as gold_sponsors %} + """ + return SponsorsNode.handle_token(parser, token) + + +@register.tag +def sponsor_levels(parser, token): + """ + {% sponsor_levels as levels %} + """ + return SponsorLevelNode.handle_token(parser, token) diff --git a/pycon/sponsorship/urls.py b/pycon/sponsorship/urls.py new file mode 100644 index 00000000..7776d200 --- /dev/null +++ b/pycon/sponsorship/urls.py @@ -0,0 +1,10 @@ +from django.conf.urls.defaults import patterns, url +from django.views.generic.simple import direct_to_template + + +urlpatterns = patterns("pycon.sponsorship.views", + url(r"^$", direct_to_template, {"template": "sponsorship/list.html"}, name="sponsor_list"), + # url(r"^jobs/$", direct_to_template, {"template": "sponsors/jobs.html"}, name="sponsor_jobs"), + url(r"^apply/$", "sponsor_apply", name="sponsor_apply"), + url(r"^(?P\d+)/$", "sponsor_detail", name="sponsor_detail"), +) diff --git a/pycon/sponsorship/views.py b/pycon/sponsorship/views.py new file mode 100644 index 00000000..12896b22 --- /dev/null +++ b/pycon/sponsorship/views.py @@ -0,0 +1,115 @@ +import itertools + +from functools import wraps + +from django.http import HttpResponse +from django.shortcuts import render_to_response, redirect, get_object_or_404 +from django.template import RequestContext + +from django.contrib import messages +from django.contrib.admin.views.decorators import staff_member_required +from django.contrib.auth.decorators import login_required + +from pycon.sponsorship.forms import SponsorApplicationForm, SponsorDetailsForm, SponsorBenefitsFormSet +from pycon.sponsorship.models import Sponsor, SponsorBenefit + + +@login_required +def sponsor_apply(request): + if request.method == "POST": + form = SponsorApplicationForm(request.POST, user=request.user) + if form.is_valid(): + form.save() + return redirect("dashboard") + else: + form = SponsorApplicationForm(user=request.user) + + return render_to_response("sponsorship/apply.html", { + "form": form, + }, context_instance=RequestContext(request)) + + +@login_required +def sponsor_detail(request, pk): + sponsor = get_object_or_404(Sponsor, pk=pk) + + if not sponsor.active or sponsor.applicant != request.user: + return redirect("sponsor_list") + + formset_kwargs = { + "instance": sponsor, + "queryset": SponsorBenefit.objects.filter(active=True) + } + + if request.method == "POST": + + form = SponsorDetailsForm(request.POST, instance=sponsor) + formset = SponsorBenefitsFormSet(request.POST, request.FILES, **formset_kwargs) + + if form.is_valid() and formset.is_valid(): + form.save() + formset.save() + + messages.success(request, "Your sponsorship application has been submitted!") + + return redirect(request.path) + else: + form = SponsorDetailsForm(instance=sponsor) + formset = SponsorBenefitsFormSet(**formset_kwargs) + + return render_to_response("sponsorship/detail.html", { + "sponsor": sponsor, + "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") From 1759cf569e7ffe09d0b6bc5fb12a45e35992515d Mon Sep 17 00:00:00 2001 From: Luke Hatcher Date: Thu, 12 Jul 2012 00:39:29 -0400 Subject: [PATCH 058/751] pycon conference fixture --- fixtures/conference.json | 67 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 fixtures/conference.json diff --git a/fixtures/conference.json b/fixtures/conference.json new file mode 100644 index 00000000..4810ff23 --- /dev/null +++ b/fixtures/conference.json @@ -0,0 +1,67 @@ +[ + { + "pk": 1, + "model": "conference.conference", + "fields": { + "timezone": "US/Pacific", + "start_date": "2013-03-13", + "end_date": "2013-03-21", + "title": "PyCon 2013" + } + }, + { + "pk": 1, + "model": "conference.section", + "fields": { + "conference": 1, + "start_date": "2013-03-13", + "name": "Tutorials", + "end_date": "2013-03-14", + "slug": "tutorials" + } + }, + { + "pk": 2, + "model": "conference.section", + "fields": { + "conference": 1, + "start_date": "2013-03-15", + "name": "Talks", + "end_date": "2013-03-18", + "slug": "talks" + } + }, + { + "pk": 3, + "model": "conference.section", + "fields": { + "conference": 1, + "start_date": null, + "name": "Posters", + "end_date": null, + "slug": "posters" + } + }, + { + "pk": 4, + "model": "conference.section", + "fields": { + "conference": 1, + "start_date": "2013-03-19", + "name": "Sprints", + "end_date": "2013-03-21", + "slug": "sprints" + } + }, + { + "pk": 5, + "model": "conference.section", + "fields": { + "conference": 1, + "start_date": null, + "name": "Open Spaces", + "end_date": null, + "slug": "open-spaces" + } + } +] \ No newline at end of file From 1a8c2d342d136504f418948ceeb80403b2e828b1 Mon Sep 17 00:00:00 2001 From: Luke Hatcher Date: Thu, 12 Jul 2012 01:06:12 -0400 Subject: [PATCH 059/751] add proposals and speakers to installed apps --- symposion_project/settings.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/symposion_project/settings.py b/symposion_project/settings.py index 60ce8b58..75c703f3 100644 --- a/symposion_project/settings.py +++ b/symposion_project/settings.py @@ -166,6 +166,8 @@ INSTALLED_APPS = [ "symposion.conference", "symposion.cms", "symposion.boxes", + "symposion.proposals", + "symposion.speakers", ] FIXTURE_DIRS = [ From 1243e2a8abbca5710d886314be216192c56a3163 Mon Sep 17 00:00:00 2001 From: Luke Hatcher Date: Thu, 12 Jul 2012 01:06:25 -0400 Subject: [PATCH 060/751] add django-model-utils to project base --- requirements/base.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements/base.txt b/requirements/base.txt index fb407993..8ab68abd 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -19,6 +19,7 @@ pytz==2011n django-openid==0.3a1 python-openid==2.2.5 django_compressor==1.2a1 +django-model-utils==1.1.0 -e git+git://github.com/pinax/pinax-theme-bootstrap-account.git@8fc34ba4309fc1edd12e5836cc1398c3f9597e6d#egg=pinax-theme-bootstrap-account -e git+git://github.com/pinax/django-user-accounts.git@fd08c676ae71d0b9d7353ddee123deb751d6ee15#egg=django-user-accounts From abc8914cc6dcdee3c01476b7016085fe193a0ae1 Mon Sep 17 00:00:00 2001 From: Luke Hatcher Date: Thu, 12 Jul 2012 01:07:31 -0400 Subject: [PATCH 061/751] modernize wsgi entry point --- symposion_project/wsgi.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/symposion_project/wsgi.py b/symposion_project/wsgi.py index 726e2a7b..d745282b 100644 --- a/symposion_project/wsgi.py +++ b/symposion_project/wsgi.py @@ -1,11 +1,6 @@ -from django.core.handlers.wsgi import WSGIHandler +import os -import pinax.env +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "symposion_project.settings") - -# setup the environment for Django and Pinax -pinax.env.setup_environ(__file__) - - -# set application for WSGI processing -application = WSGIHandler() \ No newline at end of file +from django.core.wsgi import get_wsgi_application +application = get_wsgi_application() From db95cb61809939e6fb198e1a3959de8aed45b8af Mon Sep 17 00:00:00 2001 From: Luke Hatcher Date: Thu, 12 Jul 2012 01:20:55 -0400 Subject: [PATCH 062/751] add utils --- symposion/utils/__init__.py | 0 symposion/utils/mail.py | 30 ++++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 symposion/utils/__init__.py create mode 100644 symposion/utils/mail.py diff --git a/symposion/utils/__init__.py b/symposion/utils/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/symposion/utils/mail.py b/symposion/utils/mail.py new file mode 100644 index 00000000..dc856871 --- /dev/null +++ b/symposion/utils/mail.py @@ -0,0 +1,30 @@ +from django.conf import settings +from django.core.mail import EmailMultiAlternatives +from django.template.loader import render_to_string +from django.utils.html import strip_tags + +from django.contrib.sites.models import Site + + +def send_email(to, kind, **kwargs): + + current_site = Site.objects.get_current() + + ctx = { + "current_site": current_site, + "STATIC_URL": settings.STATIC_URL, + } + ctx.update(kwargs.get("context", {})) + subject = "[%s] %s" % ( + current_site.name, + render_to_string("emails/%s/subject.txt" % kind, ctx).strip() + ) + + message_html = render_to_string("emails/%s/message.html" % kind, ctx) + message_plaintext = strip_tags(message_html) + + from_email = settings.DEFAULT_FROM_EMAIL + + email = EmailMultiAlternatives(subject, message_plaintext, from_email, to) + email.attach_alternative(message_html, "text/html") + email.send() From 674a5303ad3634a93aa4aeea405fcead08cad3d5 Mon Sep 17 00:00:00 2001 From: Luke Hatcher Date: Thu, 12 Jul 2012 01:21:02 -0400 Subject: [PATCH 063/751] update urls --- symposion_project/urls.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/symposion_project/urls.py b/symposion_project/urls.py index 4327ef0c..20ea3201 100644 --- a/symposion_project/urls.py +++ b/symposion_project/urls.py @@ -17,8 +17,9 @@ urlpatterns = patterns("", }, name="home"), url(r"^admin/", include(admin.site.urls)), url(r"^account/", include("account.urls")), - # url(r"^openid/", include(PinaxConsumer().urls)), + url(r"^speaker/", include("symposion.speakers.urls")), + url(r"^proposals/", include("symposion.proposals.urls")), url(r"^boxes/", include("symposion.boxes.urls")), url(r"^markitup/", include("markitup.urls")), From 33ee37197516a58ea1d02246ff5a849a073327ed Mon Sep 17 00:00:00 2001 From: Luke Hatcher Date: Thu, 12 Jul 2012 01:21:14 -0400 Subject: [PATCH 064/751] fix package root/project root --- symposion_project/settings.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/symposion_project/settings.py b/symposion_project/settings.py index 75c703f3..04a1315f 100644 --- a/symposion_project/settings.py +++ b/symposion_project/settings.py @@ -4,7 +4,8 @@ import os.path import posixpath -PROJECT_ROOT = os.path.abspath(os.path.dirname(__file__)) +PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir)) +PACKAGE_ROOT = os.path.abspath(os.path.dirname(__file__)) DEBUG = True TEMPLATE_DEBUG = DEBUG @@ -56,7 +57,7 @@ USE_I18N = True # Absolute path to the directory that holds media. # Example: "/home/media/media.lawrence.com/" -MEDIA_ROOT = os.path.join(PROJECT_ROOT, "site_media", "media") +MEDIA_ROOT = os.path.join(PACKAGE_ROOT, "site_media", "media") # 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). @@ -65,7 +66,7 @@ MEDIA_URL = "/site_media/media/" # Absolute path to the directory that holds static files like app media. # Example: "/home/media/media.lawrence.com/apps/" -STATIC_ROOT = os.path.join(PROJECT_ROOT, "site_media", "static") +STATIC_ROOT = os.path.join(PACKAGE_ROOT, "site_media", "static") # URL that handles the static files like app media. # Example: "http://media.lawrence.com" @@ -73,7 +74,7 @@ STATIC_URL = "/site_media/static/" # Additional directories which hold static files STATICFILES_DIRS = [ - os.path.join(PROJECT_ROOT, "static"), + os.path.join(PACKAGE_ROOT, "static"), ] STATICFILES_FINDERS = [ @@ -114,7 +115,7 @@ MIDDLEWARE_CLASSES = [ ROOT_URLCONF = "symposion_project.urls" TEMPLATE_DIRS = [ - os.path.join(PROJECT_ROOT, "templates"), + os.path.join(PACKAGE_ROOT, "templates"), ] TEMPLATE_CONTEXT_PROCESSORS = [ From df1f1dd2f4374303085c83483e1e01a64ee1c832 Mon Sep 17 00:00:00 2001 From: Luke Hatcher Date: Thu, 12 Jul 2012 01:31:29 -0400 Subject: [PATCH 065/751] update urls --- symposion_project/urls.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/symposion_project/urls.py b/symposion_project/urls.py index 20ea3201..a552630f 100644 --- a/symposion_project/urls.py +++ b/symposion_project/urls.py @@ -7,6 +7,8 @@ from django.views.generic.simple import direct_to_template from django.contrib import admin admin.autodiscover() +import symposion.views + # from pinax.apps.account.openid_consumer import PinaxConsumer WIKI_SLUG = r"(([\w-]{2,})(/[\w-]{2,})*)" @@ -18,6 +20,7 @@ urlpatterns = patterns("", url(r"^admin/", include(admin.site.urls)), url(r"^account/", include("account.urls")), + url(r"^dashboard/", symposion.views.dashboard, name="dashboard"), url(r"^speaker/", include("symposion.speakers.urls")), url(r"^proposals/", include("symposion.proposals.urls")), url(r"^boxes/", include("symposion.boxes.urls")), From 0426d8d877cb756f146a975078dd0efd9b2c31ce Mon Sep 17 00:00:00 2001 From: Luke Hatcher Date: Thu, 12 Jul 2012 01:31:41 -0400 Subject: [PATCH 066/751] speaker templates --- .../templates/speakers/speaker_create.html | 23 +++++++++++++++++++ .../templates/speakers/speaker_edit.html | 23 +++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 symposion_project/templates/speakers/speaker_create.html create mode 100644 symposion_project/templates/speakers/speaker_edit.html diff --git a/symposion_project/templates/speakers/speaker_create.html b/symposion_project/templates/speakers/speaker_create.html new file mode 100644 index 00000000..377d601c --- /dev/null +++ b/symposion_project/templates/speakers/speaker_create.html @@ -0,0 +1,23 @@ +{% extends "auth_base.html" %} + +{% load bootstrap_tags %} +{% load i18n %} +{% load boxes_tags %} + +{% block page_title %}{% trans "Create Speaker Profile" %}{% endblock %} + +{% block body %} + {% box "speaker-profile" %} + +
    + {% csrf_token %} + {% trans "Create Speaker Profile" %} +
    + {{ form|as_bootstrap }} +
    +
    + + Cancel +
    +
    +{% endblock %} diff --git a/symposion_project/templates/speakers/speaker_edit.html b/symposion_project/templates/speakers/speaker_edit.html new file mode 100644 index 00000000..d5ee120f --- /dev/null +++ b/symposion_project/templates/speakers/speaker_edit.html @@ -0,0 +1,23 @@ +{% extends "auth_base.html" %} + +{% load bootstrap_tags %} +{% load i18n %} +{% load boxes_tags %} + +{% block page_title %}{% trans "Edit Speaker Profile" %}{% endblock %} + +{% block body %} + {% box "speaker-profile" %} + +
    + {% csrf_token %} + {% trans "Edit Speaker Profile" %} +
    + {{ form|as_bootstrap }} +
    +
    + + Cancel +
    +
    +{% endblock %} From 3ca56663a470ad7bbd1488f975af6e7d496a3338 Mon Sep 17 00:00:00 2001 From: Luke Hatcher Date: Thu, 12 Jul 2012 01:31:51 -0400 Subject: [PATCH 067/751] proposals templates --- .../proposals/_pending_proposal_row.html | 38 ++++++++++ .../templates/proposals/_proposal_row.html | 32 ++++++++ .../templates/proposals/document_create.html | 17 +++++ .../templates/proposals/proposal_cancel.html | 16 ++++ .../templates/proposals/proposal_detail.html | 76 +++++++++++++++++++ .../templates/proposals/proposal_edit.html | 23 ++++++ .../proposals/proposal_speaker_manage.html | 37 +++++++++ .../templates/proposals/proposal_submit.html | 22 ++++++ .../proposals/proposal_submit_kind.html | 27 +++++++ 9 files changed, 288 insertions(+) create mode 100644 symposion_project/templates/proposals/_pending_proposal_row.html create mode 100644 symposion_project/templates/proposals/_proposal_row.html create mode 100644 symposion_project/templates/proposals/document_create.html create mode 100644 symposion_project/templates/proposals/proposal_cancel.html create mode 100644 symposion_project/templates/proposals/proposal_detail.html create mode 100644 symposion_project/templates/proposals/proposal_edit.html create mode 100644 symposion_project/templates/proposals/proposal_speaker_manage.html create mode 100644 symposion_project/templates/proposals/proposal_submit.html create mode 100644 symposion_project/templates/proposals/proposal_submit_kind.html diff --git a/symposion_project/templates/proposals/_pending_proposal_row.html b/symposion_project/templates/proposals/_pending_proposal_row.html new file mode 100644 index 00000000..5214d1aa --- /dev/null +++ b/symposion_project/templates/proposals/_pending_proposal_row.html @@ -0,0 +1,38 @@ + + + {{ proposal.title }} + + + {{ proposal.kind.name }} + + + {% if proposal.cancelled %} + Cancelled + {% else %} + {% if request.user == proposal.speaker.user %} + {% if proposal.result.accepted %} + Accepted + {% else %} + Submitted + {% endif %} + {% else %} + Invited + {% endif %} + {% endif %} + + + + {% if not proposal.cancelled %} + + {% endif %} + + diff --git a/symposion_project/templates/proposals/_proposal_row.html b/symposion_project/templates/proposals/_proposal_row.html new file mode 100644 index 00000000..c214e521 --- /dev/null +++ b/symposion_project/templates/proposals/_proposal_row.html @@ -0,0 +1,32 @@ + + + {{ proposal.title }} + + + {{ proposal.kind.name }} + + + {% if proposal.cancelled %} + Cancelled + {% else %} + {% if request.user == proposal.speaker.user %} + {% if proposal.result.accepted %} + Accepted + {% else %} + Submitted + {% endif %} + {% else %} + Associated + {% endif %} + {% endif %} + + + + {% if not proposal.cancelled %} + {% if request.user == proposal.speaker.user and proposal.can_edit %} + Edit + Manage Additional Speakers + {% endif %} + {% endif %} + + diff --git a/symposion_project/templates/proposals/document_create.html b/symposion_project/templates/proposals/document_create.html new file mode 100644 index 00000000..37ca0566 --- /dev/null +++ b/symposion_project/templates/proposals/document_create.html @@ -0,0 +1,17 @@ +{% extends "auth_base.html" %} + +{% load bootstrap_tags %} + +{% block page_title %}Upload Document to '{{ proposal.title }}'{% endblock %} + +{% block body %} +
    + {% csrf_token %} +
    + {{ form|as_bootstrap }} +
    +
    + +
    +
    +{% endblock %} diff --git a/symposion_project/templates/proposals/proposal_cancel.html b/symposion_project/templates/proposals/proposal_cancel.html new file mode 100644 index 00000000..ebe3915c --- /dev/null +++ b/symposion_project/templates/proposals/proposal_cancel.html @@ -0,0 +1,16 @@ +{% extends "site_base.html" %} + +{% load bootstrap_tags %} + +{% block head_title %}Cancel Proposal{% endblock %} + +{% block body %} +

    Cancel: {{ proposal.title }}

    + +
    + {% csrf_token %} +

    Are you sure you want to cancel {{ proposal.title }}?

    + + No, keep it for now +
    +{% endblock %} \ No newline at end of file diff --git a/symposion_project/templates/proposals/proposal_detail.html b/symposion_project/templates/proposals/proposal_detail.html new file mode 100644 index 00000000..48e8396f --- /dev/null +++ b/symposion_project/templates/proposals/proposal_detail.html @@ -0,0 +1,76 @@ +{% extends "site_base.html" %} + +{% block head_title %}{{ proposal.title }}{% endblock %} + +{% block body %} +

    {{ proposal.title }}

    + +

    + {% if not proposal.cancelled %} + {% if request.user == proposal.speaker.user %} + Edit + | Cancel Talk + {% else %} + Leave + {% endif %} + {% else %} + Cancelled + {% endif %} +

    + +
    + {{ proposal.description }} +
    + +

    Type: {{ proposal.kind.name }}

    + +
    + {{ proposal.abstract|safe }} +
    + +

    Audience level: {{ proposal.get_audience_level_display }}

    + +

    Submitting speaker: {{ proposal.speaker }}

    + + {% if proposal.additional_speakers.all %} +

    Additional speakers:

    +
      + {% for speaker in proposal.additional_speakers.all %} + {% if speaker.user %} +
    • {{ speaker.name }} — {{ speaker.email }}
    • + {% else %} +
    • {{ speaker.email }} — pending invitation
    • + {% endif %} + {% endfor %} +
    + {% endif %} + + {% if request.user == proposal.speaker.user %} +

    Additional Notes:

    + +

    {{ proposal.additional_notes }}

    + {% endif %} + + {% if request.user == proposal.speaker.user %} +

    Supporting Documents

    + + {% if proposal.supporting_documents.exists %} + + {% for document in proposal.supporting_documents.all %} + + + + + {% endfor %} +
    {{ document.description }} +
    + {% csrf_token %} + +
    +
    + {% else %} +

    No supporting documents attached to this proposal.

    + {% endif %} + add document + {% endif %} +{% endblock %} diff --git a/symposion_project/templates/proposals/proposal_edit.html b/symposion_project/templates/proposals/proposal_edit.html new file mode 100644 index 00000000..f5b6ef06 --- /dev/null +++ b/symposion_project/templates/proposals/proposal_edit.html @@ -0,0 +1,23 @@ +{% extends "site_base.html" %} + +{% load bootstrap_tags %} +{% load markitup_tags %} + +{% block head_title %}Editing {{ proposal.title }}{% endblock %} + +{% block body %} +

    Edit: {{ proposal.title }}

    + +

    Manage speakers

    + +
    + {% csrf_token %} +
    + {{ form|as_bootstrap }} +
    +
    + + Cancel +
    +
    +{% endblock %} diff --git a/symposion_project/templates/proposals/proposal_speaker_manage.html b/symposion_project/templates/proposals/proposal_speaker_manage.html new file mode 100644 index 00000000..2cae7fee --- /dev/null +++ b/symposion_project/templates/proposals/proposal_speaker_manage.html @@ -0,0 +1,37 @@ +{% extends "site_base.html" %} + +{% load bootstrap_tags %} + +{% block body %} +

    Proposal: {{ proposal.title }}

    + +

    Edit proposal

    + +

    Current Speakers

    + + {% for speaker in speakers %} + {% if speaker.user %} +

    {{ speaker.name }} — {{ speaker.email }}

    + {% else %} +

    {{ speaker.email }} — pending invitation

    + {% endif %} + {% endfor %} + +

    Add another speaker

    + +
    + {% csrf_token %} + {{ add_speaker_form|as_bootstrap }} +
    + +
    +
    +{% endblock %} + +{% block extra_body %} + +{% endblock %} \ No newline at end of file diff --git a/symposion_project/templates/proposals/proposal_submit.html b/symposion_project/templates/proposals/proposal_submit.html new file mode 100644 index 00000000..7978a08b --- /dev/null +++ b/symposion_project/templates/proposals/proposal_submit.html @@ -0,0 +1,22 @@ +{% extends "auth_base.html" %} + +{% load boxes_tags %} +{% load i18n %} + +{% block page_title %}{% trans "Submit A Proposal" %}{% endblock %} + +{% block body %} + {% box "proposal_submit" %} + + {% if kinds %} +

    Select what kind of proposal you'd like to submit:

    + +
      + {% for kind in kinds %} +
    • {{ kind }}
    • + {% endfor %} +
    + {% else %} +

    Proposals are not currently open for submission.

    + {% endif %} +{% endblock %} diff --git a/symposion_project/templates/proposals/proposal_submit_kind.html b/symposion_project/templates/proposals/proposal_submit_kind.html new file mode 100644 index 00000000..443eb01d --- /dev/null +++ b/symposion_project/templates/proposals/proposal_submit_kind.html @@ -0,0 +1,27 @@ +{% extends "auth_base.html" %} + +{% load bootstrap_tags %} +{% load boxes_tags %} +{% load markitup_tags %} +{% load i18n %} + +{% block page_title %}Submit a Proposal for a {{ kind.name }}{% endblock %} + +{% block body %} + {% box "example_proposal" %} + +
    + {% csrf_token %} +
    + {{ form|as_bootstrap }} +
    +
    +

    + You will be able to edit your proposal after it has been submitted. The program committee may ask questions, provide feedback, and even suggest changes to your proposal as part of the review processes. +

    + + + +
    +
    +{% endblock %} From d31953b5445033619383e8326a8c1d3704e4f392 Mon Sep 17 00:00:00 2001 From: Luke Hatcher Date: Thu, 12 Jul 2012 01:32:07 -0400 Subject: [PATCH 068/751] bring over forms and views form pycon for now --- symposion/forms.py | 30 ++++++++++++++++++++++++++++ symposion/views.py | 49 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 symposion/forms.py create mode 100644 symposion/views.py diff --git a/symposion/forms.py b/symposion/forms.py new file mode 100644 index 00000000..0a623606 --- /dev/null +++ b/symposion/forms.py @@ -0,0 +1,30 @@ +from django import forms + +import account.forms + + +class SignupForm(account.forms.SignupForm): + + first_name = forms.CharField() + last_name = forms.CharField() + email_confirm = forms.EmailField(label="Confirm Email") + + def __init__(self, *args, **kwargs): + super(SignupForm, self).__init__(*args, **kwargs) + del self.fields["username"] + self.fields.keyOrder = [ + "email", + "email_confirm", + "first_name", + "last_name", + "password", + "password_confirm" + ] + + def clean_email_confirm(self): + email = self.cleaned_data.get("email") + email_confirm = self.cleaned_data["email_confirm"] + if email: + if email != email_confirm: + raise forms.ValidationError("Email address must match previously typed email address") + return email_confirm diff --git a/symposion/views.py b/symposion/views.py new file mode 100644 index 00000000..1a34bdb6 --- /dev/null +++ b/symposion/views.py @@ -0,0 +1,49 @@ +import hashlib +import random + +from django.shortcuts import render, redirect + +from django.contrib.auth.models import User +from django.contrib.auth.decorators import login_required + +import account.views + +import symposion.forms + + +class SignupView(account.views.SignupView): + + form_class = symposion.forms.SignupForm + + def create_user(self, form, commit=True): + user_kwargs = { + "first_name": form.cleaned_data["first_name"], + "last_name": form.cleaned_data["last_name"] + } + return super(SignupView, self).create_user(form, commit=commit, **user_kwargs) + + def generate_username(self, form): + def random_username(): + h = hashlib.sha1(form.cleaned_data["email"]).hexdigest()[:25] + # don't ask + n = random.randint(1, (10 ** (5 - 1)) - 1) + return "%s%d" % (h, n) + while True: + try: + username = random_username() + User.objects.get(username=username) + except User.DoesNotExist: + break + return username + + +class LoginView(account.views.LoginView): + + form_class = account.forms.LoginEmailForm + + +@login_required +def dashboard(request): + if request.session.get("pending-token"): + return redirect("speaker_create_token", request.session["pending-token"]) + return render(request, "dashboard.html") From 21ad87024dcab9419690525f8164bf494e9641a0 Mon Sep 17 00:00:00 2001 From: Luke Hatcher Date: Thu, 12 Jul 2012 01:32:16 -0400 Subject: [PATCH 069/751] add dashboard template --- symposion_project/templates/dashboard.html | 165 +++++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 symposion_project/templates/dashboard.html diff --git a/symposion_project/templates/dashboard.html b/symposion_project/templates/dashboard.html new file mode 100644 index 00000000..1639e260 --- /dev/null +++ b/symposion_project/templates/dashboard.html @@ -0,0 +1,165 @@ +{% extends "site_base.html" %} + +{% load i18n %} +{% load proposal_tags %} + +{% block head_title %}Dashboard{% endblock %} + +{% block body_class %}auth{% endblock %} + +{% block body_outer %} +
    +
    +
    +

    {% trans "Dashboard" %}

    +

    + This is your dashboard where you can manage all aspects of your + involvement in the conference, from talk proposals to sponsorship. +

    + {% block breadcrumbs %}{% endblock %} +
    + +
    +
    +

    Speaking

    +
    +
    + {% if user.speaker_profile %} + + +
    +

    Your Proposals

    + {% if user.speaker_profile.proposals.exists %} + + + + + + + + {% for proposal in user.speaker_profile.proposals.all %} + {% include "proposals/_proposal_row.html" %} + {% endfor %} +
    TitleSession typeStatusActions
    + {% else %} +

    No proposals submitted yet.

    + {% endif %} +
    + + {% associated_proposals as associated_proposals %} + {% if associated_proposals %} +
    +

    Proposals you have joined as an additional speaker

    + + + + + + + + {% for proposal in associated_proposals %} + {% include "proposals/_proposal_row.html" %} + {% endfor %} +
    TitleSession typeStatusActions
    +
    + {% endif %} + + {% pending_proposals as pending_proposals %} + {% if pending_proposals %} +
    +

    Proposals you have been invited to join

    + + + + + + + + {% for proposal in pending_proposals %} + {% include "proposals/_pending_proposal_row.html" %} + {% endfor %} +
    TitleSession typeStatusActions
    +
    + {% endif %} + + {% else %} +

    To submit a proposal, first create a speaker profile.

    + + {% endif %} +
    +
    + + +
    +
    +

    Sponsorship

    +
    +
    +
    + + + Learn about sponsorship + + {% if not user.sponsorships.exists %} + + + Apply to be a sponsor + + {% endif %} +
    + + {% if user.sponsorships.exists %} +
    +

    Your Sponsorship

    +
      + {% for sponsorship in user.sponsorships.all %} +
    • + {% if sponsorship.active %} + {{ sponsorship.name }} + ({{ sponsorship.level }}) + {% else %} + {{ sponsorship.name }} + ({{ sponsorship.level }}) + awaiting approval + {% endif %} +
    • + {% endfor %} +
    +
    + {% endif %} +
    +
    + + {% if user.is_staff %} +
    +
    +

    Staff

    +
    + +
    + {% endif %} +
    +
    +{% endblock %} \ No newline at end of file From 32c8c0ce5043e112545b544f6809d18ab007ab7e Mon Sep 17 00:00:00 2001 From: Luke Hatcher Date: Thu, 12 Jul 2012 01:59:28 -0400 Subject: [PATCH 070/751] end with a newline --- symposion_project/templates/dashboard.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/symposion_project/templates/dashboard.html b/symposion_project/templates/dashboard.html index 1639e260..b0f53f00 100644 --- a/symposion_project/templates/dashboard.html +++ b/symposion_project/templates/dashboard.html @@ -162,4 +162,4 @@ {% endif %} -{% endblock %} \ No newline at end of file +{% endblock %} From 3ffcc4da7c24c012ddce39bc9cc8439fefb93560 Mon Sep 17 00:00:00 2001 From: James Tauber Date: Thu, 12 Jul 2012 15:17:42 -0400 Subject: [PATCH 071/751] first pass at merging pycon's sponsorship app with more basic symposion one --- symposion/sponsorship/admin.py | 68 ++++++++++++++++++++- symposion/sponsorship/forms.py | 72 ++++++++++++++++++++++ symposion/sponsorship/managers.py | 7 +++ symposion/sponsorship/models.py | 99 +++++++++++++++++++++++++++++-- symposion/sponsorship/urls.py | 9 +++ symposion/sponsorship/views.py | 58 ++++++++++++++++++ 6 files changed, 305 insertions(+), 8 deletions(-) create mode 100644 symposion/sponsorship/forms.py create mode 100644 symposion/sponsorship/managers.py create mode 100644 symposion/sponsorship/urls.py create mode 100644 symposion/sponsorship/views.py diff --git a/symposion/sponsorship/admin.py b/symposion/sponsorship/admin.py index a26b0a96..482ffb7b 100644 --- a/symposion/sponsorship/admin.py +++ b/symposion/sponsorship/admin.py @@ -1,7 +1,69 @@ from django.contrib import admin -from symposion.sponsorship.models import SponsorLevel, Sponsor +from symposion.sponsorship.models import SponsorLevel, Sponsor, Benefit, BenefitLevel, SponsorBenefit -admin.site.register(SponsorLevel) -admin.site.register(Sponsor, list_display=("name", "level", "added", "active"), list_filter = ("level", )) +class BenefitLevelInline(admin.TabularInline): + model = BenefitLevel + extra = 0 + + +class SponsorBenefitInline(admin.StackedInline): + model = SponsorBenefit + extra = 0 + fieldsets = [ + (None, { + "fields": [ + ("benefit", "active"), + ("max_words", "other_limits"), + "text", + "upload", + ] + }) + ] + + +class SponsorAdmin(admin.ModelAdmin): + + save_on_top = True + fieldsets = [ + (None, { + "fields": [ + ("name", "applicant"), + ("level", "active"), + "external_url", + "annotation", + ("contact_name", "contact_email") + ] + }), + ("Metadata", { + "fields": ["added"], + "classes": ["collapse"] + }) + ] + inlines = [SponsorBenefitInline] + + def get_form(self, *args, **kwargs): + # @@@ kinda ugly but using choices= on NullBooleanField is broken + form = super(SponsorAdmin, self).get_form(*args, **kwargs) + form.base_fields["active"].widget.choices = [ + (u"1", "unreviewed"), + (u"2", "approved"), + (u"3", "rejected") + ] + return form + + +class BenefitAdmin(admin.ModelAdmin): + + inlines = [BenefitLevelInline] + + +class SponsorLevelAdmin(admin.ModelAdmin): + + inlines = [BenefitLevelInline] + + +admin.site.register(SponsorLevel, SponsorLevelAdmin) +admin.site.register(Sponsor, SponsorAdmin) +admin.site.register(Benefit, BenefitAdmin) diff --git a/symposion/sponsorship/forms.py b/symposion/sponsorship/forms.py new file mode 100644 index 00000000..d8fd418a --- /dev/null +++ b/symposion/sponsorship/forms.py @@ -0,0 +1,72 @@ +from django import forms +from django.forms.models import inlineformset_factory, BaseInlineFormSet + +from django.contrib.admin.widgets import AdminFileWidget + +from symposion.sponsorship.models import Sponsor, SponsorBenefit + + +class SponsorApplicationForm(forms.ModelForm): + def __init__(self, *args, **kwargs): + self.user = kwargs.pop("user") + kwargs.update({ + "initial": { + "contact_name": self.user.get_full_name, + "contact_email": self.user.email, + } + }) + super(SponsorApplicationForm, self).__init__(*args, **kwargs) + + class Meta: + model = Sponsor + fields = ["name", "contact_name", "contact_email", "level"] + + def save(self, commit=True): + obj = super(SponsorApplicationForm, self).save(commit=False) + obj.applicant = self.user + if commit: + obj.save() + return obj + + +class SponsorDetailsForm(forms.ModelForm): + class Meta: + model = Sponsor + fields = [ + "name", + "external_url", + "contact_name", + "contact_email" + ] + + +class SponsorBenefitsInlineFormSet(BaseInlineFormSet): + + def _construct_form(self, i, **kwargs): + form = super(SponsorBenefitsInlineFormSet, self)._construct_form(i, **kwargs) + + # only include the relevant data fields for this benefit type + fields = form.instance.data_fields() + form.fields = dict((k, v) for (k, v) in form.fields.items() if k in fields + ["id"]) + + for field in fields: + # don't need a label, the form template will label it with the benefit name + form.fields[field].label = "" + + # provide word limit as help_text + if form.instance.benefit.type == "text" and form.instance.max_words: + form.fields[field].help_text = u"maximum %s words" % form.instance.max_words + + # use admin file widget that shows currently uploaded file + if field == "upload": + form.fields[field].widget = AdminFileWidget() + + return form + + +SponsorBenefitsFormSet = inlineformset_factory( + Sponsor, SponsorBenefit, + formset=SponsorBenefitsInlineFormSet, + can_delete=False, extra=0, + fields=["text", "upload"] +) diff --git a/symposion/sponsorship/managers.py b/symposion/sponsorship/managers.py new file mode 100644 index 00000000..490b328c --- /dev/null +++ b/symposion/sponsorship/managers.py @@ -0,0 +1,7 @@ +from django.db import models + + +class SponsorManager(models.Manager): + + def active(self): + return self.get_query_set().filter(active=True).order_by("level") diff --git a/symposion/sponsorship/models.py b/symposion/sponsorship/models.py index a902c0be..9c9eeda2 100644 --- a/symposion/sponsorship/models.py +++ b/symposion/sponsorship/models.py @@ -1,17 +1,24 @@ import datetime +from django.core.exceptions import ValidationError +from django.core.urlresolvers import reverse from django.db import models from django.utils.translation import ugettext_lazy as _ +from django.contrib.auth.models import User + from symposion.conference.models import Conference +from symposion.sponsorship.managers import SponsorManager + class SponsorLevel(models.Model): conference = models.ForeignKey(Conference, verbose_name=_("conference")) name = models.CharField(_("name"), max_length=100) order = models.IntegerField(_("order"), default=0) - description = models.TextField(_("description"), blank=True) + cost = models.PositiveIntegerField(_("cost")) + description = models.TextField(_("description"), blank=True, help_text=_("This is private.")) class Meta: ordering = ["conference", "order"] @@ -27,19 +34,101 @@ class SponsorLevel(models.Model): class Sponsor(models.Model): - name = models.CharField(_("name"), max_length=100) + applicant = models.ForeignKey(User, related_name="sponsorships", verbose_name=_("applicant"), null=True) + + name = models.CharField(_("Sponsor Name"), max_length=100) external_url = models.URLField(_("external URL")) annotation = models.TextField(_("annotation"), blank=True) - contact_name = models.CharField(_("contact_name"), max_length=100) - contact_email = models.EmailField(_(u"Contact e\u2011mail")) - logo = models.ImageField(_("logo"), upload_to="sponsor_logos/") + contact_name = models.CharField(_("Contact Name"), max_length=100) + contact_email = models.EmailField(_(u"Contact Email")) level = models.ForeignKey(SponsorLevel, verbose_name=_("level")) added = models.DateTimeField(_("added"), default=datetime.datetime.now) active = models.BooleanField(_("active"), default=False) + # Denormalization (this assumes only one logo) + sponsor_logo = models.ForeignKey("SponsorBenefit", related_name="+", null=True, blank=True, editable=False) + + objects = SponsorManager() + def __unicode__(self): return self.name class Meta: verbose_name = _("sponsor") verbose_name_plural = _("sponsors") + + def get_absolute_url(self): + if self.active: + return reverse("sponsor_detail", kwargs={"pk": self.pk}) + return reverse("sponsor_list") + + +BENEFIT_TYPE_CHOICES = [ + ("text", "Text"), + ("file", "File"), + ("weblogo", "Web Logo"), + ("simple", "Simple") +] + + +class Benefit(models.Model): + + name = models.CharField(_("name"), max_length=100) + description = models.TextField(_("description"), blank=True) + type = models.CharField(_("type"), choices=BENEFIT_TYPE_CHOICES, max_length=10, default="simple") + + def __unicode__(self): + return self.name + + +class BenefitLevel(models.Model): + + benefit = models.ForeignKey(Benefit, related_name="benefit_levels", verbose_name=_("benefit")) + level = models.ForeignKey(SponsorLevel, related_name="benefit_levels", verbose_name=_("level")) + + # default limits for this benefit at given level + max_words = models.PositiveIntegerField(_("max words"), blank=True, null=True) + other_limits = models.CharField(_("other limits"), max_length=200, blank=True) + + class Meta: + ordering = ["level"] + + def __unicode__(self): + return u"%s - %s" % (self.level, self.benefit) + + +class SponsorBenefit(models.Model): + + sponsor = models.ForeignKey(Sponsor, related_name="sponsor_benefits", verbose_name=_("sponsor")) + benefit = models.ForeignKey(Benefit, related_name="sponsor_benefits", verbose_name=_("benefit")) + active = models.BooleanField(default=True) + + # Limits: will initially be set to defaults from corresponding BenefitLevel + max_words = models.PositiveIntegerField(_("max words"), blank=True, null=True) + other_limits = models.CharField(_("other limits"), max_length=200, blank=True) + + # Data: zero or one of these fields will be used, depending on the + # type of the Benefit (text, file, or simple) + text = models.TextField(_("text"), blank=True) + upload = models.FileField(_("file"), blank=True, upload_to="sponsor_files") + + class Meta: + ordering = ['-active'] + + def __unicode__(self): + return u"%s - %s" % (self.sponsor, self.benefit) + + def clean(self): + if self.max_words and len(self.text.split()) > self.max_words: + raise ValidationError("Sponsorship level only allows for %s words." % self.max_words) + + def data_fields(self): + """ + Return list of data field names which should be editable for + this ``SponsorBenefit``, depending on its ``Benefit`` type. + """ + if self.benefit.type == "file" or self.benefit.type == "weblogo": + return ["upload"] + elif self.benefit.type == "text": + return ["text"] + return [] diff --git a/symposion/sponsorship/urls.py b/symposion/sponsorship/urls.py new file mode 100644 index 00000000..52ccdaae --- /dev/null +++ b/symposion/sponsorship/urls.py @@ -0,0 +1,9 @@ +from django.conf.urls.defaults import patterns, url +from django.views.generic.simple import direct_to_template + + +urlpatterns = patterns("symposion.sponsorship.views", + url(r"^$", direct_to_template, {"template": "sponsorship/list.html"}, name="sponsor_list"), + url(r"^apply/$", "sponsor_apply", name="sponsor_apply"), + url(r"^(?P\d+)/$", "sponsor_detail", name="sponsor_detail"), +) diff --git a/symposion/sponsorship/views.py b/symposion/sponsorship/views.py new file mode 100644 index 00000000..83acbf9e --- /dev/null +++ b/symposion/sponsorship/views.py @@ -0,0 +1,58 @@ +from django.shortcuts import render_to_response, redirect, get_object_or_404 +from django.template import RequestContext + +from django.contrib import messages +from django.contrib.auth.decorators import login_required + +from pycon.sponsorship.forms import SponsorApplicationForm, SponsorDetailsForm, SponsorBenefitsFormSet +from pycon.sponsorship.models import Sponsor, SponsorBenefit + + +@login_required +def sponsor_apply(request): + if request.method == "POST": + form = SponsorApplicationForm(request.POST, user=request.user) + if form.is_valid(): + form.save() + return redirect("dashboard") + else: + form = SponsorApplicationForm(user=request.user) + + return render_to_response("sponsorship/apply.html", { + "form": form, + }, context_instance=RequestContext(request)) + + +@login_required +def sponsor_detail(request, pk): + sponsor = get_object_or_404(Sponsor, pk=pk) + + if not sponsor.active or sponsor.applicant != request.user: + return redirect("sponsor_list") + + formset_kwargs = { + "instance": sponsor, + "queryset": SponsorBenefit.objects.filter(active=True) + } + + if request.method == "POST": + + form = SponsorDetailsForm(request.POST, instance=sponsor) + formset = SponsorBenefitsFormSet(request.POST, request.FILES, **formset_kwargs) + + if form.is_valid() and formset.is_valid(): + form.save() + formset.save() + + messages.success(request, "Your sponsorship application has been submitted!") + + return redirect(request.path) + else: + form = SponsorDetailsForm(instance=sponsor) + formset = SponsorBenefitsFormSet(**formset_kwargs) + + return render_to_response("sponsorship/detail.html", { + "sponsor": sponsor, + "form": form, + "formset": formset, + }, context_instance=RequestContext(request)) From 19d8b1dc7e72a5e247f4be200d0930c75a25b707 Mon Sep 17 00:00:00 2001 From: James Tauber Date: Thu, 12 Jul 2012 15:53:04 -0400 Subject: [PATCH 072/751] added initial docs on speakers app and updated sphinx --- docs/index.rst | 1 + docs/speakers.rst | 22 ++++++++++++++++++++++ requirements/docs.txt | 2 +- 3 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 docs/speakers.rst diff --git a/docs/index.rst b/docs/index.rst index 45faa102..46a43cff 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -17,6 +17,7 @@ Apps: conference sponsorship + speakers Indices and tables diff --git a/docs/speakers.rst b/docs/speakers.rst new file mode 100644 index 00000000..affaa198 --- /dev/null +++ b/docs/speakers.rst @@ -0,0 +1,22 @@ +Speaker App +=========== + +The ``speaker`` app allows speakers to set up their profile, prior to or as +part of the proposal submission phase. The **dashboard** is the means through +which speakers manage their own profiles. + +We are planning to make the Speaker model more pluggable so, if you have +particular fields you'd like your speakers to fill out, you'll be able to +customize things more easily. + +Additional Speakers +------------------- + +Because ``symposion`` supports additional speakers being attached to a +proposal or actual presentation, it has the notion of a ``Speaker`` that is +not yet a ``User`` on the site. For this reason, a ``Speaker`` may have a +NULL ``user`` field (hopefully temporarily) as well as an ``invite_email`` +and ``invite_token`` field for the invitation sent to the additional speaker +to join. + +.. todo:: perhaps explain the invitation flow diff --git a/requirements/docs.txt b/requirements/docs.txt index 82750ff4..366ee161 100644 --- a/requirements/docs.txt +++ b/requirements/docs.txt @@ -1,3 +1,3 @@ # requirements needed to build the docs -Sphinx==1.1.2 +Sphinx==1.1.3 From 81853ece305bd7116ee0ca29f693d517983a3133 Mon Sep 17 00:00:00 2001 From: James Tauber Date: Thu, 12 Jul 2012 15:53:44 -0400 Subject: [PATCH 073/751] made biography optional and cleaned up speaker code a little --- symposion/speakers/forms.py | 23 ----------------------- symposion/speakers/models.py | 4 ++-- 2 files changed, 2 insertions(+), 25 deletions(-) diff --git a/symposion/speakers/forms.py b/symposion/speakers/forms.py index c0dd1257..0b5176b6 100644 --- a/symposion/speakers/forms.py +++ b/symposion/speakers/forms.py @@ -1,7 +1,5 @@ from django import forms -from django.contrib import messages - from markitup.widgets import MarkItUpWidget from symposion.speakers.models import Speaker @@ -40,24 +38,3 @@ class SpeakerForm(forms.ModelForm): if not value: return None return int(value) - - -# class SignupForm(PinaxSignupForm): - -# def save(self, speaker, request=None): -# # don't assume a username is available. it is a common removal if -# # site developer wants to use email authentication. -# username = self.cleaned_data.get("username") -# email = self.cleaned_data["email"] -# new_user = self.create_user(username) -# if speaker.invite_email == new_user.email: -# # already verified so can just create -# EmailAddress(user=new_user, email=email, verified=True, primary=True).save() -# else: -# if request: -# messages.info(request, u"Confirmation email sent to %(email)s" % {"email": email}) -# EmailAddress.objects.add_email(new_user, email) -# new_user.is_active = False -# new_user.save() -# self.after_signup(new_user) -# return new_user diff --git a/symposion/speakers/models.py b/symposion/speakers/models.py index 8cc4d16d..9066385f 100644 --- a/symposion/speakers/models.py +++ b/symposion/speakers/models.py @@ -17,14 +17,14 @@ class Speaker(models.Model): user = models.OneToOneField(User, null=True, related_name="speaker_profile") name = models.CharField(max_length=100, help_text="As you would like it to appear in the conference program.") - biography = MarkupField(help_text="A little bit about you. Edit using Markdown.") + biography = MarkupField(blank=True, help_text="A little bit about you. Edit using Markdown.") photo = models.ImageField(upload_to="speaker_photos", blank=True) twitter_username = models.CharField( max_length = 15, blank = True, help_text = "Your Twitter account" ) - annotation = models.TextField() # staff only + annotation = models.TextField() # staff only invite_email = models.CharField(max_length=200, unique=True, null=True, db_index=True) invite_token = models.CharField(max_length=40, db_index=True) created = models.DateTimeField( From fcdad2c53b736be148fcd972ed63e2bbcb539ec9 Mon Sep 17 00:00:00 2001 From: Luke Hatcher Date: Thu, 12 Jul 2012 16:07:43 -0400 Subject: [PATCH 074/751] fix speaker templates to use proper base template --- symposion_project/templates/speakers/speaker_create.html | 2 +- symposion_project/templates/speakers/speaker_edit.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/symposion_project/templates/speakers/speaker_create.html b/symposion_project/templates/speakers/speaker_create.html index 377d601c..5762adc2 100644 --- a/symposion_project/templates/speakers/speaker_create.html +++ b/symposion_project/templates/speakers/speaker_create.html @@ -1,4 +1,4 @@ -{% extends "auth_base.html" %} +{% extends "site_base.html" %} {% load bootstrap_tags %} {% load i18n %} diff --git a/symposion_project/templates/speakers/speaker_edit.html b/symposion_project/templates/speakers/speaker_edit.html index d5ee120f..a8433bb9 100644 --- a/symposion_project/templates/speakers/speaker_edit.html +++ b/symposion_project/templates/speakers/speaker_edit.html @@ -1,4 +1,4 @@ -{% extends "auth_base.html" %} +{% extends "site_base.html" %} {% load bootstrap_tags %} {% load i18n %} From 3659da80b9fbea374f698c7f63e2f5c4ec7cdb05 Mon Sep 17 00:00:00 2001 From: Luke Hatcher Date: Thu, 12 Jul 2012 16:08:07 -0400 Subject: [PATCH 075/751] initial dashboard for symposion --- symposion_project/templates/dashboard.html | 242 ++++++++------------- 1 file changed, 92 insertions(+), 150 deletions(-) diff --git a/symposion_project/templates/dashboard.html b/symposion_project/templates/dashboard.html index b0f53f00..7082299f 100644 --- a/symposion_project/templates/dashboard.html +++ b/symposion_project/templates/dashboard.html @@ -7,159 +7,101 @@ {% block body_class %}auth{% endblock %} -{% block body_outer %} -
    -
    -
    -

    {% trans "Dashboard" %}

    -

    - This is your dashboard where you can manage all aspects of your - involvement in the conference, from talk proposals to sponsorship. -

    - {% block breadcrumbs %}{% endblock %} +{% block body %} +

    {% trans "Dashboard" %}

    + +
    +

    {% trans "Speaking" %}

    + {% if not user.speaker_profile %} +
    +

    To submit a proposal, you must first create a speaker profile.

    + + Create a speaker profile +
    -
    -
    -

    Speaking

    -
    -
    - {% if user.speaker_profile %} - + + Submit a new proposal + -
    -

    Your Proposals

    - {% if user.speaker_profile.proposals.exists %} - - - - - - - - {% for proposal in user.speaker_profile.proposals.all %} - {% include "proposals/_proposal_row.html" %} - {% endfor %} -
    TitleSession typeStatusActions
    - {% else %} -

    No proposals submitted yet.

    - {% endif %} -
    - - {% associated_proposals as associated_proposals %} - {% if associated_proposals %} -
    -

    Proposals you have joined as an additional speaker

    - - - - - - - - {% for proposal in associated_proposals %} - {% include "proposals/_proposal_row.html" %} - {% endfor %} -
    TitleSession typeStatusActions
    -
    - {% endif %} - - {% pending_proposals as pending_proposals %} - {% if pending_proposals %} -
    -

    Proposals you have been invited to join

    - - - - - - - - {% for proposal in pending_proposals %} - {% include "proposals/_pending_proposal_row.html" %} - {% endfor %} -
    TitleSession typeStatusActions
    -
    - {% endif %} - - {% else %} -

    To submit a proposal, first create a speaker profile.

    - - {% endif %} -
    -
    - - -
    -
    -

    Sponsorship

    -
    -
    -
    - - - Learn about sponsorship - - {% if not user.sponsorships.exists %} - - - Apply to be a sponsor - - {% endif %} -
    - - {% if user.sponsorships.exists %} -
    -

    Your Sponsorship

    -
      - {% for sponsorship in user.sponsorships.all %} -
    • - {% if sponsorship.active %} - {{ sponsorship.name }} - ({{ sponsorship.level }}) - {% else %} - {{ sponsorship.name }} - ({{ sponsorship.level }}) - awaiting approval - {% endif %} -
    • - {% endfor %} -
    -
    - {% endif %} -
    -
    - - {% if user.is_staff %} -
    -
    -

    Staff

    -
    - -
    +

    Your Proposals

    + {% if user.speaker_profile.proposals.exists %} + + + + + + + + {% for proposal in user.speaker_profile.proposals.all %} + {% include "proposals/_proposal_row.html" %} + {% endfor %} +
    TitleSession typeStatusActions
    + {% else %} +

    No proposals submitted yet.

    {% endif %} -
    -
    + + {% associated_proposals as associated_proposals %} + {% if associated_proposals %} +

    Proposals you have joined as an additional speaker

    + + + + + + + + {% for proposal in associated_proposals %} + {% include "proposals/_proposal_row.html" %} + {% endfor %} +
    TitleSession typeStatusActions
    + {% endif %} + + {% pending_proposals as pending_proposals %} + {% if pending_proposals %} +

    Proposals you have been invited to join

    + + + + + + + + {% for proposal in pending_proposals %} + {% include "proposals/_pending_proposal_row.html" %} + {% endfor %} +
    TitleSession typeStatusActions
    + {% endif %} + {% endif %} + + +
    +

    {% trans "Sponsorship" %}

    + {% if not user.sponsorships.exists %} + + Apply to be a sponsor + + {% else %} +

    Your Sponsorship

    +
      + {% for sponsorship in user.sponsorships.all %} +
    • + {% if sponsorship.active %} + {{ sponsorship.name }} + ({{ sponsorship.level }}) + {% else %} + {{ sponsorship.name }} + ({{ sponsorship.level }}) + awaiting approval + {% endif %} +
    • + {% endfor %} +
    + {% endif %} +
    {% endblock %} From dc11411a4aef4e2507de66efcc1712b56ce69203 Mon Sep 17 00:00:00 2001 From: James Tauber Date: Thu, 12 Jul 2012 16:38:35 -0400 Subject: [PATCH 076/751] added first pass of documentation about proposals --- docs/index.rst | 1 + docs/proposals.rst | 111 ++++++++++++++++++++++++++++++++++ symposion/proposals/models.py | 2 +- 3 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 docs/proposals.rst diff --git a/docs/index.rst b/docs/index.rst index 46a43cff..a66ab8e7 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -18,6 +18,7 @@ Apps: conference sponsorship speakers + proposals Indices and tables diff --git a/docs/proposals.rst b/docs/proposals.rst new file mode 100644 index 00000000..bb578ee8 --- /dev/null +++ b/docs/proposals.rst @@ -0,0 +1,111 @@ +Proposals App +============= + + +Models +------ + + +ProposalSection +~~~~~~~~~~~~~~~ + +Recall that a symposion instance consists of one or more ``Conference``s each +made up of one or more ``Section``s. + +Different sections can have different open / close dates for proposals. +This is managed through a ``ProposalSection`` which is a one-to-one with +``Section`` where you can define a ``start`` date, an ``end`` date and/or +simply toggle proposals for the section ``closed``. + +A section is available for proposals iff: + * it is after the ``start`` (if there is one) and + * it is before the ``end`` (if there is one) and + * ``closed`` is NULL or False + +In other words, ``closed`` can be used as an override, regardless of ``start`` +and ``end`` and, if you want, you can just manually use ``closed`` rather than +setting dates. + +This model is currently managed by conference staff via the Django admin +although given it's part of "conference setup", it may often just be a +fixture that's loaded. + + +ProposalKind +~~~~~~~~~~~~ + +A conference, even within a section, may have different kinds of +presentations, e.g. talks, panels, tutorials, posters. + +If these have different requirements for what fields should be in the +proposal form, they should be modeled as different ``ProposalKind``s. For +example, you may want talk proposals to include an intended audience level +but not require that for poster submissions. + +Note that if you have different deadlines, reviews, etc. you'll want to +distinguish the **section** as well as the kind. + +This model is currently managed by conference staff via the Django admin +although given it's part of "conference setup", it may often just be a +fixture that's loaded. + + +ProposalBase +~~~~~~~~~~~~ + +Each proposal kind should have a subclass of ``ProposalBase`` defining the +fields for proposals of that kind. We discuss below how that association is +made. + +``ProposalBase`` provides fields for a ``title``, a single-paragraph +plain-text ``description`` and an ``abstract`` which can contain markup. + +There is also an ``additional_notes`` field which can be used for speakers to +communicate additional information about their proposal to reviewers that is +not intended to be shared with others. + +This base model supports each proposal having multiple speakers (although +the submitting speaker is always treated differently) and also supports +the attachments of supporting documents for reviewers that are, like the +``additional_notes`` not intended to be shared with others. + +A ``cancelled`` boolean field is also provided to indicate that a proposal +has been cancelled or withdrawn. + + +AdditionalSpeaker +~~~~~~~~~~~~~~~~~ + +Used for modeling the additional speakers on a proposal in additional to the +submitting speaker. The status of an additional speaker may be ``Pending``, +``Accepted`` or ``Declined``. + +.. todo:: see note in speakers docs about explaining the flow + + +SupportingDocument +~~~~~~~~~~~~~~~~~~ + +Used for modeling the supporting documents that can be attached to a proposal. + + +How to Add Custom Proposal Kinds +-------------------------------- + +For each kind: + + * create a ``ProposalKind`` instance + * subclass ``ProposalBase`` and add the fields you want + * define a Django ``ModelForm`` for proposals of that kind + * make sure your settings file has a ``PROPOSAL_FORMS`` dictionary + that maps the slug of your ``ProposalKind`` to the fully-qualified + name of your ``ModelForm``. + +For example:: + + PROPOSAL_FORMS = { + "tutorial": "pycon.forms.PyConTutorialProposalForm", + "talk": "pycon.forms.PyConTalkProposalForm", + "poster": "pycon.forms.PyConPosterProposalForm", + } + diff --git a/symposion/proposals/models.py b/symposion/proposals/models.py index f602147d..16b56102 100644 --- a/symposion/proposals/models.py +++ b/symposion/proposals/models.py @@ -30,7 +30,7 @@ class ProposalSection(models.Model): start = models.DateTimeField(null=True, blank=True) end = models.DateTimeField(null=True, blank=True) closed = models.NullBooleanField() - published = models.NullBooleanField() + published = models.NullBooleanField() # @@@ what is this used for? @classmethod def available(cls): From dc9f313fe61032e46650a1b964e356215983d505 Mon Sep 17 00:00:00 2001 From: Luke Hatcher Date: Thu, 12 Jul 2012 22:50:48 -0400 Subject: [PATCH 077/751] add in less --- symposion_project/static/symposion/Makefile | 12 + .../static/symposion/css/symposion.css | 768 +++++++++++++++ .../symposion/less/bootstrap/accordion.less | 33 + .../symposion/less/bootstrap/alerts.less | 58 ++ .../symposion/less/bootstrap/bootstrap.less | 62 ++ .../symposion/less/bootstrap/breadcrumbs.less | 24 + .../less/bootstrap/button-groups.less | 191 ++++ .../symposion/less/bootstrap/buttons.less | 191 ++++ .../symposion/less/bootstrap/carousel.less | 121 +++ .../symposion/less/bootstrap/close.less | 29 + .../static/symposion/less/bootstrap/code.less | 57 ++ .../less/bootstrap/component-animations.less | 20 + .../symposion/less/bootstrap/dropdowns.less | 143 +++ .../symposion/less/bootstrap/forms.less | 583 +++++++++++ .../static/symposion/less/bootstrap/grid.less | 5 + .../symposion/less/bootstrap/hero-unit.less | 22 + .../less/bootstrap/labels-badges.less | 55 ++ .../symposion/less/bootstrap/layouts.less | 17 + .../symposion/less/bootstrap/mixins.less | 646 ++++++++++++ .../symposion/less/bootstrap/modals.less | 90 ++ .../symposion/less/bootstrap/navbar.less | 358 +++++++ .../static/symposion/less/bootstrap/navs.less | 363 +++++++ .../symposion/less/bootstrap/pager.less | 36 + .../symposion/less/bootstrap/pagination.less | 56 ++ .../symposion/less/bootstrap/popovers.less | 49 + .../less/bootstrap/progress-bars.less | 117 +++ .../symposion/less/bootstrap/reset.less | 131 +++ .../less/bootstrap/responsive-1200px-min.less | 26 + .../less/bootstrap/responsive-767px-max.less | 149 +++ .../bootstrap/responsive-768px-979px.less | 17 + .../less/bootstrap/responsive-navbar.less | 153 +++ .../less/bootstrap/responsive-utilities.less | 41 + .../symposion/less/bootstrap/responsive.less | 48 + .../symposion/less/bootstrap/scaffolding.less | 29 + .../symposion/less/bootstrap/sprites.less | 191 ++++ .../symposion/less/bootstrap/tables.less | 176 ++++ .../less/bootstrap/tests/css-tests.css | 52 + .../less/bootstrap/tests/css-tests.html | 917 ++++++++++++++++++ .../symposion/less/bootstrap/tests/forms.html | 179 ++++ .../less/bootstrap/tests/navbar.html | 108 +++ .../symposion/less/bootstrap/thumbnails.less | 47 + .../symposion/less/bootstrap/tooltip.less | 35 + .../static/symposion/less/bootstrap/type.less | 232 +++++ .../symposion/less/bootstrap/utilities.less | 23 + .../symposion/less/bootstrap/variables.less | 206 ++++ .../symposion/less/bootstrap/wells.less | 27 + .../static/symposion/less/bootswatch.less | 193 ++++ .../static/symposion/less/custom.less | 23 + .../static/symposion/less/symposion.less | 92 ++ .../static/symposion/less/variables.less | 206 ++++ symposion_project/templates/site_base.html | 5 + 51 files changed, 7412 insertions(+) create mode 100644 symposion_project/static/symposion/Makefile create mode 100644 symposion_project/static/symposion/css/symposion.css create mode 100644 symposion_project/static/symposion/less/bootstrap/accordion.less create mode 100644 symposion_project/static/symposion/less/bootstrap/alerts.less create mode 100644 symposion_project/static/symposion/less/bootstrap/bootstrap.less create mode 100644 symposion_project/static/symposion/less/bootstrap/breadcrumbs.less create mode 100644 symposion_project/static/symposion/less/bootstrap/button-groups.less create mode 100644 symposion_project/static/symposion/less/bootstrap/buttons.less create mode 100644 symposion_project/static/symposion/less/bootstrap/carousel.less create mode 100644 symposion_project/static/symposion/less/bootstrap/close.less create mode 100644 symposion_project/static/symposion/less/bootstrap/code.less create mode 100644 symposion_project/static/symposion/less/bootstrap/component-animations.less create mode 100644 symposion_project/static/symposion/less/bootstrap/dropdowns.less create mode 100644 symposion_project/static/symposion/less/bootstrap/forms.less create mode 100644 symposion_project/static/symposion/less/bootstrap/grid.less create mode 100644 symposion_project/static/symposion/less/bootstrap/hero-unit.less create mode 100644 symposion_project/static/symposion/less/bootstrap/labels-badges.less create mode 100644 symposion_project/static/symposion/less/bootstrap/layouts.less create mode 100644 symposion_project/static/symposion/less/bootstrap/mixins.less create mode 100644 symposion_project/static/symposion/less/bootstrap/modals.less create mode 100644 symposion_project/static/symposion/less/bootstrap/navbar.less create mode 100644 symposion_project/static/symposion/less/bootstrap/navs.less create mode 100644 symposion_project/static/symposion/less/bootstrap/pager.less create mode 100644 symposion_project/static/symposion/less/bootstrap/pagination.less create mode 100644 symposion_project/static/symposion/less/bootstrap/popovers.less create mode 100644 symposion_project/static/symposion/less/bootstrap/progress-bars.less create mode 100644 symposion_project/static/symposion/less/bootstrap/reset.less create mode 100644 symposion_project/static/symposion/less/bootstrap/responsive-1200px-min.less create mode 100644 symposion_project/static/symposion/less/bootstrap/responsive-767px-max.less create mode 100644 symposion_project/static/symposion/less/bootstrap/responsive-768px-979px.less create mode 100644 symposion_project/static/symposion/less/bootstrap/responsive-navbar.less create mode 100644 symposion_project/static/symposion/less/bootstrap/responsive-utilities.less create mode 100644 symposion_project/static/symposion/less/bootstrap/responsive.less create mode 100644 symposion_project/static/symposion/less/bootstrap/scaffolding.less create mode 100644 symposion_project/static/symposion/less/bootstrap/sprites.less create mode 100644 symposion_project/static/symposion/less/bootstrap/tables.less create mode 100644 symposion_project/static/symposion/less/bootstrap/tests/css-tests.css create mode 100644 symposion_project/static/symposion/less/bootstrap/tests/css-tests.html create mode 100644 symposion_project/static/symposion/less/bootstrap/tests/forms.html create mode 100644 symposion_project/static/symposion/less/bootstrap/tests/navbar.html create mode 100644 symposion_project/static/symposion/less/bootstrap/thumbnails.less create mode 100644 symposion_project/static/symposion/less/bootstrap/tooltip.less create mode 100644 symposion_project/static/symposion/less/bootstrap/type.less create mode 100644 symposion_project/static/symposion/less/bootstrap/utilities.less create mode 100644 symposion_project/static/symposion/less/bootstrap/variables.less create mode 100644 symposion_project/static/symposion/less/bootstrap/wells.less create mode 100644 symposion_project/static/symposion/less/bootswatch.less create mode 100644 symposion_project/static/symposion/less/custom.less create mode 100644 symposion_project/static/symposion/less/symposion.less create mode 100644 symposion_project/static/symposion/less/variables.less diff --git a/symposion_project/static/symposion/Makefile b/symposion_project/static/symposion/Makefile new file mode 100644 index 00000000..e84978df --- /dev/null +++ b/symposion_project/static/symposion/Makefile @@ -0,0 +1,12 @@ +BOOTSTRAP_LESS = ./less/symposion.less +CSS_OUTPUT_FILENAME = symposion.css +LESS_COMPRESSOR ?= `which lessc` + +# +# BUILD SITE MEDIA +# lessc & uglifyjs are required +# + +build: + mkdir -p css + lessc --compress ${BOOTSTRAP_LESS} > css/${CSS_OUTPUT_FILENAME} diff --git a/symposion_project/static/symposion/css/symposion.css b/symposion_project/static/symposion/css/symposion.css new file mode 100644 index 00000000..bd9b1624 --- /dev/null +++ b/symposion_project/static/symposion/css/symposion.css @@ -0,0 +1,768 @@ +article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block;} +audio,canvas,video{display:inline-block;*display:inline;*zoom:1;} +audio:not([controls]){display:none;} +html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;} +a:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px;} +a:hover,a:active{outline:0;} +sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline;} +sup{top:-0.5em;} +sub{bottom:-0.25em;} +img{max-width:100%;vertical-align:middle;border:0;-ms-interpolation-mode:bicubic;} +#map_canvas img{max-width:none;} +button,input,select,textarea{margin:0;font-size:100%;vertical-align:middle;} +button,input{*overflow:visible;line-height:normal;} +button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0;} +button,input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button;} +input[type="search"]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield;} +input[type="search"]::-webkit-search-decoration,input[type="search"]::-webkit-search-cancel-button{-webkit-appearance:none;} +textarea{overflow:auto;vertical-align:top;} +.clearfix{*zoom:1;}.clearfix:before,.clearfix:after{display:table;content:"";} +.clearfix:after{clear:both;} +.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0;} +.input-block-level{display:block;width:100%;min-height:28px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box;} +body{margin:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;line-height:18px;color:#333333;background-color:#ffffff;} +a{color:#4183c4;text-decoration:none;} +a:hover{color:#4183c4;text-decoration:underline;} +.row{margin-left:-20px;*zoom:1;}.row:before,.row:after{display:table;content:"";} +.row:after{clear:both;} +[class*="span"]{float:left;margin-left:20px;} +.container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px;} +.span12{width:940px;} +.span11{width:860px;} +.span10{width:780px;} +.span9{width:700px;} +.span8{width:620px;} +.span7{width:540px;} +.span6{width:460px;} +.span5{width:380px;} +.span4{width:300px;} +.span3{width:220px;} +.span2{width:140px;} +.span1{width:60px;} +.offset12{margin-left:980px;} +.offset11{margin-left:900px;} +.offset10{margin-left:820px;} +.offset9{margin-left:740px;} +.offset8{margin-left:660px;} +.offset7{margin-left:580px;} +.offset6{margin-left:500px;} +.offset5{margin-left:420px;} +.offset4{margin-left:340px;} +.offset3{margin-left:260px;} +.offset2{margin-left:180px;} +.offset1{margin-left:100px;} +.row-fluid{width:100%;*zoom:1;}.row-fluid:before,.row-fluid:after{display:table;content:"";} +.row-fluid:after{clear:both;} +.row-fluid [class*="span"]{display:block;width:100%;min-height:28px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box;float:left;margin-left:2.127659574%;*margin-left:2.0744680846382977%;} +.row-fluid [class*="span"]:first-child{margin-left:0;} +.row-fluid .span12{width:99.99999998999999%;*width:99.94680850063828%;} +.row-fluid .span11{width:91.489361693%;*width:91.4361702036383%;} +.row-fluid .span10{width:82.97872339599999%;*width:82.92553190663828%;} +.row-fluid .span9{width:74.468085099%;*width:74.4148936096383%;} +.row-fluid .span8{width:65.95744680199999%;*width:65.90425531263828%;} +.row-fluid .span7{width:57.446808505%;*width:57.3936170156383%;} +.row-fluid .span6{width:48.93617020799999%;*width:48.88297871863829%;} +.row-fluid .span5{width:40.425531911%;*width:40.3723404216383%;} +.row-fluid .span4{width:31.914893614%;*width:31.8617021246383%;} +.row-fluid .span3{width:23.404255317%;*width:23.3510638276383%;} +.row-fluid .span2{width:14.89361702%;*width:14.8404255306383%;} +.row-fluid .span1{width:6.382978723%;*width:6.329787233638298%;} +.container{margin-right:auto;margin-left:auto;*zoom:1;}.container:before,.container:after{display:table;content:"";} +.container:after{clear:both;} +.container-fluid{padding-right:20px;padding-left:20px;*zoom:1;}.container-fluid:before,.container-fluid:after{display:table;content:"";} +.container-fluid:after{clear:both;} +p{margin:0 0 9px;}p small{font-size:11px;color:#999999;} +.lead{margin-bottom:18px;font-size:20px;font-weight:200;line-height:27px;} +h1,h2,h3,h4,h5,h6{margin:0;font-family:inherit;font-weight:bold;color:inherit;text-rendering:optimizelegibility;}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{font-weight:normal;color:#999999;} +h1{font-size:30px;line-height:36px;}h1 small{font-size:18px;} +h2{font-size:24px;line-height:36px;}h2 small{font-size:18px;} +h3{font-size:18px;line-height:27px;}h3 small{font-size:14px;} +h4,h5,h6{line-height:18px;} +h4{font-size:14px;}h4 small{font-size:12px;} +h5{font-size:12px;} +h6{font-size:11px;color:#999999;text-transform:uppercase;} +.page-header{padding-bottom:17px;margin:18px 0;border-bottom:1px solid #f5f5f5;} +.page-header h1{line-height:1;} +ul,ol{padding:0;margin:0 0 9px 25px;} +ul ul,ul ol,ol ol,ol ul{margin-bottom:0;} +ul{list-style:disc;} +ol{list-style:decimal;} +li{line-height:18px;} +ul.unstyled,ol.unstyled{margin-left:0;list-style:none;} +dl{margin-bottom:18px;} +dt,dd{line-height:18px;} +dt{font-weight:bold;line-height:17px;} +dd{margin-left:9px;} +.dl-horizontal dt{float:left;width:120px;clear:left;text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;} +.dl-horizontal dd{margin-left:130px;} +hr{margin:18px 0;border:0;border-top:1px solid #f5f5f5;border-bottom:1px solid #ffffff;} +strong{font-weight:bold;} +em{font-style:italic;} +.muted{color:#999999;} +abbr[title]{cursor:help;border-bottom:1px dotted #999999;} +abbr.initialism{font-size:90%;text-transform:uppercase;} +blockquote{padding:0 0 0 15px;margin:0 0 18px;border-left:5px solid #f5f5f5;}blockquote p{margin-bottom:0;font-size:16px;font-weight:300;line-height:22.5px;} +blockquote small{display:block;line-height:18px;color:#999999;}blockquote small:before{content:'\2014 \00A0';} +blockquote.pull-right{float:right;padding-right:15px;padding-left:0;border-right:5px solid #f5f5f5;border-left:0;}blockquote.pull-right p,blockquote.pull-right small{text-align:right;} +q:before,q:after,blockquote:before,blockquote:after{content:"";} +address{display:block;margin-bottom:18px;font-style:normal;line-height:18px;} +small{font-size:100%;} +cite{font-style:normal;} +code,pre{padding:0 3px 2px;font-family:Menlo,Monaco,Consolas,"Courier New",monospace;font-size:12px;color:#333333;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;} +code{padding:2px 4px;color:#d14;background-color:#f7f7f9;border:1px solid #e1e1e8;} +pre{display:block;padding:8.5px;margin:0 0 9px;font-size:12.025px;line-height:18px;word-break:break-all;word-wrap:break-word;white-space:pre;white-space:pre-wrap;background-color:#f5f5f5;border:1px solid #ccc;border:1px solid rgba(0, 0, 0, 0.15);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}pre.prettyprint{margin-bottom:18px;} +pre code{padding:0;color:inherit;background-color:transparent;border:0;} +.pre-scrollable{max-height:340px;overflow-y:scroll;} +form{margin:0 0 18px;} +fieldset{padding:0;margin:0;border:0;} +legend{display:block;width:100%;padding:0;margin-bottom:27px;font-size:19.5px;line-height:36px;color:#333333;border:0;border-bottom:1px solid #e5e5e5;}legend small{font-size:13.5px;color:#999999;} +label,input,button,select,textarea{font-size:13px;font-weight:normal;line-height:18px;} +input,button,select,textarea{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;} +label{display:block;margin-bottom:5px;} +select,textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"],.uneditable-input{display:inline-block;height:18px;padding:4px;margin-bottom:9px;font-size:13px;line-height:18px;color:#555555;} +input,textarea{width:210px;} +textarea{height:auto;} +textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"],.uneditable-input{background-color:#ffffff;border:1px solid #cccccc;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);-webkit-transition:border linear 0.2s,box-shadow linear 0.2s;-moz-transition:border linear 0.2s,box-shadow linear 0.2s;-ms-transition:border linear 0.2s,box-shadow linear 0.2s;-o-transition:border linear 0.2s,box-shadow linear 0.2s;transition:border linear 0.2s,box-shadow linear 0.2s;}textarea:focus,input[type="text"]:focus,input[type="password"]:focus,input[type="datetime"]:focus,input[type="datetime-local"]:focus,input[type="date"]:focus,input[type="month"]:focus,input[type="time"]:focus,input[type="week"]:focus,input[type="number"]:focus,input[type="email"]:focus,input[type="url"]:focus,input[type="search"]:focus,input[type="tel"]:focus,input[type="color"]:focus,.uneditable-input:focus{border-color:rgba(82, 168, 236, 0.8);outline:0;outline:thin dotted \9;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(82,168,236,.6);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(82,168,236,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(82,168,236,.6);} +input[type="radio"],input[type="checkbox"]{margin:3px 0;*margin-top:0;line-height:normal;cursor:pointer;} +input[type="submit"],input[type="reset"],input[type="button"],input[type="radio"],input[type="checkbox"]{width:auto;} +.uneditable-textarea{width:auto;height:auto;} +select,input[type="file"]{height:28px;*margin-top:4px;line-height:28px;} +select{width:220px;border:1px solid #bbb;} +select[multiple],select[size]{height:auto;} +select:focus,input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px;} +.radio,.checkbox{min-height:18px;padding-left:18px;} +.radio input[type="radio"],.checkbox input[type="checkbox"]{float:left;margin-left:-18px;} +.controls>.radio:first-child,.controls>.checkbox:first-child{padding-top:5px;} +.radio.inline,.checkbox.inline{display:inline-block;padding-top:5px;margin-bottom:0;vertical-align:middle;} +.radio.inline+.radio.inline,.checkbox.inline+.checkbox.inline{margin-left:10px;} +.input-mini{width:60px;} +.input-small{width:90px;} +.input-medium{width:150px;} +.input-large{width:210px;} +.input-xlarge{width:270px;} +.input-xxlarge{width:530px;} +input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input[class*="span"],.row-fluid input[class*="span"],.row-fluid select[class*="span"],.row-fluid textarea[class*="span"],.row-fluid .uneditable-input[class*="span"]{float:none;margin-left:0;} +.input-append input[class*="span"],.input-append .uneditable-input[class*="span"],.input-prepend input[class*="span"],.input-prepend .uneditable-input[class*="span"],.row-fluid .input-prepend [class*="span"],.row-fluid .input-append [class*="span"]{display:inline-block;} +input,textarea,.uneditable-input{margin-left:0;} +input.span12, textarea.span12, .uneditable-input.span12{width:930px;} +input.span11, textarea.span11, .uneditable-input.span11{width:850px;} +input.span10, textarea.span10, .uneditable-input.span10{width:770px;} +input.span9, textarea.span9, .uneditable-input.span9{width:690px;} +input.span8, textarea.span8, .uneditable-input.span8{width:610px;} +input.span7, textarea.span7, .uneditable-input.span7{width:530px;} +input.span6, textarea.span6, .uneditable-input.span6{width:450px;} +input.span5, textarea.span5, .uneditable-input.span5{width:370px;} +input.span4, textarea.span4, .uneditable-input.span4{width:290px;} +input.span3, textarea.span3, .uneditable-input.span3{width:210px;} +input.span2, textarea.span2, .uneditable-input.span2{width:130px;} +input.span1, textarea.span1, .uneditable-input.span1{width:50px;} +input[disabled],select[disabled],textarea[disabled],input[readonly],select[readonly],textarea[readonly]{cursor:not-allowed;background-color:#f5f5f5;border-color:#ddd;} +input[type="radio"][disabled],input[type="checkbox"][disabled],input[type="radio"][readonly],input[type="checkbox"][readonly]{background-color:transparent;} +.control-group.warning>label,.control-group.warning .help-block,.control-group.warning .help-inline{color:#393939;} +.control-group.warning .checkbox,.control-group.warning .radio,.control-group.warning input,.control-group.warning select,.control-group.warning textarea{color:#393939;border-color:#393939;}.control-group.warning .checkbox:focus,.control-group.warning .radio:focus,.control-group.warning input:focus,.control-group.warning select:focus,.control-group.warning textarea:focus{border-color:#202020;-webkit-box-shadow:0 0 6px #6c6c6c;-moz-box-shadow:0 0 6px #6c6c6c;box-shadow:0 0 6px #6c6c6c;} +.control-group.warning .input-prepend .add-on,.control-group.warning .input-append .add-on{color:#393939;background-color:#f5f3b4;border-color:#393939;} +.control-group.error>label,.control-group.error .help-block,.control-group.error .help-inline{color:#393939;} +.control-group.error .checkbox,.control-group.error .radio,.control-group.error input,.control-group.error select,.control-group.error textarea{color:#393939;border-color:#393939;}.control-group.error .checkbox:focus,.control-group.error .radio:focus,.control-group.error input:focus,.control-group.error select:focus,.control-group.error textarea:focus{border-color:#202020;-webkit-box-shadow:0 0 6px #6c6c6c;-moz-box-shadow:0 0 6px #6c6c6c;box-shadow:0 0 6px #6c6c6c;} +.control-group.error .input-prepend .add-on,.control-group.error .input-append .add-on{color:#393939;background-color:#ffe9e9;border-color:#393939;} +.control-group.success>label,.control-group.success .help-block,.control-group.success .help-inline{color:#333333;} +.control-group.success .checkbox,.control-group.success .radio,.control-group.success input,.control-group.success select,.control-group.success textarea{color:#333333;border-color:#333333;}.control-group.success .checkbox:focus,.control-group.success .radio:focus,.control-group.success input:focus,.control-group.success select:focus,.control-group.success textarea:focus{border-color:#1a1a1a;-webkit-box-shadow:0 0 6px #666666;-moz-box-shadow:0 0 6px #666666;box-shadow:0 0 6px #666666;} +.control-group.success .input-prepend .add-on,.control-group.success .input-append .add-on{color:#333333;background-color:#bedebe;border-color:#333333;} +input:focus:required:invalid,textarea:focus:required:invalid,select:focus:required:invalid{color:#b94a48;border-color:#ee5f5b;}input:focus:required:invalid:focus,textarea:focus:required:invalid:focus,select:focus:required:invalid:focus{border-color:#e9322d;-webkit-box-shadow:0 0 6px #f8b9b7;-moz-box-shadow:0 0 6px #f8b9b7;box-shadow:0 0 6px #f8b9b7;} +.form-actions{padding:17px 20px 18px;margin-top:18px;margin-bottom:18px;background-color:#f5f5f5;border-top:1px solid #e5e5e5;*zoom:1;}.form-actions:before,.form-actions:after{display:table;content:"";} +.form-actions:after{clear:both;} +.uneditable-input{overflow:hidden;white-space:nowrap;cursor:not-allowed;background-color:#ffffff;border-color:#eee;-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);-moz-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);} +:-moz-placeholder{color:#999999;} +:-ms-input-placeholder{color:#999999;} +::-webkit-input-placeholder{color:#999999;} +.help-block,.help-inline{color:#555555;} +.help-block{display:block;margin-bottom:9px;} +.help-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle;padding-left:5px;} +.input-prepend,.input-append{margin-bottom:5px;}.input-prepend input,.input-append input,.input-prepend select,.input-append select,.input-prepend .uneditable-input,.input-append .uneditable-input{position:relative;margin-bottom:0;*margin-left:0;vertical-align:middle;-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0;}.input-prepend input:focus,.input-append input:focus,.input-prepend select:focus,.input-append select:focus,.input-prepend .uneditable-input:focus,.input-append .uneditable-input:focus{z-index:2;} +.input-prepend .uneditable-input,.input-append .uneditable-input{border-left-color:#ccc;} +.input-prepend .add-on,.input-append .add-on{display:inline-block;width:auto;height:18px;min-width:16px;padding:4px 5px;font-weight:normal;line-height:18px;text-align:center;text-shadow:0 1px 0 #ffffff;vertical-align:middle;background-color:#f5f5f5;border:1px solid #ccc;} +.input-prepend .add-on,.input-append .add-on,.input-prepend .btn,.input-append .btn{margin-left:-1px;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;} +.input-prepend .active,.input-append .active{background-color:#fafefa;border-color:#84de81;} +.input-prepend .add-on,.input-prepend .btn{margin-right:-1px;} +.input-prepend .add-on:first-child,.input-prepend .btn:first-child{-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;} +.input-append input,.input-append select,.input-append .uneditable-input{-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;} +.input-append .uneditable-input{border-right-color:#ccc;border-left-color:#eee;} +.input-append .add-on:last-child,.input-append .btn:last-child{-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0;} +.input-prepend.input-append input,.input-prepend.input-append select,.input-prepend.input-append .uneditable-input{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;} +.input-prepend.input-append .add-on:first-child,.input-prepend.input-append .btn:first-child{margin-right:-1px;-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;} +.input-prepend.input-append .add-on:last-child,.input-prepend.input-append .btn:last-child{margin-left:-1px;-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0;} +.search-query{padding-right:14px;padding-right:4px \9;padding-left:14px;padding-left:4px \9;margin-bottom:0;-webkit-border-radius:14px;-moz-border-radius:14px;border-radius:14px;} +.form-search input,.form-inline input,.form-horizontal input,.form-search textarea,.form-inline textarea,.form-horizontal textarea,.form-search select,.form-inline select,.form-horizontal select,.form-search .help-inline,.form-inline .help-inline,.form-horizontal .help-inline,.form-search .uneditable-input,.form-inline .uneditable-input,.form-horizontal .uneditable-input,.form-search .input-prepend,.form-inline .input-prepend,.form-horizontal .input-prepend,.form-search .input-append,.form-inline .input-append,.form-horizontal .input-append{display:inline-block;*display:inline;*zoom:1;margin-bottom:0;} +.form-search .hide,.form-inline .hide,.form-horizontal .hide{display:none;} +.form-search label,.form-inline label{display:inline-block;} +.form-search .input-append,.form-inline .input-append,.form-search .input-prepend,.form-inline .input-prepend{margin-bottom:0;} +.form-search .radio,.form-search .checkbox,.form-inline .radio,.form-inline .checkbox{padding-left:0;margin-bottom:0;vertical-align:middle;} +.form-search .radio input[type="radio"],.form-search .checkbox input[type="checkbox"],.form-inline .radio input[type="radio"],.form-inline .checkbox input[type="checkbox"]{float:left;margin-right:3px;margin-left:0;} +.control-group{margin-bottom:9px;} +legend+.control-group{margin-top:18px;-webkit-margin-top-collapse:separate;} +.form-horizontal .control-group{margin-bottom:18px;*zoom:1;}.form-horizontal .control-group:before,.form-horizontal .control-group:after{display:table;content:"";} +.form-horizontal .control-group:after{clear:both;} +.form-horizontal .control-label{float:left;width:140px;padding-top:5px;text-align:right;} +.form-horizontal .controls{*display:inline-block;*padding-left:20px;margin-left:160px;*margin-left:0;}.form-horizontal .controls:first-child{*padding-left:160px;} +.form-horizontal .help-block{margin-top:9px;margin-bottom:0;} +.form-horizontal .form-actions{padding-left:160px;} +table{max-width:100%;background-color:transparent;border-collapse:collapse;border-spacing:0;} +.table{width:100%;margin-bottom:18px;}.table th,.table td{padding:8px;line-height:18px;text-align:left;vertical-align:top;border-top:1px solid #dddddd;} +.table th{font-weight:bold;} +.table thead th{vertical-align:bottom;} +.table caption+thead tr:first-child th,.table caption+thead tr:first-child td,.table colgroup+thead tr:first-child th,.table colgroup+thead tr:first-child td,.table thead:first-child tr:first-child th,.table thead:first-child tr:first-child td{border-top:0;} +.table tbody+tbody{border-top:2px solid #dddddd;} +.table-condensed th,.table-condensed td{padding:4px 5px;} +.table-bordered{border:1px solid #dddddd;border-collapse:separate;*border-collapse:collapsed;border-left:0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}.table-bordered th,.table-bordered td{border-left:1px solid #dddddd;} +.table-bordered caption+thead tr:first-child th,.table-bordered caption+tbody tr:first-child th,.table-bordered caption+tbody tr:first-child td,.table-bordered colgroup+thead tr:first-child th,.table-bordered colgroup+tbody tr:first-child th,.table-bordered colgroup+tbody tr:first-child td,.table-bordered thead:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child td{border-top:0;} +.table-bordered thead:first-child tr:first-child th:first-child,.table-bordered tbody:first-child tr:first-child td:first-child{-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topleft:4px;} +.table-bordered thead:first-child tr:first-child th:last-child,.table-bordered tbody:first-child tr:first-child td:last-child{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-moz-border-radius-topright:4px;} +.table-bordered thead:last-child tr:last-child th:first-child,.table-bordered tbody:last-child tr:last-child td:first-child{-webkit-border-radius:0 0 0 4px;-moz-border-radius:0 0 0 4px;border-radius:0 0 0 4px;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px;} +.table-bordered thead:last-child tr:last-child th:last-child,.table-bordered tbody:last-child tr:last-child td:last-child{-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px;} +.table-striped tbody tr:nth-child(odd) td,.table-striped tbody tr:nth-child(odd) th{background-color:#f9f9f9;} +.table tbody tr:hover td,.table tbody tr:hover th{background-color:#f5f5f5;} +table .span1{float:none;width:44px;margin-left:0;} +table .span2{float:none;width:124px;margin-left:0;} +table .span3{float:none;width:204px;margin-left:0;} +table .span4{float:none;width:284px;margin-left:0;} +table .span5{float:none;width:364px;margin-left:0;} +table .span6{float:none;width:444px;margin-left:0;} +table .span7{float:none;width:524px;margin-left:0;} +table .span8{float:none;width:604px;margin-left:0;} +table .span9{float:none;width:684px;margin-left:0;} +table .span10{float:none;width:764px;margin-left:0;} +table .span11{float:none;width:844px;margin-left:0;} +table .span12{float:none;width:924px;margin-left:0;} +table .span13{float:none;width:1004px;margin-left:0;} +table .span14{float:none;width:1084px;margin-left:0;} +table .span15{float:none;width:1164px;margin-left:0;} +table .span16{float:none;width:1244px;margin-left:0;} +table .span17{float:none;width:1324px;margin-left:0;} +table .span18{float:none;width:1404px;margin-left:0;} +table .span19{float:none;width:1484px;margin-left:0;} +table .span20{float:none;width:1564px;margin-left:0;} +table .span21{float:none;width:1644px;margin-left:0;} +table .span22{float:none;width:1724px;margin-left:0;} +table .span23{float:none;width:1804px;margin-left:0;} +table .span24{float:none;width:1884px;margin-left:0;} +[class^="icon-"],[class*=" icon-"]{display:inline-block;width:14px;height:14px;*margin-right:.3em;line-height:14px;vertical-align:text-top;background-image:url("../../bootstrap/img/glyphicons-halflings.png");background-position:14px 14px;background-repeat:no-repeat;}[class^="icon-"]:last-child,[class*=" icon-"]:last-child{*margin-left:0;} +.icon-white{background-image:url("../../bootstrap/img/glyphicons-halflings-white.png");} +.icon-glass{background-position:0 0;} +.icon-music{background-position:-24px 0;} +.icon-search{background-position:-48px 0;} +.icon-envelope{background-position:-72px 0;} +.icon-heart{background-position:-96px 0;} +.icon-star{background-position:-120px 0;} +.icon-star-empty{background-position:-144px 0;} +.icon-user{background-position:-168px 0;} +.icon-film{background-position:-192px 0;} +.icon-th-large{background-position:-216px 0;} +.icon-th{background-position:-240px 0;} +.icon-th-list{background-position:-264px 0;} +.icon-ok{background-position:-288px 0;} +.icon-remove{background-position:-312px 0;} +.icon-zoom-in{background-position:-336px 0;} +.icon-zoom-out{background-position:-360px 0;} +.icon-off{background-position:-384px 0;} +.icon-signal{background-position:-408px 0;} +.icon-cog{background-position:-432px 0;} +.icon-trash{background-position:-456px 0;} +.icon-home{background-position:0 -24px;} +.icon-file{background-position:-24px -24px;} +.icon-time{background-position:-48px -24px;} +.icon-road{background-position:-72px -24px;} +.icon-download-alt{background-position:-96px -24px;} +.icon-download{background-position:-120px -24px;} +.icon-upload{background-position:-144px -24px;} +.icon-inbox{background-position:-168px -24px;} +.icon-play-circle{background-position:-192px -24px;} +.icon-repeat{background-position:-216px -24px;} +.icon-refresh{background-position:-240px -24px;} +.icon-list-alt{background-position:-264px -24px;} +.icon-lock{background-position:-287px -24px;} +.icon-flag{background-position:-312px -24px;} +.icon-headphones{background-position:-336px -24px;} +.icon-volume-off{background-position:-360px -24px;} +.icon-volume-down{background-position:-384px -24px;} +.icon-volume-up{background-position:-408px -24px;} +.icon-qrcode{background-position:-432px -24px;} +.icon-barcode{background-position:-456px -24px;} +.icon-tag{background-position:0 -48px;} +.icon-tags{background-position:-25px -48px;} +.icon-book{background-position:-48px -48px;} +.icon-bookmark{background-position:-72px -48px;} +.icon-print{background-position:-96px -48px;} +.icon-camera{background-position:-120px -48px;} +.icon-font{background-position:-144px -48px;} +.icon-bold{background-position:-167px -48px;} +.icon-italic{background-position:-192px -48px;} +.icon-text-height{background-position:-216px -48px;} +.icon-text-width{background-position:-240px -48px;} +.icon-align-left{background-position:-264px -48px;} +.icon-align-center{background-position:-288px -48px;} +.icon-align-right{background-position:-312px -48px;} +.icon-align-justify{background-position:-336px -48px;} +.icon-list{background-position:-360px -48px;} +.icon-indent-left{background-position:-384px -48px;} +.icon-indent-right{background-position:-408px -48px;} +.icon-facetime-video{background-position:-432px -48px;} +.icon-picture{background-position:-456px -48px;} +.icon-pencil{background-position:0 -72px;} +.icon-map-marker{background-position:-24px -72px;} +.icon-adjust{background-position:-48px -72px;} +.icon-tint{background-position:-72px -72px;} +.icon-edit{background-position:-96px -72px;} +.icon-share{background-position:-120px -72px;} +.icon-check{background-position:-144px -72px;} +.icon-move{background-position:-168px -72px;} +.icon-step-backward{background-position:-192px -72px;} +.icon-fast-backward{background-position:-216px -72px;} +.icon-backward{background-position:-240px -72px;} +.icon-play{background-position:-264px -72px;} +.icon-pause{background-position:-288px -72px;} +.icon-stop{background-position:-312px -72px;} +.icon-forward{background-position:-336px -72px;} +.icon-fast-forward{background-position:-360px -72px;} +.icon-step-forward{background-position:-384px -72px;} +.icon-eject{background-position:-408px -72px;} +.icon-chevron-left{background-position:-432px -72px;} +.icon-chevron-right{background-position:-456px -72px;} +.icon-plus-sign{background-position:0 -96px;} +.icon-minus-sign{background-position:-24px -96px;} +.icon-remove-sign{background-position:-48px -96px;} +.icon-ok-sign{background-position:-72px -96px;} +.icon-question-sign{background-position:-96px -96px;} +.icon-info-sign{background-position:-120px -96px;} +.icon-screenshot{background-position:-144px -96px;} +.icon-remove-circle{background-position:-168px -96px;} +.icon-ok-circle{background-position:-192px -96px;} +.icon-ban-circle{background-position:-216px -96px;} +.icon-arrow-left{background-position:-240px -96px;} +.icon-arrow-right{background-position:-264px -96px;} +.icon-arrow-up{background-position:-289px -96px;} +.icon-arrow-down{background-position:-312px -96px;} +.icon-share-alt{background-position:-336px -96px;} +.icon-resize-full{background-position:-360px -96px;} +.icon-resize-small{background-position:-384px -96px;} +.icon-plus{background-position:-408px -96px;} +.icon-minus{background-position:-433px -96px;} +.icon-asterisk{background-position:-456px -96px;} +.icon-exclamation-sign{background-position:0 -120px;} +.icon-gift{background-position:-24px -120px;} +.icon-leaf{background-position:-48px -120px;} +.icon-fire{background-position:-72px -120px;} +.icon-eye-open{background-position:-96px -120px;} +.icon-eye-close{background-position:-120px -120px;} +.icon-warning-sign{background-position:-144px -120px;} +.icon-plane{background-position:-168px -120px;} +.icon-calendar{background-position:-192px -120px;} +.icon-random{background-position:-216px -120px;} +.icon-comment{background-position:-240px -120px;} +.icon-magnet{background-position:-264px -120px;} +.icon-chevron-up{background-position:-288px -120px;} +.icon-chevron-down{background-position:-313px -119px;} +.icon-retweet{background-position:-336px -120px;} +.icon-shopping-cart{background-position:-360px -120px;} +.icon-folder-close{background-position:-384px -120px;} +.icon-folder-open{background-position:-408px -120px;} +.icon-resize-vertical{background-position:-432px -119px;} +.icon-resize-horizontal{background-position:-456px -118px;} +.icon-hdd{background-position:0 -144px;} +.icon-bullhorn{background-position:-24px -144px;} +.icon-bell{background-position:-48px -144px;} +.icon-certificate{background-position:-72px -144px;} +.icon-thumbs-up{background-position:-96px -144px;} +.icon-thumbs-down{background-position:-120px -144px;} +.icon-hand-right{background-position:-144px -144px;} +.icon-hand-left{background-position:-168px -144px;} +.icon-hand-up{background-position:-192px -144px;} +.icon-hand-down{background-position:-216px -144px;} +.icon-circle-arrow-right{background-position:-240px -144px;} +.icon-circle-arrow-left{background-position:-264px -144px;} +.icon-circle-arrow-up{background-position:-288px -144px;} +.icon-circle-arrow-down{background-position:-312px -144px;} +.icon-globe{background-position:-336px -144px;} +.icon-wrench{background-position:-360px -144px;} +.icon-tasks{background-position:-384px -144px;} +.icon-filter{background-position:-408px -144px;} +.icon-briefcase{background-position:-432px -144px;} +.icon-fullscreen{background-position:-456px -144px;} +.dropup,.dropdown{position:relative;} +.dropdown-toggle{*margin-bottom:-3px;} +.dropdown-toggle:active,.open .dropdown-toggle{outline:0;} +.caret{display:inline-block;width:0;height:0;vertical-align:top;border-top:4px solid #000000;border-right:4px solid transparent;border-left:4px solid transparent;content:"";opacity:0.3;filter:alpha(opacity=30);} +.dropdown .caret{margin-top:8px;margin-left:2px;} +.dropdown:hover .caret,.open .caret{opacity:1;filter:alpha(opacity=100);} +.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:4px 0;margin:1px 0 0;list-style:none;background-color:#ffffff;border:1px solid #ccc;border:1px solid rgba(0, 0, 0, 0.2);*border-right-width:2px;*border-bottom-width:2px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;-webkit-box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);-moz-box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box;}.dropdown-menu.pull-right{right:0;left:auto;} +.dropdown-menu .divider{*width:100%;height:1px;margin:8px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #ffffff;} +.dropdown-menu a{display:block;padding:3px 15px;clear:both;font-weight:normal;line-height:18px;color:#333333;white-space:nowrap;} +.dropdown-menu li>a:hover,.dropdown-menu .active>a,.dropdown-menu .active>a:hover{color:#ffffff;text-decoration:none;background-color:#4183c4;} +.open{*z-index:1000;}.open >.dropdown-menu{display:block;} +.pull-right>.dropdown-menu{right:0;left:auto;} +.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0;border-bottom:4px solid #000000;content:"\2191";} +.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px;} +.typeahead{margin-top:2px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;} +.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #eee;border:1px solid rgba(0, 0, 0, 0.05);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);}.well blockquote{border-color:#ddd;border-color:rgba(0, 0, 0, 0.15);} +.well-large{padding:24px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;} +.well-small{padding:9px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;} +.fade{opacity:0;-webkit-transition:opacity 0.15s linear;-moz-transition:opacity 0.15s linear;-ms-transition:opacity 0.15s linear;-o-transition:opacity 0.15s linear;transition:opacity 0.15s linear;}.fade.in{opacity:1;} +.collapse{position:relative;height:0;overflow:hidden;-webkit-transition:height 0.35s ease;-moz-transition:height 0.35s ease;-ms-transition:height 0.35s ease;-o-transition:height 0.35s ease;transition:height 0.35s ease;}.collapse.in{height:auto;} +.close{float:right;font-size:20px;font-weight:bold;line-height:18px;color:#000000;text-shadow:0 1px 0 #ffffff;opacity:0.2;filter:alpha(opacity=20);}.close:hover{color:#000000;text-decoration:none;cursor:pointer;opacity:0.4;filter:alpha(opacity=40);} +button.close{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none;} +.btn{display:inline-block;*display:inline;*zoom:1;padding:4px 10px 4px;margin-bottom:0;font-size:13px;line-height:18px;*line-height:20px;color:#333333;text-align:center;text-shadow:0 1px 1px rgba(255, 255, 255, 0.75);vertical-align:middle;cursor:pointer;background-color:#f5f5f5;background-image:-moz-linear-gradient(top, #ffffff, #e6e6e6);background-image:-ms-linear-gradient(top, #ffffff, #e6e6e6);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#e6e6e6));background-image:-webkit-linear-gradient(top, #ffffff, #e6e6e6);background-image:-o-linear-gradient(top, #ffffff, #e6e6e6);background-image:linear-gradient(top, #ffffff, #e6e6e6);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#e6e6e6', GradientType=0);border-color:#e6e6e6 #e6e6e6 #bfbfbf;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#e6e6e6;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);border:1px solid #cccccc;*border:0;border-bottom-color:#b3b3b3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;*margin-left:.3em;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);}.btn:hover,.btn:active,.btn.active,.btn.disabled,.btn[disabled]{background-color:#e6e6e6;*background-color:#d9d9d9;} +.btn:active,.btn.active{background-color:#cccccc \9;} +.btn:first-child{*margin-left:0;} +.btn:hover{color:#333333;text-decoration:none;background-color:#e6e6e6;*background-color:#d9d9d9;background-position:0 -15px;-webkit-transition:background-position 0.1s linear;-moz-transition:background-position 0.1s linear;-ms-transition:background-position 0.1s linear;-o-transition:background-position 0.1s linear;transition:background-position 0.1s linear;} +.btn:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px;} +.btn.active,.btn:active{background-color:#e6e6e6;background-color:#d9d9d9 \9;background-image:none;outline:0;-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05);} +.btn.disabled,.btn[disabled]{cursor:default;background-color:#e6e6e6;background-image:none;opacity:0.65;filter:alpha(opacity=65);-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;} +.btn-large{padding:9px 14px;font-size:15px;line-height:normal;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;} +.btn-large [class^="icon-"]{margin-top:1px;} +.btn-small{padding:5px 9px;font-size:11px;line-height:16px;} +.btn-small [class^="icon-"]{margin-top:-1px;} +.btn-mini{padding:2px 6px;font-size:11px;line-height:14px;} +.btn-primary,.btn-primary:hover,.btn-warning,.btn-warning:hover,.btn-danger,.btn-danger:hover,.btn-success,.btn-success:hover,.btn-info,.btn-info:hover,.btn-inverse,.btn-inverse:hover{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);} +.btn-primary.active,.btn-warning.active,.btn-danger.active,.btn-success.active,.btn-info.active,.btn-inverse.active{color:rgba(255, 255, 255, 0.75);} +.btn{border-color:#ccc;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);} +.btn-primary{background-color:#4176c4;background-image:-moz-linear-gradient(top, #4183c4, #4162c4);background-image:-ms-linear-gradient(top, #4183c4, #4162c4);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#4183c4), to(#4162c4));background-image:-webkit-linear-gradient(top, #4183c4, #4162c4);background-image:-o-linear-gradient(top, #4183c4, #4162c4);background-image:linear-gradient(top, #4183c4, #4162c4);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#4183c4', endColorstr='#4162c4', GradientType=0);border-color:#4162c4 #4162c4 #2c448d;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#4162c4;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-primary:hover,.btn-primary:active,.btn-primary.active,.btn-primary.disabled,.btn-primary[disabled]{background-color:#4162c4;*background-color:#3857b4;} +.btn-primary:active,.btn-primary.active{background-color:#324ea0 \9;} +.btn-warning{background-color:#ff942e;background-image:-moz-linear-gradient(top, #ffa44d, #ff7d00);background-image:-ms-linear-gradient(top, #ffa44d, #ff7d00);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ffa44d), to(#ff7d00));background-image:-webkit-linear-gradient(top, #ffa44d, #ff7d00);background-image:-o-linear-gradient(top, #ffa44d, #ff7d00);background-image:linear-gradient(top, #ffa44d, #ff7d00);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffa44d', endColorstr='#ff7d00', GradientType=0);border-color:#ff7d00 #ff7d00 #b35800;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#ff7d00;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-warning:hover,.btn-warning:active,.btn-warning.active,.btn-warning.disabled,.btn-warning[disabled]{background-color:#ff7d00;*background-color:#e67100;} +.btn-warning:active,.btn-warning.active{background-color:#cc6400 \9;} +.btn-danger{background-color:#da4f49;background-image:-moz-linear-gradient(top, #ee5f5b, #bd362f);background-image:-ms-linear-gradient(top, #ee5f5b, #bd362f);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#bd362f));background-image:-webkit-linear-gradient(top, #ee5f5b, #bd362f);background-image:-o-linear-gradient(top, #ee5f5b, #bd362f);background-image:linear-gradient(top, #ee5f5b, #bd362f);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ee5f5b', endColorstr='#bd362f', GradientType=0);border-color:#bd362f #bd362f #802420;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#bd362f;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-danger:hover,.btn-danger:active,.btn-danger.active,.btn-danger.disabled,.btn-danger[disabled]{background-color:#bd362f;*background-color:#a9302a;} +.btn-danger:active,.btn-danger.active{background-color:#942a25 \9;} +.btn-success{background-color:#5bb75b;background-image:-moz-linear-gradient(top, #62c462, #51a351);background-image:-ms-linear-gradient(top, #62c462, #51a351);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#62c462), to(#51a351));background-image:-webkit-linear-gradient(top, #62c462, #51a351);background-image:-o-linear-gradient(top, #62c462, #51a351);background-image:linear-gradient(top, #62c462, #51a351);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#62c462', endColorstr='#51a351', GradientType=0);border-color:#51a351 #51a351 #387038;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#51a351;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-success:hover,.btn-success:active,.btn-success.active,.btn-success.disabled,.btn-success[disabled]{background-color:#51a351;*background-color:#499249;} +.btn-success:active,.btn-success.active{background-color:#408140 \9;} +.btn-info{background-color:#49afcd;background-image:-moz-linear-gradient(top, #5bc0de, #2f96b4);background-image:-ms-linear-gradient(top, #5bc0de, #2f96b4);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#5bc0de), to(#2f96b4));background-image:-webkit-linear-gradient(top, #5bc0de, #2f96b4);background-image:-o-linear-gradient(top, #5bc0de, #2f96b4);background-image:linear-gradient(top, #5bc0de, #2f96b4);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#5bc0de', endColorstr='#2f96b4', GradientType=0);border-color:#2f96b4 #2f96b4 #1f6377;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#2f96b4;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-info:hover,.btn-info:active,.btn-info.active,.btn-info.disabled,.btn-info[disabled]{background-color:#2f96b4;*background-color:#2a85a0;} +.btn-info:active,.btn-info.active{background-color:#24748c \9;} +.btn-inverse{background-color:#414141;background-image:-moz-linear-gradient(top, #555555, #222222);background-image:-ms-linear-gradient(top, #555555, #222222);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#555555), to(#222222));background-image:-webkit-linear-gradient(top, #555555, #222222);background-image:-o-linear-gradient(top, #555555, #222222);background-image:linear-gradient(top, #555555, #222222);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#555555', endColorstr='#222222', GradientType=0);border-color:#222222 #222222 #000000;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#222222;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-inverse:hover,.btn-inverse:active,.btn-inverse.active,.btn-inverse.disabled,.btn-inverse[disabled]{background-color:#222222;*background-color:#151515;} +.btn-inverse:active,.btn-inverse.active{background-color:#080808 \9;} +button.btn,input[type="submit"].btn{*padding-top:2px;*padding-bottom:2px;}button.btn::-moz-focus-inner,input[type="submit"].btn::-moz-focus-inner{padding:0;border:0;} +button.btn.btn-large,input[type="submit"].btn.btn-large{*padding-top:7px;*padding-bottom:7px;} +button.btn.btn-small,input[type="submit"].btn.btn-small{*padding-top:3px;*padding-bottom:3px;} +button.btn.btn-mini,input[type="submit"].btn.btn-mini{*padding-top:1px;*padding-bottom:1px;} +.btn-group{position:relative;*zoom:1;*margin-left:.3em;}.btn-group:before,.btn-group:after{display:table;content:"";} +.btn-group:after{clear:both;} +.btn-group:first-child{*margin-left:0;} +.btn-group+.btn-group{margin-left:5px;} +.btn-toolbar{margin-top:9px;margin-bottom:9px;}.btn-toolbar .btn-group{display:inline-block;*display:inline;*zoom:1;} +.btn-group>.btn{position:relative;float:left;margin-left:-1px;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;} +.btn-group>.btn:first-child{margin-left:0;-webkit-border-top-left-radius:4px;-moz-border-radius-topleft:4px;border-top-left-radius:4px;-webkit-border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px;border-bottom-left-radius:4px;} +.btn-group>.btn:last-child,.btn-group>.dropdown-toggle{-webkit-border-top-right-radius:4px;-moz-border-radius-topright:4px;border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px;border-bottom-right-radius:4px;} +.btn-group>.btn.large:first-child{margin-left:0;-webkit-border-top-left-radius:6px;-moz-border-radius-topleft:6px;border-top-left-radius:6px;-webkit-border-bottom-left-radius:6px;-moz-border-radius-bottomleft:6px;border-bottom-left-radius:6px;} +.btn-group>.btn.large:last-child,.btn-group>.large.dropdown-toggle{-webkit-border-top-right-radius:6px;-moz-border-radius-topright:6px;border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px;-moz-border-radius-bottomright:6px;border-bottom-right-radius:6px;} +.btn-group>.btn:hover,.btn-group>.btn:focus,.btn-group>.btn:active,.btn-group>.btn.active{z-index:2;} +.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0;} +.btn-group>.dropdown-toggle{padding-left:8px;padding-right:8px;-webkit-box-shadow:inset 1px 0 0 rgba(255,255,255,.125), inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);-moz-box-shadow:inset 1px 0 0 rgba(255,255,255,.125), inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);box-shadow:inset 1px 0 0 rgba(255,255,255,.125), inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);*padding-top:4px;*padding-bottom:4px;} +.btn-group>.btn-mini.dropdown-toggle{padding-left:5px;padding-right:5px;} +.btn-group>.btn-small.dropdown-toggle{*padding-top:4px;*padding-bottom:4px;} +.btn-group>.btn-large.dropdown-toggle{padding-left:12px;padding-right:12px;} +.btn-group.open .dropdown-toggle{background-image:none;-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05);} +.btn-group.open .btn.dropdown-toggle{background-color:#e6e6e6;} +.btn-group.open .btn-primary.dropdown-toggle{background-color:#4162c4;} +.btn-group.open .btn-warning.dropdown-toggle{background-color:#ff7d00;} +.btn-group.open .btn-danger.dropdown-toggle{background-color:#bd362f;} +.btn-group.open .btn-success.dropdown-toggle{background-color:#51a351;} +.btn-group.open .btn-info.dropdown-toggle{background-color:#2f96b4;} +.btn-group.open .btn-inverse.dropdown-toggle{background-color:#222222;} +.btn .caret{margin-top:7px;margin-left:0;} +.btn:hover .caret,.open.btn-group .caret{opacity:1;filter:alpha(opacity=100);} +.btn-mini .caret{margin-top:5px;} +.btn-small .caret{margin-top:6px;} +.btn-large .caret{margin-top:6px;border-left-width:5px;border-right-width:5px;border-top-width:5px;} +.dropup .btn-large .caret{border-bottom:5px solid #000000;border-top:0;} +.btn-primary .caret,.btn-warning .caret,.btn-danger .caret,.btn-info .caret,.btn-success .caret,.btn-inverse .caret{border-top-color:#ffffff;border-bottom-color:#ffffff;opacity:0.75;filter:alpha(opacity=75);} +.alert{padding:8px 35px 8px 14px;margin-bottom:18px;text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);background-color:#f5f3b4;border:1px solid #f3e4a7;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;color:#393939;} +.alert-heading{color:inherit;} +.alert .close{position:relative;top:-2px;right:-21px;line-height:18px;} +.alert-success{background-color:#bedebe;border-color:#b4d5ad;color:#333333;} +.alert-danger,.alert-error{background-color:#ffe9e9;border-color:#ffdae0;color:#393939;} +.alert-info{background-color:#e4f0ff;border-color:#c0e7ff;color:#1b3650;} +.alert-block{padding-top:14px;padding-bottom:14px;} +.alert-block>p,.alert-block>ul{margin-bottom:0;} +.alert-block p+p{margin-top:5px;} +.nav{margin-left:0;margin-bottom:18px;list-style:none;} +.nav>li>a{display:block;} +.nav>li>a:hover{text-decoration:none;background-color:#f5f5f5;} +.nav>.pull-right{float:right;} +.nav .nav-header{display:block;padding:3px 15px;font-size:11px;font-weight:bold;line-height:18px;color:#999999;text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);text-transform:uppercase;} +.nav li+.nav-header{margin-top:9px;} +.nav-list{padding-left:15px;padding-right:15px;margin-bottom:0;} +.nav-list>li>a,.nav-list .nav-header{margin-left:-15px;margin-right:-15px;text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);} +.nav-list>li>a{padding:3px 15px;} +.nav-list>.active>a,.nav-list>.active>a:hover{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.2);background-color:#4183c4;} +.nav-list [class^="icon-"]{margin-right:2px;} +.nav-list .divider{*width:100%;height:1px;margin:8px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #ffffff;} +.nav-tabs,.nav-pills{*zoom:1;}.nav-tabs:before,.nav-pills:before,.nav-tabs:after,.nav-pills:after{display:table;content:"";} +.nav-tabs:after,.nav-pills:after{clear:both;} +.nav-tabs>li,.nav-pills>li{float:left;} +.nav-tabs>li>a,.nav-pills>li>a{padding-right:12px;padding-left:12px;margin-right:2px;line-height:14px;} +.nav-tabs{border-bottom:1px solid #ddd;} +.nav-tabs>li{margin-bottom:-1px;} +.nav-tabs>li>a{padding-top:8px;padding-bottom:8px;line-height:18px;border:1px solid transparent;-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0;}.nav-tabs>li>a:hover{border-color:#f5f5f5 #f5f5f5 #dddddd;} +.nav-tabs>.active>a,.nav-tabs>.active>a:hover{color:#555555;background-color:#ffffff;border:1px solid #ddd;border-bottom-color:transparent;cursor:default;} +.nav-pills>li>a{padding-top:8px;padding-bottom:8px;margin-top:2px;margin-bottom:2px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;} +.nav-pills>.active>a,.nav-pills>.active>a:hover{color:#ffffff;background-color:#4183c4;} +.nav-stacked>li{float:none;} +.nav-stacked>li>a{margin-right:0;} +.nav-tabs.nav-stacked{border-bottom:0;} +.nav-tabs.nav-stacked>li>a{border:1px solid #ddd;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;} +.nav-tabs.nav-stacked>li:first-child>a{-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0;} +.nav-tabs.nav-stacked>li:last-child>a{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px;} +.nav-tabs.nav-stacked>li>a:hover{border-color:#ddd;z-index:2;} +.nav-pills.nav-stacked>li>a{margin-bottom:3px;} +.nav-pills.nav-stacked>li:last-child>a{margin-bottom:1px;} +.nav-tabs .dropdown-menu{-webkit-border-radius:0 0 5px 5px;-moz-border-radius:0 0 5px 5px;border-radius:0 0 5px 5px;} +.nav-pills .dropdown-menu{-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;} +.nav-tabs .dropdown-toggle .caret,.nav-pills .dropdown-toggle .caret{border-top-color:#4183c4;border-bottom-color:#4183c4;margin-top:6px;} +.nav-tabs .dropdown-toggle:hover .caret,.nav-pills .dropdown-toggle:hover .caret{border-top-color:#4183c4;border-bottom-color:#4183c4;} +.nav-tabs .active .dropdown-toggle .caret,.nav-pills .active .dropdown-toggle .caret{border-top-color:#333333;border-bottom-color:#333333;} +.nav>.dropdown.active>a:hover{color:#000000;cursor:pointer;} +.nav-tabs .open .dropdown-toggle,.nav-pills .open .dropdown-toggle,.nav>li.dropdown.open.active>a:hover{color:#ffffff;background-color:#999999;border-color:#999999;} +.nav li.dropdown.open .caret,.nav li.dropdown.open.active .caret,.nav li.dropdown.open a:hover .caret{border-top-color:#ffffff;border-bottom-color:#ffffff;opacity:1;filter:alpha(opacity=100);} +.tabs-stacked .open>a:hover{border-color:#999999;} +.tabbable{*zoom:1;}.tabbable:before,.tabbable:after{display:table;content:"";} +.tabbable:after{clear:both;} +.tab-content{overflow:auto;} +.tabs-below>.nav-tabs,.tabs-right>.nav-tabs,.tabs-left>.nav-tabs{border-bottom:0;} +.tab-content>.tab-pane,.pill-content>.pill-pane{display:none;} +.tab-content>.active,.pill-content>.active{display:block;} +.tabs-below>.nav-tabs{border-top:1px solid #ddd;} +.tabs-below>.nav-tabs>li{margin-top:-1px;margin-bottom:0;} +.tabs-below>.nav-tabs>li>a{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px;}.tabs-below>.nav-tabs>li>a:hover{border-bottom-color:transparent;border-top-color:#ddd;} +.tabs-below>.nav-tabs>.active>a,.tabs-below>.nav-tabs>.active>a:hover{border-color:transparent #ddd #ddd #ddd;} +.tabs-left>.nav-tabs>li,.tabs-right>.nav-tabs>li{float:none;} +.tabs-left>.nav-tabs>li>a,.tabs-right>.nav-tabs>li>a{min-width:74px;margin-right:0;margin-bottom:3px;} +.tabs-left>.nav-tabs{float:left;margin-right:19px;border-right:1px solid #ddd;} +.tabs-left>.nav-tabs>li>a{margin-right:-1px;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px;} +.tabs-left>.nav-tabs>li>a:hover{border-color:#f5f5f5 #dddddd #f5f5f5 #f5f5f5;} +.tabs-left>.nav-tabs .active>a,.tabs-left>.nav-tabs .active>a:hover{border-color:#ddd transparent #ddd #ddd;*border-right-color:#ffffff;} +.tabs-right>.nav-tabs{float:right;margin-left:19px;border-left:1px solid #ddd;} +.tabs-right>.nav-tabs>li>a{margin-left:-1px;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0;} +.tabs-right>.nav-tabs>li>a:hover{border-color:#f5f5f5 #f5f5f5 #f5f5f5 #dddddd;} +.tabs-right>.nav-tabs .active>a,.tabs-right>.nav-tabs .active>a:hover{border-color:#ddd #ddd #ddd transparent;*border-left-color:#ffffff;} +.navbar{*position:relative;*z-index:2;overflow:visible;margin-bottom:18px;} +.navbar-inner{min-height:40px;padding-left:20px;padding-right:20px;background-color:#f7f7f7;background-image:-moz-linear-gradient(top, #ffffff, #eaeaea);background-image:-ms-linear-gradient(top, #ffffff, #eaeaea);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#eaeaea));background-image:-webkit-linear-gradient(top, #ffffff, #eaeaea);background-image:-o-linear-gradient(top, #ffffff, #eaeaea);background-image:linear-gradient(top, #ffffff, #eaeaea);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#eaeaea', GradientType=0);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 3px rgba(0,0,0,.25), inset 0 -1px 0 rgba(0,0,0,.1);-moz-box-shadow:0 1px 3px rgba(0,0,0,.25), inset 0 -1px 0 rgba(0,0,0,.1);box-shadow:0 1px 3px rgba(0,0,0,.25), inset 0 -1px 0 rgba(0,0,0,.1);} +.navbar .container{width:auto;} +.nav-collapse.collapse{height:auto;} +.navbar{color:#222222;}.navbar .brand:hover{text-decoration:none;} +.navbar .brand{float:left;display:block;padding:8px 20px 12px;margin-left:-20px;font-size:20px;font-weight:200;line-height:1;color:#333333;} +.navbar .navbar-text{margin-bottom:0;line-height:40px;} +.navbar .navbar-link{color:#222222;}.navbar .navbar-link:hover{color:#4183c4;} +.navbar .btn,.navbar .btn-group{margin-top:5px;} +.navbar .btn-group .btn{margin:0;} +.navbar-form{margin-bottom:0;*zoom:1;}.navbar-form:before,.navbar-form:after{display:table;content:"";} +.navbar-form:after{clear:both;} +.navbar-form input,.navbar-form select,.navbar-form .radio,.navbar-form .checkbox{margin-top:5px;} +.navbar-form input,.navbar-form select{display:inline-block;margin-bottom:0;} +.navbar-form input[type="image"],.navbar-form input[type="checkbox"],.navbar-form input[type="radio"]{margin-top:3px;} +.navbar-form .input-append,.navbar-form .input-prepend{margin-top:6px;white-space:nowrap;}.navbar-form .input-append input,.navbar-form .input-prepend input{margin-top:0;} +.navbar-search{position:relative;float:left;margin-top:6px;margin-bottom:0;}.navbar-search .search-query{padding:4px 9px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;font-weight:normal;line-height:1;color:#ffffff;background-color:#ffffff;border:1px solid #b3b3b3;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1), 0 1px 0 rgba(255,255,255,.15);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,.1), 0 1px 0 rgba(255,255,255,.15);box-shadow:inset 0 1px 2px rgba(0,0,0,.1), 0 1px 0 rgba(255,255,255,.15);-webkit-transition:none;-moz-transition:none;-ms-transition:none;-o-transition:none;transition:none;}.navbar-search .search-query:-moz-placeholder{color:#cccccc;} +.navbar-search .search-query:-ms-input-placeholder{color:#cccccc;} +.navbar-search .search-query::-webkit-input-placeholder{color:#cccccc;} +.navbar-search .search-query:focus,.navbar-search .search-query.focused{padding:5px 10px;color:#333333;text-shadow:0 1px 0 #ffffff;background-color:#ffffff;border:0;-webkit-box-shadow:0 0 3px rgba(0, 0, 0, 0.15);-moz-box-shadow:0 0 3px rgba(0, 0, 0, 0.15);box-shadow:0 0 3px rgba(0, 0, 0, 0.15);outline:0;} +.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030;margin-bottom:0;} +.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding-left:0;padding-right:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;} +.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px;} +.navbar-fixed-top{top:0;} +.navbar-fixed-bottom{bottom:0;} +.navbar .nav{position:relative;left:0;display:block;float:left;margin:0 10px 0 0;} +.navbar .nav.pull-right{float:right;} +.navbar .nav>li{display:block;float:left;} +.navbar .nav>li>a{float:none;padding:9px 10px 11px;line-height:19px;color:#222222;text-decoration:none;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);} +.navbar .btn{display:inline-block;padding:4px 10px 4px;margin:5px 5px 6px;line-height:18px;} +.navbar .btn-group{margin:0;padding:5px 5px 6px;} +.navbar .nav>li>a:hover{background-color:transparent;color:#4183c4;text-decoration:none;} +.navbar .nav .active>a,.navbar .nav .active>a:hover{color:#4183c4;text-decoration:none;background-color:#eaeaea;} +.navbar .divider-vertical{height:40px;width:1px;margin:0 9px;overflow:hidden;background-color:#eaeaea;border-right:1px solid #ffffff;} +.navbar .nav.pull-right{margin-left:10px;margin-right:0;} +.navbar .btn-navbar{display:none;float:right;padding:7px 10px;margin-left:5px;margin-right:5px;background-color:#f7f7f7;background-image:-moz-linear-gradient(top, #ffffff, #eaeaea);background-image:-ms-linear-gradient(top, #ffffff, #eaeaea);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#eaeaea));background-image:-webkit-linear-gradient(top, #ffffff, #eaeaea);background-image:-o-linear-gradient(top, #ffffff, #eaeaea);background-image:linear-gradient(top, #ffffff, #eaeaea);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#eaeaea', GradientType=0);border-color:#eaeaea #eaeaea #c4c4c4;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#eaeaea;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.075);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.075);}.navbar .btn-navbar:hover,.navbar .btn-navbar:active,.navbar .btn-navbar.active,.navbar .btn-navbar.disabled,.navbar .btn-navbar[disabled]{background-color:#eaeaea;*background-color:#dddddd;} +.navbar .btn-navbar:active,.navbar .btn-navbar.active{background-color:#d1d1d1 \9;} +.navbar .btn-navbar .icon-bar{display:block;width:18px;height:2px;background-color:#f5f5f5;-webkit-border-radius:1px;-moz-border-radius:1px;border-radius:1px;-webkit-box-shadow:0 1px 0 rgba(0, 0, 0, 0.25);-moz-box-shadow:0 1px 0 rgba(0, 0, 0, 0.25);box-shadow:0 1px 0 rgba(0, 0, 0, 0.25);} +.btn-navbar .icon-bar+.icon-bar{margin-top:3px;} +.navbar .dropdown-menu:before{content:'';display:inline-block;border-left:7px solid transparent;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-bottom-color:rgba(0, 0, 0, 0.2);position:absolute;top:-7px;left:9px;} +.navbar .dropdown-menu:after{content:'';display:inline-block;border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #ffffff;position:absolute;top:-6px;left:10px;} +.navbar-fixed-bottom .dropdown-menu:before{border-top:7px solid #ccc;border-top-color:rgba(0, 0, 0, 0.2);border-bottom:0;bottom:-7px;top:auto;} +.navbar-fixed-bottom .dropdown-menu:after{border-top:6px solid #ffffff;border-bottom:0;bottom:-6px;top:auto;} +.navbar .nav li.dropdown .dropdown-toggle .caret,.navbar .nav li.dropdown.open .caret{border-top-color:#ffffff;border-bottom-color:#ffffff;} +.navbar .nav li.dropdown.active .caret{opacity:1;filter:alpha(opacity=100);} +.navbar .nav li.dropdown.open>.dropdown-toggle,.navbar .nav li.dropdown.active>.dropdown-toggle,.navbar .nav li.dropdown.open.active>.dropdown-toggle{background-color:transparent;} +.navbar .nav li.dropdown.active>.dropdown-toggle:hover{color:#ffffff;} +.navbar .pull-right .dropdown-menu,.navbar .dropdown-menu.pull-right{left:auto;right:0;}.navbar .pull-right .dropdown-menu:before,.navbar .dropdown-menu.pull-right:before{left:auto;right:12px;} +.navbar .pull-right .dropdown-menu:after,.navbar .dropdown-menu.pull-right:after{left:auto;right:13px;} +.breadcrumb{padding:7px 14px;margin:0 0 18px;list-style:none;background-color:#fbfbfb;background-image:-moz-linear-gradient(top, #ffffff, #f5f5f5);background-image:-ms-linear-gradient(top, #ffffff, #f5f5f5);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#f5f5f5));background-image:-webkit-linear-gradient(top, #ffffff, #f5f5f5);background-image:-o-linear-gradient(top, #ffffff, #f5f5f5);background-image:linear-gradient(top, #ffffff, #f5f5f5);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#f5f5f5', GradientType=0);border:1px solid #ddd;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:inset 0 1px 0 #ffffff;-moz-box-shadow:inset 0 1px 0 #ffffff;box-shadow:inset 0 1px 0 #ffffff;}.breadcrumb li{display:inline-block;*display:inline;*zoom:1;text-shadow:0 1px 0 #ffffff;} +.breadcrumb .divider{padding:0 5px;color:#999999;} +.breadcrumb .active a{color:#333333;} +.pagination{height:36px;margin:18px 0;} +.pagination ul{display:inline-block;*display:inline;*zoom:1;margin-left:0;margin-bottom:0;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);} +.pagination li{display:inline;} +.pagination a{float:left;padding:0 14px;line-height:34px;text-decoration:none;border:1px solid #ddd;border-left-width:0;} +.pagination a:hover,.pagination .active a{background-color:#f5f5f5;} +.pagination .active a{color:#999999;cursor:default;} +.pagination .disabled span,.pagination .disabled a,.pagination .disabled a:hover{color:#999999;background-color:transparent;cursor:default;} +.pagination li:first-child a{border-left-width:1px;-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;} +.pagination li:last-child a{-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0;} +.pagination-centered{text-align:center;} +.pagination-right{text-align:right;} +.pager{margin-left:0;margin-bottom:18px;list-style:none;text-align:center;*zoom:1;}.pager:before,.pager:after{display:table;content:"";} +.pager:after{clear:both;} +.pager li{display:inline;} +.pager a{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px;} +.pager a:hover{text-decoration:none;background-color:#f5f5f5;} +.pager .next a{float:right;} +.pager .previous a{float:left;} +.pager .disabled a,.pager .disabled a:hover{color:#999999;background-color:#fff;cursor:default;} +.modal-open .dropdown-menu{z-index:2050;} +.modal-open .dropdown.open{*z-index:2050;} +.modal-open .popover{z-index:2060;} +.modal-open .tooltip{z-index:2070;} +.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000000;}.modal-backdrop.fade{opacity:0;} +.modal-backdrop,.modal-backdrop.fade.in{opacity:0.8;filter:alpha(opacity=80);} +.modal{position:fixed;top:50%;left:50%;z-index:1050;overflow:auto;width:560px;margin:-250px 0 0 -280px;background-color:#ffffff;border:1px solid #999;border:1px solid rgba(0, 0, 0, 0.3);*border:1px solid #999;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-moz-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box;}.modal.fade{-webkit-transition:opacity .3s linear, top .3s ease-out;-moz-transition:opacity .3s linear, top .3s ease-out;-ms-transition:opacity .3s linear, top .3s ease-out;-o-transition:opacity .3s linear, top .3s ease-out;transition:opacity .3s linear, top .3s ease-out;top:-25%;} +.modal.fade.in{top:50%;} +.modal-header{padding:9px 15px;border-bottom:1px solid #eee;}.modal-header .close{margin-top:2px;} +.modal-body{overflow-y:auto;max-height:400px;padding:15px;} +.modal-form{margin-bottom:0;} +.modal-footer{padding:14px 15px 15px;margin-bottom:0;text-align:right;background-color:#f5f5f5;border-top:1px solid #ddd;-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;-webkit-box-shadow:inset 0 1px 0 #ffffff;-moz-box-shadow:inset 0 1px 0 #ffffff;box-shadow:inset 0 1px 0 #ffffff;*zoom:1;}.modal-footer:before,.modal-footer:after{display:table;content:"";} +.modal-footer:after{clear:both;} +.modal-footer .btn+.btn{margin-left:5px;margin-bottom:0;} +.modal-footer .btn-group .btn+.btn{margin-left:-1px;} +.tooltip{position:absolute;z-index:1020;display:block;visibility:visible;padding:5px;font-size:11px;opacity:0;filter:alpha(opacity=0);}.tooltip.in{opacity:0.8;filter:alpha(opacity=80);} +.tooltip.top{margin-top:-2px;} +.tooltip.right{margin-left:2px;} +.tooltip.bottom{margin-top:2px;} +.tooltip.left{margin-left:-2px;} +.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-top:5px solid #000000;} +.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-left:5px solid #000000;} +.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-bottom:5px solid #000000;} +.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-right:5px solid #000000;} +.tooltip-inner{max-width:200px;padding:3px 8px;color:#ffffff;text-align:center;text-decoration:none;background-color:#000000;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;} +.tooltip-arrow{position:absolute;width:0;height:0;} +.popover{position:absolute;top:0;left:0;z-index:1010;display:none;padding:5px;}.popover.top{margin-top:-5px;} +.popover.right{margin-left:5px;} +.popover.bottom{margin-top:5px;} +.popover.left{margin-left:-5px;} +.popover.top .arrow{bottom:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-top:5px solid #000000;} +.popover.right .arrow{top:50%;left:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-right:5px solid #000000;} +.popover.bottom .arrow{top:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-bottom:5px solid #000000;} +.popover.left .arrow{top:50%;right:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-left:5px solid #000000;} +.popover .arrow{position:absolute;width:0;height:0;} +.popover-inner{padding:3px;width:280px;overflow:hidden;background:#000000;background:rgba(0, 0, 0, 0.8);-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-moz-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);} +.popover-title{padding:9px 15px;line-height:1;background-color:#f5f5f5;border-bottom:1px solid #eee;-webkit-border-radius:3px 3px 0 0;-moz-border-radius:3px 3px 0 0;border-radius:3px 3px 0 0;} +.popover-content{padding:14px;background-color:#ffffff;-webkit-border-radius:0 0 3px 3px;-moz-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px;-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box;}.popover-content p,.popover-content ul,.popover-content ol{margin-bottom:0;} +.thumbnails{margin-left:-20px;list-style:none;*zoom:1;}.thumbnails:before,.thumbnails:after{display:table;content:"";} +.thumbnails:after{clear:both;} +.row-fluid .thumbnails{margin-left:0;} +.thumbnails>li{float:left;margin-bottom:18px;margin-left:20px;} +.thumbnail{display:block;padding:4px;line-height:1;border:1px solid #ddd;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow:0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:0 1px 1px rgba(0, 0, 0, 0.075);} +a.thumbnail:hover{border-color:#4183c4;-webkit-box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);-moz-box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);} +.thumbnail>img{display:block;max-width:100%;margin-left:auto;margin-right:auto;} +.thumbnail .caption{padding:9px;} +.label,.badge{font-size:10.998px;font-weight:bold;line-height:14px;color:#ffffff;vertical-align:baseline;white-space:nowrap;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#999999;} +.label{padding:1px 4px 2px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;} +.badge{padding:1px 9px 2px;-webkit-border-radius:9px;-moz-border-radius:9px;border-radius:9px;} +a.label:hover,a.badge:hover{color:#ffffff;text-decoration:none;cursor:pointer;} +.label-important,.badge-important{background-color:#393939;} +.label-important[href],.badge-important[href]{background-color:#202020;} +.label-warning,.badge-warning{background-color:#ff7d00;} +.label-warning[href],.badge-warning[href]{background-color:#cc6400;} +.label-success,.badge-success{background-color:#333333;} +.label-success[href],.badge-success[href]{background-color:#1a1a1a;} +.label-info,.badge-info{background-color:#1b3650;} +.label-info[href],.badge-info[href]{background-color:#0e1c2a;} +.label-inverse,.badge-inverse{background-color:#333333;} +.label-inverse[href],.badge-inverse[href]{background-color:#1a1a1a;} +@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0;} to{background-position:0 0;}}@-moz-keyframes progress-bar-stripes{from{background-position:40px 0;} to{background-position:0 0;}}@-ms-keyframes progress-bar-stripes{from{background-position:40px 0;} to{background-position:0 0;}}@-o-keyframes progress-bar-stripes{from{background-position:0 0;} to{background-position:40px 0;}}@keyframes progress-bar-stripes{from{background-position:40px 0;} to{background-position:0 0;}}.progress{overflow:hidden;height:18px;margin-bottom:18px;background-color:#f7f7f7;background-image:-moz-linear-gradient(top, #f5f5f5, #f9f9f9);background-image:-ms-linear-gradient(top, #f5f5f5, #f9f9f9);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#f5f5f5), to(#f9f9f9));background-image:-webkit-linear-gradient(top, #f5f5f5, #f9f9f9);background-image:-o-linear-gradient(top, #f5f5f5, #f9f9f9);background-image:linear-gradient(top, #f5f5f5, #f9f9f9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#f5f5f5', endColorstr='#f9f9f9', GradientType=0);-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1);-moz-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;} +.progress .bar{width:0%;height:18px;color:#ffffff;font-size:12px;text-align:center;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#0e90d2;background-image:-moz-linear-gradient(top, #149bdf, #0480be);background-image:-ms-linear-gradient(top, #149bdf, #0480be);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#149bdf), to(#0480be));background-image:-webkit-linear-gradient(top, #149bdf, #0480be);background-image:-o-linear-gradient(top, #149bdf, #0480be);background-image:linear-gradient(top, #149bdf, #0480be);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#149bdf', endColorstr='#0480be', GradientType=0);-webkit-box-shadow:inset 0 -1px 0 rgba(0, 0, 0, 0.15);-moz-box-shadow:inset 0 -1px 0 rgba(0, 0, 0, 0.15);box-shadow:inset 0 -1px 0 rgba(0, 0, 0, 0.15);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box;-webkit-transition:width 0.6s ease;-moz-transition:width 0.6s ease;-ms-transition:width 0.6s ease;-o-transition:width 0.6s ease;transition:width 0.6s ease;} +.progress-striped .bar{background-color:#149bdf;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-ms-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);-webkit-background-size:40px 40px;-moz-background-size:40px 40px;-o-background-size:40px 40px;background-size:40px 40px;} +.progress.active .bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-moz-animation:progress-bar-stripes 2s linear infinite;-ms-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite;} +.progress-danger .bar{background-color:#dd514c;background-image:-moz-linear-gradient(top, #ee5f5b, #c43c35);background-image:-ms-linear-gradient(top, #ee5f5b, #c43c35);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#c43c35));background-image:-webkit-linear-gradient(top, #ee5f5b, #c43c35);background-image:-o-linear-gradient(top, #ee5f5b, #c43c35);background-image:linear-gradient(top, #ee5f5b, #c43c35);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ee5f5b', endColorstr='#c43c35', GradientType=0);} +.progress-danger.progress-striped .bar{background-color:#ee5f5b;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-ms-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);} +.progress-success .bar{background-color:#5eb95e;background-image:-moz-linear-gradient(top, #62c462, #57a957);background-image:-ms-linear-gradient(top, #62c462, #57a957);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#62c462), to(#57a957));background-image:-webkit-linear-gradient(top, #62c462, #57a957);background-image:-o-linear-gradient(top, #62c462, #57a957);background-image:linear-gradient(top, #62c462, #57a957);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#62c462', endColorstr='#57a957', GradientType=0);} +.progress-success.progress-striped .bar{background-color:#62c462;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-ms-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);} +.progress-info .bar{background-color:#4bb1cf;background-image:-moz-linear-gradient(top, #5bc0de, #339bb9);background-image:-ms-linear-gradient(top, #5bc0de, #339bb9);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#5bc0de), to(#339bb9));background-image:-webkit-linear-gradient(top, #5bc0de, #339bb9);background-image:-o-linear-gradient(top, #5bc0de, #339bb9);background-image:linear-gradient(top, #5bc0de, #339bb9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#5bc0de', endColorstr='#339bb9', GradientType=0);} +.progress-info.progress-striped .bar{background-color:#5bc0de;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-ms-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);} +.progress-warning .bar{background-color:#ff942e;background-image:-moz-linear-gradient(top, #ffa44d, #ff7d00);background-image:-ms-linear-gradient(top, #ffa44d, #ff7d00);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ffa44d), to(#ff7d00));background-image:-webkit-linear-gradient(top, #ffa44d, #ff7d00);background-image:-o-linear-gradient(top, #ffa44d, #ff7d00);background-image:linear-gradient(top, #ffa44d, #ff7d00);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffa44d', endColorstr='#ff7d00', GradientType=0);} +.progress-warning.progress-striped .bar{background-color:#ffa44d;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-ms-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);} +.accordion{margin-bottom:18px;} +.accordion-group{margin-bottom:2px;border:1px solid #e5e5e5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;} +.accordion-heading{border-bottom:0;} +.accordion-heading .accordion-toggle{display:block;padding:8px 15px;} +.accordion-toggle{cursor:pointer;} +.accordion-inner{padding:9px 15px;border-top:1px solid #e5e5e5;} +.carousel{position:relative;margin-bottom:18px;line-height:1;} +.carousel-inner{overflow:hidden;width:100%;position:relative;} +.carousel .item{display:none;position:relative;-webkit-transition:0.6s ease-in-out left;-moz-transition:0.6s ease-in-out left;-ms-transition:0.6s ease-in-out left;-o-transition:0.6s ease-in-out left;transition:0.6s ease-in-out left;} +.carousel .item>img{display:block;line-height:1;} +.carousel .active,.carousel .next,.carousel .prev{display:block;} +.carousel .active{left:0;} +.carousel .next,.carousel .prev{position:absolute;top:0;width:100%;} +.carousel .next{left:100%;} +.carousel .prev{left:-100%;} +.carousel .next.left,.carousel .prev.right{left:0;} +.carousel .active.left{left:-100%;} +.carousel .active.right{left:100%;} +.carousel-control{position:absolute;top:40%;left:15px;width:40px;height:40px;margin-top:-20px;font-size:60px;font-weight:100;line-height:30px;color:#ffffff;text-align:center;background:#222222;border:3px solid #ffffff;-webkit-border-radius:23px;-moz-border-radius:23px;border-radius:23px;opacity:0.5;filter:alpha(opacity=50);}.carousel-control.right{left:auto;right:15px;} +.carousel-control:hover{color:#ffffff;text-decoration:none;opacity:0.9;filter:alpha(opacity=90);} +.carousel-caption{position:absolute;left:0;right:0;bottom:0;padding:10px 15px 5px;background:#333333;background:rgba(0, 0, 0, 0.75);} +.carousel-caption h4,.carousel-caption p{color:#ffffff;} +.hero-unit{padding:60px;margin-bottom:30px;background-color:#f5f5f5;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;}.hero-unit h1{margin-bottom:0;font-size:60px;line-height:1;color:inherit;letter-spacing:-1px;} +.hero-unit p{font-size:18px;font-weight:200;line-height:27px;color:inherit;} +.pull-right{float:right;} +.pull-left{float:left;} +.hide{display:none;} +.show{display:block;} +.invisible{visibility:hidden;} +.hidden{display:none;visibility:hidden;} +.visible-phone{display:none !important;} +.visible-tablet{display:none !important;} +.hidden-desktop{display:none !important;} +@media (max-width:767px){.visible-phone{display:inherit !important;} .hidden-phone{display:none !important;} .hidden-desktop{display:inherit !important;} .visible-desktop{display:none !important;}}@media (min-width:768px) and (max-width:979px){.visible-tablet{display:inherit !important;} .hidden-tablet{display:none !important;} .hidden-desktop{display:inherit !important;} .visible-desktop{display:none !important ;}}@media (max-width:480px){.nav-collapse{-webkit-transform:translate3d(0, 0, 0);} .page-header h1 small{display:block;line-height:18px;} input[type="checkbox"],input[type="radio"]{border:1px solid #ccc;} .form-horizontal .control-group>label{float:none;width:auto;padding-top:0;text-align:left;} .form-horizontal .controls{margin-left:0;} .form-horizontal .control-list{padding-top:0;} .form-horizontal .form-actions{padding-left:10px;padding-right:10px;} .modal{position:absolute;top:10px;left:10px;right:10px;width:auto;margin:0;}.modal.fade.in{top:auto;} .modal-header .close{padding:10px;margin:-10px;} .carousel-caption{position:static;}}@media (max-width:767px){body{padding-left:20px;padding-right:20px;} .navbar-fixed-top,.navbar-fixed-bottom{margin-left:-20px;margin-right:-20px;} .container-fluid{padding:0;} .dl-horizontal dt{float:none;clear:none;width:auto;text-align:left;} .dl-horizontal dd{margin-left:0;} .container{width:auto;} .row-fluid{width:100%;} .row,.thumbnails{margin-left:0;} [class*="span"],.row-fluid [class*="span"]{float:none;display:block;width:auto;margin-left:0;} .input-large,.input-xlarge,.input-xxlarge,input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input{display:block;width:100%;min-height:28px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box;} .input-prepend input,.input-append input,.input-prepend input[class*="span"],.input-append input[class*="span"]{display:inline-block;width:auto;}}@media (min-width:768px) and (max-width:979px){.row{margin-left:-20px;*zoom:1;}.row:before,.row:after{display:table;content:"";} .row:after{clear:both;} [class*="span"]{float:left;margin-left:20px;} .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:724px;} .span12{width:724px;} .span11{width:662px;} .span10{width:600px;} .span9{width:538px;} .span8{width:476px;} .span7{width:414px;} .span6{width:352px;} .span5{width:290px;} .span4{width:228px;} .span3{width:166px;} .span2{width:104px;} .span1{width:42px;} .offset12{margin-left:764px;} .offset11{margin-left:702px;} .offset10{margin-left:640px;} .offset9{margin-left:578px;} .offset8{margin-left:516px;} .offset7{margin-left:454px;} .offset6{margin-left:392px;} .offset5{margin-left:330px;} .offset4{margin-left:268px;} .offset3{margin-left:206px;} .offset2{margin-left:144px;} .offset1{margin-left:82px;} .row-fluid{width:100%;*zoom:1;}.row-fluid:before,.row-fluid:after{display:table;content:"";} .row-fluid:after{clear:both;} .row-fluid [class*="span"]{display:block;width:100%;min-height:28px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box;float:left;margin-left:2.762430939%;*margin-left:2.709239449638298%;} .row-fluid [class*="span"]:first-child{margin-left:0;} .row-fluid .span12{width:99.999999993%;*width:99.9468085036383%;} .row-fluid .span11{width:91.436464082%;*width:91.38327259263829%;} .row-fluid .span10{width:82.87292817100001%;*width:82.8197366816383%;} .row-fluid .span9{width:74.30939226%;*width:74.25620077063829%;} .row-fluid .span8{width:65.74585634900001%;*width:65.6926648596383%;} .row-fluid .span7{width:57.182320438000005%;*width:57.129128948638304%;} .row-fluid .span6{width:48.618784527%;*width:48.5655930376383%;} .row-fluid .span5{width:40.055248616%;*width:40.0020571266383%;} .row-fluid .span4{width:31.491712705%;*width:31.4385212156383%;} .row-fluid .span3{width:22.928176794%;*width:22.874985304638297%;} .row-fluid .span2{width:14.364640883%;*width:14.311449393638298%;} .row-fluid .span1{width:5.801104972%;*width:5.747913482638298%;} input,textarea,.uneditable-input{margin-left:0;} input.span12, textarea.span12, .uneditable-input.span12{width:714px;} input.span11, textarea.span11, .uneditable-input.span11{width:652px;} input.span10, textarea.span10, .uneditable-input.span10{width:590px;} input.span9, textarea.span9, .uneditable-input.span9{width:528px;} input.span8, textarea.span8, .uneditable-input.span8{width:466px;} input.span7, textarea.span7, .uneditable-input.span7{width:404px;} input.span6, textarea.span6, .uneditable-input.span6{width:342px;} input.span5, textarea.span5, .uneditable-input.span5{width:280px;} input.span4, textarea.span4, .uneditable-input.span4{width:218px;} input.span3, textarea.span3, .uneditable-input.span3{width:156px;} input.span2, textarea.span2, .uneditable-input.span2{width:94px;} input.span1, textarea.span1, .uneditable-input.span1{width:32px;}}@media (min-width:1200px){.row{margin-left:-30px;*zoom:1;}.row:before,.row:after{display:table;content:"";} .row:after{clear:both;} [class*="span"]{float:left;margin-left:30px;} .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:1170px;} .span12{width:1170px;} .span11{width:1070px;} .span10{width:970px;} .span9{width:870px;} .span8{width:770px;} .span7{width:670px;} .span6{width:570px;} .span5{width:470px;} .span4{width:370px;} .span3{width:270px;} .span2{width:170px;} .span1{width:70px;} .offset12{margin-left:1230px;} .offset11{margin-left:1130px;} .offset10{margin-left:1030px;} .offset9{margin-left:930px;} .offset8{margin-left:830px;} .offset7{margin-left:730px;} .offset6{margin-left:630px;} .offset5{margin-left:530px;} .offset4{margin-left:430px;} .offset3{margin-left:330px;} .offset2{margin-left:230px;} .offset1{margin-left:130px;} .row-fluid{width:100%;*zoom:1;}.row-fluid:before,.row-fluid:after{display:table;content:"";} .row-fluid:after{clear:both;} .row-fluid [class*="span"]{display:block;width:100%;min-height:28px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box;float:left;margin-left:2.564102564%;*margin-left:2.510911074638298%;} .row-fluid [class*="span"]:first-child{margin-left:0;} .row-fluid .span12{width:100%;*width:99.94680851063829%;} .row-fluid .span11{width:91.45299145300001%;*width:91.3997999636383%;} .row-fluid .span10{width:82.905982906%;*width:82.8527914166383%;} .row-fluid .span9{width:74.358974359%;*width:74.30578286963829%;} .row-fluid .span8{width:65.81196581200001%;*width:65.7587743226383%;} .row-fluid .span7{width:57.264957265%;*width:57.2117657756383%;} .row-fluid .span6{width:48.717948718%;*width:48.6647572286383%;} .row-fluid .span5{width:40.170940171000005%;*width:40.117748681638304%;} .row-fluid .span4{width:31.623931624%;*width:31.5707401346383%;} .row-fluid .span3{width:23.076923077%;*width:23.0237315876383%;} .row-fluid .span2{width:14.529914530000001%;*width:14.4767230406383%;} .row-fluid .span1{width:5.982905983%;*width:5.929714493638298%;} input,textarea,.uneditable-input{margin-left:0;} input.span12, textarea.span12, .uneditable-input.span12{width:1160px;} input.span11, textarea.span11, .uneditable-input.span11{width:1060px;} input.span10, textarea.span10, .uneditable-input.span10{width:960px;} input.span9, textarea.span9, .uneditable-input.span9{width:860px;} input.span8, textarea.span8, .uneditable-input.span8{width:760px;} input.span7, textarea.span7, .uneditable-input.span7{width:660px;} input.span6, textarea.span6, .uneditable-input.span6{width:560px;} input.span5, textarea.span5, .uneditable-input.span5{width:460px;} input.span4, textarea.span4, .uneditable-input.span4{width:360px;} input.span3, textarea.span3, .uneditable-input.span3{width:260px;} input.span2, textarea.span2, .uneditable-input.span2{width:160px;} input.span1, textarea.span1, .uneditable-input.span1{width:60px;} .thumbnails{margin-left:-30px;} .thumbnails>li{margin-left:30px;} .row-fluid .thumbnails{margin-left:0;}}@media (max-width:979px){body{padding-top:0;} .navbar-fixed-top,.navbar-fixed-bottom{position:static;} .navbar-fixed-top{margin-bottom:18px;} .navbar-fixed-bottom{margin-top:18px;} .navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding:5px;} .navbar .container{width:auto;padding:0;} .navbar .brand{padding-left:10px;padding-right:10px;margin:0 0 0 -5px;} .nav-collapse{clear:both;} .nav-collapse .nav{float:none;margin:0 0 9px;} .nav-collapse .nav>li{float:none;} .nav-collapse .nav>li>a{margin-bottom:2px;} .nav-collapse .nav>.divider-vertical{display:none;} .nav-collapse .nav .nav-header{color:#222222;text-shadow:none;} .nav-collapse .nav>li>a,.nav-collapse .dropdown-menu a{padding:6px 15px;font-weight:bold;color:#222222;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;} .nav-collapse .btn{padding:4px 10px 4px;font-weight:normal;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;} .nav-collapse .dropdown-menu li+li a{margin-bottom:2px;} .nav-collapse .nav>li>a:hover,.nav-collapse .dropdown-menu a:hover{background-color:#eaeaea;} .nav-collapse.in .btn-group{margin-top:5px;padding:0;} .nav-collapse .dropdown-menu{position:static;top:auto;left:auto;float:none;display:block;max-width:none;margin:0 15px;padding:0;background-color:transparent;border:none;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;} .nav-collapse .dropdown-menu:before,.nav-collapse .dropdown-menu:after{display:none;} .nav-collapse .dropdown-menu .divider{display:none;} .nav-collapse .navbar-form,.nav-collapse .navbar-search{float:none;padding:9px 15px;margin:9px 0;border-top:1px solid #eaeaea;border-bottom:1px solid #eaeaea;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.1);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.1);} .navbar .nav-collapse .nav.pull-right{float:none;margin-left:0;} .nav-collapse,.nav-collapse.collapse{overflow:hidden;height:0;} .navbar .btn-navbar{display:block;} .navbar-static .navbar-inner{padding-left:10px;padding-right:10px;}}@media (min-width:980px){.nav-collapse.collapse{height:auto !important;overflow:visible !important;}}.navbar .icon-bar{color:#4183c4;} +.navbar .brand{font-weight:bold;}.navbar .brand:hover{color:#4183c4;} +.navbar .navbar-text{line-height:38px;padding:0 10px;} +.navbar .nav>li>a{font-weight:bold;text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);} +.navbar .nav .active>a{background-color:transparent;color:#333333;}.navbar .nav .active>a:hover{background-color:transparent;color:#4183c4;} +.navbar .nav>li>a:hover,.navbar .nav li.open.dropdown .dropdown-toggle{color:#4183c4;} +.navbar .nav .dropdown-toggle .caret{border-top-color:#333333;opacity:1;} +.navbar .navbar-search .search-query,.navbar .navbar-search .search-query:hover{border:none;color:#999999;-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.5);-moz-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.5);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.5);}.navbar .navbar-search .search-query:-moz-placeholder,.navbar .navbar-search .search-query:hover:-moz-placeholder{color:#999999;} +.navbar .navbar-search .search-query:-ms-input-placeholder,.navbar .navbar-search .search-query:hover:-ms-input-placeholder{color:#999999;} +.navbar .navbar-search .search-query::-webkit-input-placeholder,.navbar .navbar-search .search-query:hover::-webkit-input-placeholder{color:#999999;} +.navbar .navbar-search .search-query:focus,.navbar .navbar-search .search-query:hover:focus,.navbar .navbar-search .search-query.focused,.navbar .navbar-search .search-query:hover.focused{-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.5);-moz-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.5);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.5);color:#333333;} +.navbar .nav-collapse.in .navbar-search{border-top:none;border-bottom:none;} +.navbar .nav-collapse.in>.nav li>a{color:#333333;}.navbar .nav-collapse.in>.nav li>a .caret{border-top-color:#999999;} +.navbar .nav-collapse.in>.nav li>a:hover{text-shadow:none;color:#4183c4;background-color:transparent;}.navbar .nav-collapse.in>.nav li>a:hover .caret{border-top-color:#ffffff;} +.nav li.open.dropdown .caret,.nav .dropdown-toggle:hover .caret{border-top-color:#4183c4;} +div.subnav .nav>li>a{font-weight:bold;color:#333333;}div.subnav .nav>li>a:hover{color:#4183c4;} +div.subnav .nav>li.active>a{color:#333333;}div.subnav .nav>li.active>a:hover{color:#333333;} +div.subnav .nav>li>.dropdown-toggle{background-color:transparent;} +div.subnav .nav>li.dropdown.open>.dropdown-toggle{border-left:1px solid whiteSmoke;border-right:1px solid #E5E5E5;color:#4183c4;} +div.subnav .nav>.open>.dropdown-toggle:hover .caret{border-top-color:#4183c4;} +.btn{background-color:#f1f1f1;background-image:-moz-linear-gradient(top, #f4f4f4, #ececec);background-image:-ms-linear-gradient(top, #f4f4f4, #ececec);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#f4f4f4), to(#ececec));background-image:-webkit-linear-gradient(top, #f4f4f4, #ececec);background-image:-o-linear-gradient(top, #f4f4f4, #ececec);background-image:linear-gradient(top, #f4f4f4, #ececec);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#f4f4f4', endColorstr='#ececec', GradientType=0);border-color:#ececec #ececec #c6c6c6;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#ececec;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn:hover,.btn:active,.btn.active,.btn.disabled,.btn[disabled]{background-color:#ececec;*background-color:#dfdfdf;} +.btn:active,.btn.active{background-color:#d3d3d3 \9;} +.btn-primary{background-color:#707070;background-image:-moz-linear-gradient(top, #909090, #3f3f3f);background-image:-ms-linear-gradient(top, #909090, #3f3f3f);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#909090), to(#3f3f3f));background-image:-webkit-linear-gradient(top, #909090, #3f3f3f);background-image:-o-linear-gradient(top, #909090, #3f3f3f);background-image:linear-gradient(top, #909090, #3f3f3f);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#909090', endColorstr='#3f3f3f', GradientType=0);border-color:#3f3f3f #3f3f3f #191919;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#3f3f3f;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-primary:hover,.btn-primary:active,.btn-primary.active,.btn-primary.disabled,.btn-primary[disabled]{background-color:#3f3f3f;*background-color:#323232;} +.btn-primary:active,.btn-primary.active{background-color:#262626 \9;} +.btn-warning{background-color:#fbd627;background-image:-moz-linear-gradient(top, #ffde42, #f4ca00);background-image:-ms-linear-gradient(top, #ffde42, #f4ca00);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ffde42), to(#f4ca00));background-image:-webkit-linear-gradient(top, #ffde42, #f4ca00);background-image:-o-linear-gradient(top, #ffde42, #f4ca00);background-image:linear-gradient(top, #ffde42, #f4ca00);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffde42', endColorstr='#f4ca00', GradientType=0);border-color:#f4ca00 #f4ca00 #a88b00;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#f4ca00;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-warning:hover,.btn-warning:active,.btn-warning.active,.btn-warning.disabled,.btn-warning[disabled]{background-color:#f4ca00;*background-color:#dbb500;} +.btn-warning:active,.btn-warning.active{background-color:#c1a000 \9;} +.btn-danger{background-color:#e15454;background-image:-moz-linear-gradient(top, #e56e6e, #da2d2d);background-image:-ms-linear-gradient(top, #e56e6e, #da2d2d);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#e56e6e), to(#da2d2d));background-image:-webkit-linear-gradient(top, #e56e6e, #da2d2d);background-image:-o-linear-gradient(top, #e56e6e, #da2d2d);background-image:linear-gradient(top, #e56e6e, #da2d2d);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#e56e6e', endColorstr='#da2d2d', GradientType=0);border-color:#da2d2d #da2d2d #9f1c1c;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#da2d2d;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-danger:hover,.btn-danger:active,.btn-danger.active,.btn-danger.disabled,.btn-danger[disabled]{background-color:#da2d2d;*background-color:#ca2424;} +.btn-danger:active,.btn-danger.active{background-color:#b42020 \9;} +.btn-success{background-color:#79cb5d;background-image:-moz-linear-gradient(top, #8add6d, #60b044);background-image:-ms-linear-gradient(top, #8add6d, #60b044);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#8add6d), to(#60b044));background-image:-webkit-linear-gradient(top, #8add6d, #60b044);background-image:-o-linear-gradient(top, #8add6d, #60b044);background-image:linear-gradient(top, #8add6d, #60b044);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#8add6d', endColorstr='#60b044', GradientType=0);border-color:#60b044 #60b044 #42792f;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#60b044;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-success:hover,.btn-success:active,.btn-success.active,.btn-success.disabled,.btn-success[disabled]{background-color:#60b044;*background-color:#569e3d;} +.btn-success:active,.btn-success.active{background-color:#4c8b36 \9;} +.btn-info{background-color:#669dc7;background-image:-moz-linear-gradient(top, #7caccf, #4488bb);background-image:-ms-linear-gradient(top, #7caccf, #4488bb);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#7caccf), to(#4488bb));background-image:-webkit-linear-gradient(top, #7caccf, #4488bb);background-image:-o-linear-gradient(top, #7caccf, #4488bb);background-image:linear-gradient(top, #7caccf, #4488bb);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#7caccf', endColorstr='#4488bb', GradientType=0);border-color:#4488bb #4488bb #305f83;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#4488bb;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-info:hover,.btn-info:active,.btn-info.active,.btn-info.disabled,.btn-info[disabled]{background-color:#4488bb;*background-color:#3d7aa8;} +.btn-info:active,.btn-info.active{background-color:#366d96 \9;} +.btn-inverse{background-color:#466274;background-image:-moz-linear-gradient(top, #4a687a, #405a6a);background-image:-ms-linear-gradient(top, #4a687a, #405a6a);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#4a687a), to(#405a6a));background-image:-webkit-linear-gradient(top, #4a687a, #405a6a);background-image:-o-linear-gradient(top, #4a687a, #405a6a);background-image:linear-gradient(top, #4a687a, #405a6a);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#4a687a', endColorstr='#405a6a', GradientType=0);border-color:#405a6a #405a6a #23323a;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#405a6a;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-inverse:hover,.btn-inverse:active,.btn-inverse.active,.btn-inverse.disabled,.btn-inverse[disabled]{background-color:#405a6a;*background-color:#364d5a;} +.btn-inverse:active,.btn-inverse.active{background-color:#2d3f4a \9;} +.control-group.warning>label,.control-group.warning .help-block,.control-group.warning .help-inline{color:#e29235;} +.control-group.warning .checkbox,.control-group.warning .radio,.control-group.warning input,.control-group.warning select,.control-group.warning textarea{color:#e29235;border-color:#e29235;}.control-group.warning .checkbox:focus,.control-group.warning .radio:focus,.control-group.warning input:focus,.control-group.warning select:focus,.control-group.warning textarea:focus{border-color:#c7781d;-webkit-box-shadow:0 0 6px #efc28e;-moz-box-shadow:0 0 6px #efc28e;box-shadow:0 0 6px #efc28e;} +.control-group.warning .input-prepend .add-on,.control-group.warning .input-append .add-on{color:#e29235;background-color:#f5f3b4;border-color:#e29235;} +.control-group.error>label,.control-group.error .help-block,.control-group.error .help-inline{color:#cc0000;} +.control-group.error .checkbox,.control-group.error .radio,.control-group.error input,.control-group.error select,.control-group.error textarea{color:#cc0000;border-color:#cc0000;}.control-group.error .checkbox:focus,.control-group.error .radio:focus,.control-group.error input:focus,.control-group.error select:focus,.control-group.error textarea:focus{border-color:#990000;-webkit-box-shadow:0 0 6px #ff3333;-moz-box-shadow:0 0 6px #ff3333;box-shadow:0 0 6px #ff3333;} +.control-group.error .input-prepend .add-on,.control-group.error .input-append .add-on{color:#cc0000;background-color:#ffe9e9;border-color:#cc0000;} +.control-group.success>label,.control-group.success .help-block,.control-group.success .help-inline{color:#2ba949;} +.control-group.success .checkbox,.control-group.success .radio,.control-group.success input,.control-group.success select,.control-group.success textarea{color:#2ba949;border-color:#2ba949;}.control-group.success .checkbox:focus,.control-group.success .radio:focus,.control-group.success input:focus,.control-group.success select:focus,.control-group.success textarea:focus{border-color:#218037;-webkit-box-shadow:0 0 6px #63d77e;-moz-box-shadow:0 0 6px #63d77e;box-shadow:0 0 6px #63d77e;} +.control-group.success .input-prepend .add-on,.control-group.success .input-append .add-on{color:#2ba949;background-color:#bedebe;border-color:#2ba949;} +.label-important{background-color:#BD2C00;} +.label-warning{background-color:#E3E84D;} +.label-success{background-color:#6CC644;} +.label-info{background-color:#4183C4;} +.hero-unit{-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);border:1px solid rgba(0, 0, 0, 0.05);} +body{padding-top:24px;} +header{position:relative;margin-bottom:24px;z-index:999;}header h1{margin-bottom:9px;font-size:48px;letter-spacing:-1px;line-height:1;-webkit-margin-before:0;-webkit-margin-after:0;} +header p{font-size:30px;line-height:36px;} diff --git a/symposion_project/static/symposion/less/bootstrap/accordion.less b/symposion_project/static/symposion/less/bootstrap/accordion.less new file mode 100644 index 00000000..31b8cdc3 --- /dev/null +++ b/symposion_project/static/symposion/less/bootstrap/accordion.less @@ -0,0 +1,33 @@ +// ACCORDION +// --------- + + +// Parent container +.accordion { + margin-bottom: @baseLineHeight; +} + +// Group == heading + body +.accordion-group { + margin-bottom: 2px; + border: 1px solid #e5e5e5; + .border-radius(4px); +} +.accordion-heading { + border-bottom: 0; +} +.accordion-heading .accordion-toggle { + display: block; + padding: 8px 15px; +} + +// General toggle styles +.accordion-toggle { + cursor: pointer; +} + +// Inner needs the styles because you can't animate properly with any styles on the element +.accordion-inner { + padding: 9px 15px; + border-top: 1px solid #e5e5e5; +} diff --git a/symposion_project/static/symposion/less/bootstrap/alerts.less b/symposion_project/static/symposion/less/bootstrap/alerts.less new file mode 100644 index 00000000..46a0d77b --- /dev/null +++ b/symposion_project/static/symposion/less/bootstrap/alerts.less @@ -0,0 +1,58 @@ +// ALERT STYLES +// ------------ + +// Base alert styles +.alert { + padding: 8px 35px 8px 14px; + margin-bottom: @baseLineHeight; + text-shadow: 0 1px 0 rgba(255,255,255,.5); + background-color: @warningBackground; + border: 1px solid @warningBorder; + .border-radius(4px); + color: @warningText; +} +.alert-heading { + color: inherit; +} + +// Adjust close link position +.alert .close { + position: relative; + top: -2px; + right: -21px; + line-height: 18px; +} + +// Alternate styles +// ---------------- + +.alert-success { + background-color: @successBackground; + border-color: @successBorder; + color: @successText; +} +.alert-danger, +.alert-error { + background-color: @errorBackground; + border-color: @errorBorder; + color: @errorText; +} +.alert-info { + background-color: @infoBackground; + border-color: @infoBorder; + color: @infoText; +} + +// Block alerts +// ------------------------ +.alert-block { + padding-top: 14px; + padding-bottom: 14px; +} +.alert-block > p, +.alert-block > ul { + margin-bottom: 0; +} +.alert-block p + p { + margin-top: 5px; +} diff --git a/symposion_project/static/symposion/less/bootstrap/bootstrap.less b/symposion_project/static/symposion/less/bootstrap/bootstrap.less new file mode 100644 index 00000000..c43875a8 --- /dev/null +++ b/symposion_project/static/symposion/less/bootstrap/bootstrap.less @@ -0,0 +1,62 @@ +/*! + * Bootstrap v2.0.4 + * + * Copyright 2012 Twitter, Inc + * Licensed under the Apache License v2.0 + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Designed and built with all the love in the world @twitter by @mdo and @fat. + */ + +// CSS Reset +@import "reset.less"; + +// Core variables and mixins +@import "variables.less"; // Modify this for custom colors, font-sizes, etc +@import "mixins.less"; + +// Grid system and page structure +@import "scaffolding.less"; +@import "grid.less"; +@import "layouts.less"; + +// Base CSS +@import "type.less"; +@import "code.less"; +@import "forms.less"; +@import "tables.less"; + +// Components: common +@import "sprites.less"; +@import "dropdowns.less"; +@import "wells.less"; +@import "component-animations.less"; +@import "close.less"; + +// Components: Buttons & Alerts +@import "buttons.less"; +@import "button-groups.less"; +@import "alerts.less"; // Note: alerts share common CSS with buttons and thus have styles in buttons.less + +// Components: Nav +@import "navs.less"; +@import "navbar.less"; +@import "breadcrumbs.less"; +@import "pagination.less"; +@import "pager.less"; + +// Components: Popovers +@import "modals.less"; +@import "tooltip.less"; +@import "popovers.less"; + +// Components: Misc +@import "thumbnails.less"; +@import "labels-badges.less"; +@import "progress-bars.less"; +@import "accordion.less"; +@import "carousel.less"; +@import "hero-unit.less"; + +// Utility classes +@import "utilities.less"; // Has to be last to override when necessary diff --git a/symposion_project/static/symposion/less/bootstrap/breadcrumbs.less b/symposion_project/static/symposion/less/bootstrap/breadcrumbs.less new file mode 100644 index 00000000..111f1227 --- /dev/null +++ b/symposion_project/static/symposion/less/bootstrap/breadcrumbs.less @@ -0,0 +1,24 @@ +// BREADCRUMBS +// ----------- + +.breadcrumb { + padding: 7px 14px; + margin: 0 0 @baseLineHeight; + list-style: none; + #gradient > .vertical(@white, #f5f5f5); + border: 1px solid #ddd; + .border-radius(3px); + .box-shadow(inset 0 1px 0 @white); + li { + display: inline-block; + .ie7-inline-block(); + text-shadow: 0 1px 0 @white; + } + .divider { + padding: 0 5px; + color: @grayLight; + } + .active a { + color: @grayDark; + } +} diff --git a/symposion_project/static/symposion/less/bootstrap/button-groups.less b/symposion_project/static/symposion/less/bootstrap/button-groups.less new file mode 100644 index 00000000..5338c5a4 --- /dev/null +++ b/symposion_project/static/symposion/less/bootstrap/button-groups.less @@ -0,0 +1,191 @@ +// BUTTON GROUPS +// ------------- + + +// Make the div behave like a button +.btn-group { + position: relative; + .clearfix(); // clears the floated buttons + .ie7-restore-left-whitespace(); +} + +// Space out series of button groups +.btn-group + .btn-group { + margin-left: 5px; +} + +// Optional: Group multiple button groups together for a toolbar +.btn-toolbar { + margin-top: @baseLineHeight / 2; + margin-bottom: @baseLineHeight / 2; + .btn-group { + display: inline-block; + .ie7-inline-block(); + } +} + +// Float them, remove border radius, then re-add to first and last elements +.btn-group > .btn { + position: relative; + float: left; + margin-left: -1px; + .border-radius(0); +} +// Set corners individual because sometimes a single button can be in a .btn-group and we need :first-child and :last-child to both match +.btn-group > .btn:first-child { + margin-left: 0; + -webkit-border-top-left-radius: 4px; + -moz-border-radius-topleft: 4px; + border-top-left-radius: 4px; + -webkit-border-bottom-left-radius: 4px; + -moz-border-radius-bottomleft: 4px; + border-bottom-left-radius: 4px; +} +// Need .dropdown-toggle since :last-child doesn't apply given a .dropdown-menu immediately after it +.btn-group > .btn:last-child, +.btn-group > .dropdown-toggle { + -webkit-border-top-right-radius: 4px; + -moz-border-radius-topright: 4px; + border-top-right-radius: 4px; + -webkit-border-bottom-right-radius: 4px; + -moz-border-radius-bottomright: 4px; + border-bottom-right-radius: 4px; +} +// Reset corners for large buttons +.btn-group > .btn.large:first-child { + margin-left: 0; + -webkit-border-top-left-radius: 6px; + -moz-border-radius-topleft: 6px; + border-top-left-radius: 6px; + -webkit-border-bottom-left-radius: 6px; + -moz-border-radius-bottomleft: 6px; + border-bottom-left-radius: 6px; +} +.btn-group > .btn.large:last-child, +.btn-group > .large.dropdown-toggle { + -webkit-border-top-right-radius: 6px; + -moz-border-radius-topright: 6px; + border-top-right-radius: 6px; + -webkit-border-bottom-right-radius: 6px; + -moz-border-radius-bottomright: 6px; + border-bottom-right-radius: 6px; +} + +// On hover/focus/active, bring the proper btn to front +.btn-group > .btn:hover, +.btn-group > .btn:focus, +.btn-group > .btn:active, +.btn-group > .btn.active { + z-index: 2; +} + +// On active and open, don't show outline +.btn-group .dropdown-toggle:active, +.btn-group.open .dropdown-toggle { + outline: 0; +} + + + +// Split button dropdowns +// ---------------------- + +// Give the line between buttons some depth +.btn-group > .dropdown-toggle { + padding-left: 8px; + padding-right: 8px; + .box-shadow(~"inset 1px 0 0 rgba(255,255,255,.125), inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05)"); + *padding-top: 4px; + *padding-bottom: 4px; +} +.btn-group > .btn-mini.dropdown-toggle { + padding-left: 5px; + padding-right: 5px; +} +.btn-group > .btn-small.dropdown-toggle { + *padding-top: 4px; + *padding-bottom: 4px; +} +.btn-group > .btn-large.dropdown-toggle { + padding-left: 12px; + padding-right: 12px; +} + +.btn-group.open { + + // The clickable button for toggling the menu + // Remove the gradient and set the same inset shadow as the :active state + .dropdown-toggle { + background-image: none; + .box-shadow(~"inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05)"); + } + + // Keep the hover's background when dropdown is open + .btn.dropdown-toggle { + background-color: @btnBackgroundHighlight; + } + .btn-primary.dropdown-toggle { + background-color: @btnPrimaryBackgroundHighlight; + } + .btn-warning.dropdown-toggle { + background-color: @btnWarningBackgroundHighlight; + } + .btn-danger.dropdown-toggle { + background-color: @btnDangerBackgroundHighlight; + } + .btn-success.dropdown-toggle { + background-color: @btnSuccessBackgroundHighlight; + } + .btn-info.dropdown-toggle { + background-color: @btnInfoBackgroundHighlight; + } + .btn-inverse.dropdown-toggle { + background-color: @btnInverseBackgroundHighlight; + } +} + + +// Reposition the caret +.btn .caret { + margin-top: 7px; + margin-left: 0; +} +.btn:hover .caret, +.open.btn-group .caret { + .opacity(100); +} +// Carets in other button sizes +.btn-mini .caret { + margin-top: 5px; +} +.btn-small .caret { + margin-top: 6px; +} +.btn-large .caret { + margin-top: 6px; + border-left-width: 5px; + border-right-width: 5px; + border-top-width: 5px; +} +// Upside down carets for .dropup +.dropup .btn-large .caret { + border-bottom: 5px solid @black; + border-top: 0; +} + + + +// Account for other colors +.btn-primary, +.btn-warning, +.btn-danger, +.btn-info, +.btn-success, +.btn-inverse { + .caret { + border-top-color: @white; + border-bottom-color: @white; + .opacity(75); + } +} + diff --git a/symposion_project/static/symposion/less/bootstrap/buttons.less b/symposion_project/static/symposion/less/bootstrap/buttons.less new file mode 100644 index 00000000..c44ff3e6 --- /dev/null +++ b/symposion_project/static/symposion/less/bootstrap/buttons.less @@ -0,0 +1,191 @@ +// BUTTON STYLES +// ------------- + + +// Base styles +// -------------------------------------------------- + +// Core +.btn { + display: inline-block; + .ie7-inline-block(); + padding: 4px 10px 4px; + margin-bottom: 0; // For input.btn + font-size: @baseFontSize; + line-height: @baseLineHeight; + *line-height: 20px; + color: @grayDark; + text-align: center; + text-shadow: 0 1px 1px rgba(255,255,255,.75); + vertical-align: middle; + cursor: pointer; + .buttonBackground(@btnBackground, @btnBackgroundHighlight); + border: 1px solid @btnBorder; + *border: 0; // Remove the border to prevent IE7's black border on input:focus + border-bottom-color: darken(@btnBorder, 10%); + .border-radius(4px); + .ie7-restore-left-whitespace(); // Give IE7 some love + .box-shadow(~"inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05)"); +} + +// Hover state +.btn:hover { + color: @grayDark; + text-decoration: none; + background-color: darken(@white, 10%); + *background-color: darken(@white, 15%); /* Buttons in IE7 don't get borders, so darken on hover */ + background-position: 0 -15px; + + // transition is only when going to hover, otherwise the background + // behind the gradient (there for IE<=9 fallback) gets mismatched + .transition(background-position .1s linear); +} + +// Focus state for keyboard and accessibility +.btn:focus { + .tab-focus(); +} + +// Active state +.btn.active, +.btn:active { + background-color: darken(@white, 10%); + background-color: darken(@white, 15%) e("\9"); + background-image: none; + outline: 0; + .box-shadow(~"inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05)"); +} + +// Disabled state +.btn.disabled, +.btn[disabled] { + cursor: default; + background-color: darken(@white, 10%); + background-image: none; + .opacity(65); + .box-shadow(none); +} + + +// Button Sizes +// -------------------------------------------------- + +// Large +.btn-large { + padding: 9px 14px; + font-size: @baseFontSize + 2px; + line-height: normal; + .border-radius(5px); +} +.btn-large [class^="icon-"] { + margin-top: 1px; +} + +// Small +.btn-small { + padding: 5px 9px; + font-size: @baseFontSize - 2px; + line-height: @baseLineHeight - 2px; +} +.btn-small [class^="icon-"] { + margin-top: -1px; +} + +// Mini +.btn-mini { + padding: 2px 6px; + font-size: @baseFontSize - 2px; + line-height: @baseLineHeight - 4px; +} + + +// Alternate buttons +// -------------------------------------------------- + +// Set text color +// ------------------------- +.btn-primary, +.btn-primary:hover, +.btn-warning, +.btn-warning:hover, +.btn-danger, +.btn-danger:hover, +.btn-success, +.btn-success:hover, +.btn-info, +.btn-info:hover, +.btn-inverse, +.btn-inverse:hover { + color: @white; + text-shadow: 0 -1px 0 rgba(0,0,0,.25); +} +// Provide *some* extra contrast for those who can get it +.btn-primary.active, +.btn-warning.active, +.btn-danger.active, +.btn-success.active, +.btn-info.active, +.btn-inverse.active { + color: rgba(255,255,255,.75); +} + +// Set the backgrounds +// ------------------------- +.btn { + // reset here as of 2.0.3 due to Recess property order + border-color: #ccc; + border-color: rgba(0,0,0,.1) rgba(0,0,0,.1) rgba(0,0,0,.25); +} +.btn-primary { + .buttonBackground(@btnPrimaryBackground, @btnPrimaryBackgroundHighlight); +} +// Warning appears are orange +.btn-warning { + .buttonBackground(@btnWarningBackground, @btnWarningBackgroundHighlight); +} +// Danger and error appear as red +.btn-danger { + .buttonBackground(@btnDangerBackground, @btnDangerBackgroundHighlight); +} +// Success appears as green +.btn-success { + .buttonBackground(@btnSuccessBackground, @btnSuccessBackgroundHighlight); +} +// Info appears as a neutral blue +.btn-info { + .buttonBackground(@btnInfoBackground, @btnInfoBackgroundHighlight); +} +// Inverse appears as dark gray +.btn-inverse { + .buttonBackground(@btnInverseBackground, @btnInverseBackgroundHighlight); +} + + +// Cross-browser Jank +// -------------------------------------------------- + +button.btn, +input[type="submit"].btn { + + // Firefox 3.6 only I believe + &::-moz-focus-inner { + padding: 0; + border: 0; + } + + // IE7 has some default padding on button controls + *padding-top: 2px; + *padding-bottom: 2px; + &.btn-large { + *padding-top: 7px; + *padding-bottom: 7px; + } + &.btn-small { + *padding-top: 3px; + *padding-bottom: 3px; + } + &.btn-mini { + *padding-top: 1px; + *padding-bottom: 1px; + } +} diff --git a/symposion_project/static/symposion/less/bootstrap/carousel.less b/symposion_project/static/symposion/less/bootstrap/carousel.less new file mode 100644 index 00000000..8fbd3031 --- /dev/null +++ b/symposion_project/static/symposion/less/bootstrap/carousel.less @@ -0,0 +1,121 @@ +// CAROUSEL +// -------- + +.carousel { + position: relative; + margin-bottom: @baseLineHeight; + line-height: 1; +} + +.carousel-inner { + overflow: hidden; + width: 100%; + position: relative; +} + +.carousel { + + .item { + display: none; + position: relative; + .transition(.6s ease-in-out left); + } + + // Account for jankitude on images + .item > img { + display: block; + line-height: 1; + } + + .active, + .next, + .prev { display: block; } + + .active { + left: 0; + } + + .next, + .prev { + position: absolute; + top: 0; + width: 100%; + } + + .next { + left: 100%; + } + .prev { + left: -100%; + } + .next.left, + .prev.right { + left: 0; + } + + .active.left { + left: -100%; + } + .active.right { + left: 100%; + } + +} + +// Left/right controls for nav +// --------------------------- + +.carousel-control { + position: absolute; + top: 40%; + left: 15px; + width: 40px; + height: 40px; + margin-top: -20px; + font-size: 60px; + font-weight: 100; + line-height: 30px; + color: @white; + text-align: center; + background: @grayDarker; + border: 3px solid @white; + .border-radius(23px); + .opacity(50); + + // we can't have this transition here + // because webkit cancels the carousel + // animation if you trip this while + // in the middle of another animation + // ;_; + // .transition(opacity .2s linear); + + // Reposition the right one + &.right { + left: auto; + right: 15px; + } + + // Hover state + &:hover { + color: @white; + text-decoration: none; + .opacity(90); + } +} + +// Caption for text below images +// ----------------------------- + +.carousel-caption { + position: absolute; + left: 0; + right: 0; + bottom: 0; + padding: 10px 15px 5px; + background: @grayDark; + background: rgba(0,0,0,.75); +} +.carousel-caption h4, +.carousel-caption p { + color: @white; +} diff --git a/symposion_project/static/symposion/less/bootstrap/close.less b/symposion_project/static/symposion/less/bootstrap/close.less new file mode 100644 index 00000000..31fe6fcb --- /dev/null +++ b/symposion_project/static/symposion/less/bootstrap/close.less @@ -0,0 +1,29 @@ +// CLOSE ICONS +// ----------- + +.close { + float: right; + font-size: 20px; + font-weight: bold; + line-height: @baseLineHeight; + color: @black; + text-shadow: 0 1px 0 rgba(255,255,255,1); + .opacity(20); + &:hover { + color: @black; + text-decoration: none; + cursor: pointer; + .opacity(40); + } +} + +// Additional properties for button version +// iOS requires the button element instead of an anchor tag. +// If you want the anchor version, it requires `href="#"`. +button.close { + padding: 0; + cursor: pointer; + background: transparent; + border: 0; + -webkit-appearance: none; +} \ No newline at end of file diff --git a/symposion_project/static/symposion/less/bootstrap/code.less b/symposion_project/static/symposion/less/bootstrap/code.less new file mode 100644 index 00000000..0cae749a --- /dev/null +++ b/symposion_project/static/symposion/less/bootstrap/code.less @@ -0,0 +1,57 @@ +// Code.less +// Code typography styles for the and
     elements
    +// --------------------------------------------------------
    +
    +// Inline and block code styles
    +code,
    +pre {
    +  padding: 0 3px 2px;
    +  #font > #family > .monospace;
    +  font-size: @baseFontSize - 1;
    +  color: @grayDark;
    +  .border-radius(3px);
    +}
    +
    +// Inline code
    +code {
    +  padding: 2px 4px;
    +  color: #d14;
    +  background-color: #f7f7f9;
    +  border: 1px solid #e1e1e8;
    +}
    +
    +// Blocks of code
    +pre {
    +  display: block;
    +  padding: (@baseLineHeight - 1) / 2;
    +  margin: 0 0 @baseLineHeight / 2;
    +  font-size: @baseFontSize * .925; // 13px to 12px
    +  line-height: @baseLineHeight;
    +  word-break: break-all;
    +  word-wrap: break-word;
    +  white-space: pre;
    +  white-space: pre-wrap;
    +  background-color: #f5f5f5;
    +  border: 1px solid #ccc; // fallback for IE7-8
    +  border: 1px solid rgba(0,0,0,.15);
    +  .border-radius(4px);
    +
    +  // Make prettyprint styles more spaced out for readability
    +  &.prettyprint {
    +    margin-bottom: @baseLineHeight;
    +  }
    +
    +  // Account for some code outputs that place code tags in pre tags
    +  code {
    +    padding: 0;
    +    color: inherit;
    +    background-color: transparent;
    +    border: 0;
    +  }
    +}
    +
    +// Enable scrollable blocks of code
    +.pre-scrollable {
    +  max-height: 340px;
    +  overflow-y: scroll;
    +}
    \ No newline at end of file
    diff --git a/symposion_project/static/symposion/less/bootstrap/component-animations.less b/symposion_project/static/symposion/less/bootstrap/component-animations.less
    new file mode 100644
    index 00000000..1e1e78b8
    --- /dev/null
    +++ b/symposion_project/static/symposion/less/bootstrap/component-animations.less
    @@ -0,0 +1,20 @@
    +// COMPONENT ANIMATIONS
    +// --------------------
    +
    +.fade {
    +  opacity: 0;
    +  .transition(opacity .15s linear);
    +  &.in {
    +    opacity: 1;
    +  }
    +}
    +
    +.collapse {
    +  position: relative;
    +  height: 0;
    +  overflow: hidden;
    +  .transition(height .35s ease);
    +  &.in {
    +    height: auto;
    +  }
    +}
    diff --git a/symposion_project/static/symposion/less/bootstrap/dropdowns.less b/symposion_project/static/symposion/less/bootstrap/dropdowns.less
    new file mode 100644
    index 00000000..5e232556
    --- /dev/null
    +++ b/symposion_project/static/symposion/less/bootstrap/dropdowns.less
    @@ -0,0 +1,143 @@
    +// DROPDOWN MENUS
    +// --------------
    +
    +// Use the .menu class on any 
  • element within the topbar or ul.tabs and you'll get some superfancy dropdowns +.dropup, +.dropdown { + position: relative; +} +.dropdown-toggle { + // The caret makes the toggle a bit too tall in IE7 + *margin-bottom: -3px; +} +.dropdown-toggle:active, +.open .dropdown-toggle { + outline: 0; +} + +// Dropdown arrow/caret +// -------------------- +.caret { + display: inline-block; + width: 0; + height: 0; + vertical-align: top; + border-top: 4px solid @black; + border-right: 4px solid transparent; + border-left: 4px solid transparent; + content: ""; + .opacity(30); +} + +// Place the caret +.dropdown .caret { + margin-top: 8px; + margin-left: 2px; +} +.dropdown:hover .caret, +.open .caret { + .opacity(100); +} + +// The dropdown menu (ul) +// ---------------------- +.dropdown-menu { + position: absolute; + top: 100%; + left: 0; + z-index: @zindexDropdown; + display: none; // none by default, but block on "open" of the menu + float: left; + min-width: 160px; + padding: 4px 0; + margin: 1px 0 0; // override default ul + list-style: none; + background-color: @dropdownBackground; + border: 1px solid #ccc; + border: 1px solid rgba(0,0,0,.2); + *border-right-width: 2px; + *border-bottom-width: 2px; + .border-radius(5px); + .box-shadow(0 5px 10px rgba(0,0,0,.2)); + -webkit-background-clip: padding-box; + -moz-background-clip: padding; + background-clip: padding-box; + + // Aligns the dropdown menu to right + &.pull-right { + right: 0; + left: auto; + } + + // Dividers (basically an hr) within the dropdown + .divider { + .nav-divider(@dropdownDividerTop, @dropdownDividerBottom); + } + + // Links within the dropdown menu + a { + display: block; + padding: 3px 15px; + clear: both; + font-weight: normal; + line-height: @baseLineHeight; + color: @dropdownLinkColor; + white-space: nowrap; + } +} + +// Hover state +// ----------- +.dropdown-menu li > a:hover, +.dropdown-menu .active > a, +.dropdown-menu .active > a:hover { + color: @dropdownLinkColorHover; + text-decoration: none; + background-color: @dropdownLinkBackgroundHover; +} + +// Open state for the dropdown +// --------------------------- +.open { + // IE7's z-index only goes to the nearest positioned ancestor, which would + // make the menu appear below buttons that appeared later on the page + *z-index: @zindexDropdown; + + & > .dropdown-menu { + display: block; + } +} + +// Right aligned dropdowns +// --------------------------- +.pull-right > .dropdown-menu { + right: 0; + left: auto; +} + +// Allow for dropdowns to go bottom up (aka, dropup-menu) +// ------------------------------------------------------ +// Just add .dropup after the standard .dropdown class and you're set, bro. +// TODO: abstract this so that the navbar fixed styles are not placed here? +.dropup, +.navbar-fixed-bottom .dropdown { + // Reverse the caret + .caret { + border-top: 0; + border-bottom: 4px solid @black; + content: "\2191"; + } + // Different positioning for bottom up menu + .dropdown-menu { + top: auto; + bottom: 100%; + margin-bottom: 1px; + } +} + +// Typeahead +// --------- +.typeahead { + margin-top: 2px; // give it some space to breathe + .border-radius(4px); +} diff --git a/symposion_project/static/symposion/less/bootstrap/forms.less b/symposion_project/static/symposion/less/bootstrap/forms.less new file mode 100644 index 00000000..1163fe91 --- /dev/null +++ b/symposion_project/static/symposion/less/bootstrap/forms.less @@ -0,0 +1,583 @@ +// Forms.less +// Base styles for various input types, form layouts, and states +// ------------------------------------------------------------- + + +// GENERAL STYLES +// -------------- + +// Make all forms have space below them +form { + margin: 0 0 @baseLineHeight; +} + +fieldset { + padding: 0; + margin: 0; + border: 0; +} + +// Groups of fields with labels on top (legends) +legend { + display: block; + width: 100%; + padding: 0; + margin-bottom: @baseLineHeight * 1.5; + font-size: @baseFontSize * 1.5; + line-height: @baseLineHeight * 2; + color: @grayDark; + border: 0; + border-bottom: 1px solid #e5e5e5; + + // Small + small { + font-size: @baseLineHeight * .75; + color: @grayLight; + } +} + +// Set font for forms +label, +input, +button, +select, +textarea { + #font > .shorthand(@baseFontSize,normal,@baseLineHeight); // Set size, weight, line-height here +} +input, +button, +select, +textarea { + font-family: @baseFontFamily; // And only set font-family here for those that need it (note the missing label element) +} + +// Identify controls by their labels +label { + display: block; + margin-bottom: 5px; +} + +// Form controls +// ------------------------- + +// Shared size and type resets +select, +textarea, +input[type="text"], +input[type="password"], +input[type="datetime"], +input[type="datetime-local"], +input[type="date"], +input[type="month"], +input[type="time"], +input[type="week"], +input[type="number"], +input[type="email"], +input[type="url"], +input[type="search"], +input[type="tel"], +input[type="color"], +.uneditable-input { + display: inline-block; + height: @baseLineHeight; + padding: 4px; + margin-bottom: 9px; + font-size: @baseFontSize; + line-height: @baseLineHeight; + color: @gray; +} + +// Reset appearance properties for textual inputs and textarea +// Declare width for legacy (can't be on input[type=*] selectors or it's too specific) +input, +textarea { + width: 210px; +} +// Reset height since textareas have rows +textarea { + height: auto; +} +// Everything else +textarea, +input[type="text"], +input[type="password"], +input[type="datetime"], +input[type="datetime-local"], +input[type="date"], +input[type="month"], +input[type="time"], +input[type="week"], +input[type="number"], +input[type="email"], +input[type="url"], +input[type="search"], +input[type="tel"], +input[type="color"], +.uneditable-input { + background-color: @inputBackground; + border: 1px solid @inputBorder; + .border-radius(@inputBorderRadius); + .box-shadow(inset 0 1px 1px rgba(0,0,0,.075)); + @transition: border linear .2s, box-shadow linear .2s; + .transition(@transition); + + // Focus state + &:focus { + border-color: rgba(82,168,236,.8); + outline: 0; + outline: thin dotted \9; /* IE6-9 */ + .box-shadow(~"inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(82,168,236,.6)"); + } +} + +// Position radios and checkboxes better +input[type="radio"], +input[type="checkbox"] { + margin: 3px 0; + *margin-top: 0; /* IE7 */ + line-height: normal; + cursor: pointer; +} + +// Reset width of input buttons, radios, checkboxes +input[type="submit"], +input[type="reset"], +input[type="button"], +input[type="radio"], +input[type="checkbox"] { + width: auto; // Override of generic input selector +} + +// Make uneditable textareas behave like a textarea +.uneditable-textarea { + width: auto; + height: auto; +} + +// Set the height of select and file controls to match text inputs +select, +input[type="file"] { + height: 28px; /* In IE7, the height of the select element cannot be changed by height, only font-size */ + *margin-top: 4px; /* For IE7, add top margin to align select with labels */ + line-height: 28px; +} + +// Make select elements obey height by applying a border +select { + width: 220px; // default input width + 10px of padding that doesn't get applied + border: 1px solid #bbb; +} + +// Make multiple select elements height not fixed +select[multiple], +select[size] { + height: auto; +} + +// Focus for select, file, radio, and checkbox +select:focus, +input[type="file"]:focus, +input[type="radio"]:focus, +input[type="checkbox"]:focus { + .tab-focus(); +} + + + +// CHECKBOXES & RADIOS +// ------------------- + +// Indent the labels to position radios/checkboxes as hanging +.radio, +.checkbox { + min-height: 18px; // clear the floating input if there is no label text + padding-left: 18px; +} +.radio input[type="radio"], +.checkbox input[type="checkbox"] { + float: left; + margin-left: -18px; +} + +// Move the options list down to align with labels +.controls > .radio:first-child, +.controls > .checkbox:first-child { + padding-top: 5px; // has to be padding because margin collaspes +} + +// Radios and checkboxes on same line +// TODO v3: Convert .inline to .control-inline +.radio.inline, +.checkbox.inline { + display: inline-block; + padding-top: 5px; + margin-bottom: 0; + vertical-align: middle; +} +.radio.inline + .radio.inline, +.checkbox.inline + .checkbox.inline { + margin-left: 10px; // space out consecutive inline controls +} + + + +// INPUT SIZES +// ----------- + +// General classes for quick sizes +.input-mini { width: 60px; } +.input-small { width: 90px; } +.input-medium { width: 150px; } +.input-large { width: 210px; } +.input-xlarge { width: 270px; } +.input-xxlarge { width: 530px; } + +// Grid style input sizes +input[class*="span"], +select[class*="span"], +textarea[class*="span"], +.uneditable-input[class*="span"], +// Redeclare since the fluid row class is more specific +.row-fluid input[class*="span"], +.row-fluid select[class*="span"], +.row-fluid textarea[class*="span"], +.row-fluid .uneditable-input[class*="span"] { + float: none; + margin-left: 0; +} +// Ensure input-prepend/append never wraps +.input-append input[class*="span"], +.input-append .uneditable-input[class*="span"], +.input-prepend input[class*="span"], +.input-prepend .uneditable-input[class*="span"], +.row-fluid .input-prepend [class*="span"], +.row-fluid .input-append [class*="span"] { + display: inline-block; +} + + + +// GRID SIZING FOR INPUTS +// ---------------------- + +#grid > .input(@gridColumnWidth, @gridGutterWidth); + + + +// DISABLED STATE +// -------------- + +// Disabled and read-only inputs +input[disabled], +select[disabled], +textarea[disabled], +input[readonly], +select[readonly], +textarea[readonly] { + cursor: not-allowed; + background-color: @inputDisabledBackground; + border-color: #ddd; +} +// Explicitly reset the colors here +input[type="radio"][disabled], +input[type="checkbox"][disabled], +input[type="radio"][readonly], +input[type="checkbox"][readonly] { + background-color: transparent; +} + + + + +// FORM FIELD FEEDBACK STATES +// -------------------------- + +// Warning +.control-group.warning { + .formFieldState(@warningText, @warningText, @warningBackground); +} +// Error +.control-group.error { + .formFieldState(@errorText, @errorText, @errorBackground); +} +// Success +.control-group.success { + .formFieldState(@successText, @successText, @successBackground); +} + +// HTML5 invalid states +// Shares styles with the .control-group.error above +input:focus:required:invalid, +textarea:focus:required:invalid, +select:focus:required:invalid { + color: #b94a48; + border-color: #ee5f5b; + &:focus { + border-color: darken(#ee5f5b, 10%); + .box-shadow(0 0 6px lighten(#ee5f5b, 20%)); + } +} + + + +// FORM ACTIONS +// ------------ + +.form-actions { + padding: (@baseLineHeight - 1) 20px @baseLineHeight; + margin-top: @baseLineHeight; + margin-bottom: @baseLineHeight; + background-color: @formActionsBackground; + border-top: 1px solid #e5e5e5; + .clearfix(); // Adding clearfix to allow for .pull-right button containers +} + +// For text that needs to appear as an input but should not be an input +.uneditable-input { + overflow: hidden; // prevent text from wrapping, but still cut it off like an input does + white-space: nowrap; + cursor: not-allowed; + background-color: @inputBackground; + border-color: #eee; + .box-shadow(inset 0 1px 2px rgba(0,0,0,.025)); +} + +// Placeholder text gets special styles; can't be bundled together though for some reason +.placeholder(); + + + +// HELP TEXT +// --------- + +.help-block, +.help-inline { + color: @gray; // lighten the text some for contrast +} + +.help-block { + display: block; // account for any element using help-block + margin-bottom: @baseLineHeight / 2; +} + +.help-inline { + display: inline-block; + .ie7-inline-block(); + vertical-align: middle; + padding-left: 5px; +} + + + +// INPUT GROUPS +// ------------ + +// Allow us to put symbols and text within the input field for a cleaner look +.input-prepend, +.input-append { + margin-bottom: 5px; + input, + select, + .uneditable-input { + position: relative; // placed here by default so that on :focus we can place the input above the .add-on for full border and box-shadow goodness + margin-bottom: 0; // prevent bottom margin from screwing up alignment in stacked forms + *margin-left: 0; + vertical-align: middle; + .border-radius(0 @inputBorderRadius @inputBorderRadius 0); + // Make input on top when focused so blue border and shadow always show + &:focus { + z-index: 2; + } + } + .uneditable-input { + border-left-color: #ccc; + } + .add-on { + display: inline-block; + width: auto; + height: @baseLineHeight; + min-width: 16px; + padding: 4px 5px; + font-weight: normal; + line-height: @baseLineHeight; + text-align: center; + text-shadow: 0 1px 0 @white; + vertical-align: middle; + background-color: @grayLighter; + border: 1px solid #ccc; + } + .add-on, + .btn { + margin-left: -1px; + .border-radius(0); + } + .active { + background-color: lighten(@green, 30); + border-color: @green; + } +} +.input-prepend { + .add-on, + .btn { + margin-right: -1px; + } + .add-on:first-child, + .btn:first-child { + .border-radius(@inputBorderRadius 0 0 @inputBorderRadius); + } +} +.input-append { + input, + select, + .uneditable-input { + .border-radius(@inputBorderRadius 0 0 @inputBorderRadius); + } + .uneditable-input { + border-right-color: #ccc; + border-left-color: #eee; + } + .add-on:last-child, + .btn:last-child { + .border-radius(0 @inputBorderRadius @inputBorderRadius 0); + } +} +// Remove all border-radius for inputs with both prepend and append +.input-prepend.input-append { + input, + select, + .uneditable-input { + .border-radius(0); + } + .add-on:first-child, + .btn:first-child { + margin-right: -1px; + .border-radius(@inputBorderRadius 0 0 @inputBorderRadius); + } + .add-on:last-child, + .btn:last-child { + margin-left: -1px; + .border-radius(0 @inputBorderRadius @inputBorderRadius 0); + } +} + + + +// SEARCH FORM +// ----------- + +.search-query { + padding-right: 14px; + padding-right: 4px \9; + padding-left: 14px; + padding-left: 4px \9; /* IE7-8 doesn't have border-radius, so don't indent the padding */ + margin-bottom: 0; // remove the default margin on all inputs + .border-radius(14px); +} + + + +// HORIZONTAL & VERTICAL FORMS +// --------------------------- + +// Common properties +// ----------------- + +.form-search, +.form-inline, +.form-horizontal { + input, + textarea, + select, + .help-inline, + .uneditable-input, + .input-prepend, + .input-append { + display: inline-block; + .ie7-inline-block(); + margin-bottom: 0; + } + // Re-hide hidden elements due to specifity + .hide { + display: none; + } +} +.form-search label, +.form-inline label { + display: inline-block; +} +// Remove margin for input-prepend/-append +.form-search .input-append, +.form-inline .input-append, +.form-search .input-prepend, +.form-inline .input-prepend { + margin-bottom: 0; +} +// Inline checkbox/radio labels (remove padding on left) +.form-search .radio, +.form-search .checkbox, +.form-inline .radio, +.form-inline .checkbox { + padding-left: 0; + margin-bottom: 0; + vertical-align: middle; +} +// Remove float and margin, set to inline-block +.form-search .radio input[type="radio"], +.form-search .checkbox input[type="checkbox"], +.form-inline .radio input[type="radio"], +.form-inline .checkbox input[type="checkbox"] { + float: left; + margin-right: 3px; + margin-left: 0; +} + + +// Margin to space out fieldsets +.control-group { + margin-bottom: @baseLineHeight / 2; +} + +// Legend collapses margin, so next element is responsible for spacing +legend + .control-group { + margin-top: @baseLineHeight; + -webkit-margin-top-collapse: separate; +} + +// Horizontal-specific styles +// -------------------------- + +.form-horizontal { + // Increase spacing between groups + .control-group { + margin-bottom: @baseLineHeight; + .clearfix(); + } + // Float the labels left + .control-label { + float: left; + width: 140px; + padding-top: 5px; + text-align: right; + } + // Move over all input controls and content + .controls { + // Super jank IE7 fix to ensure the inputs in .input-append and input-prepend + // don't inherit the margin of the parent, in this case .controls + *display: inline-block; + *padding-left: 20px; + margin-left: 160px; + *margin-left: 0; + &:first-child { + *padding-left: 160px; + } + } + // Remove bottom margin on block level help text since that's accounted for on .control-group + .help-block { + margin-top: @baseLineHeight / 2; + margin-bottom: 0; + } + // Move over buttons in .form-actions to align with .controls + .form-actions { + padding-left: 160px; + } +} diff --git a/symposion_project/static/symposion/less/bootstrap/grid.less b/symposion_project/static/symposion/less/bootstrap/grid.less new file mode 100644 index 00000000..e62a9609 --- /dev/null +++ b/symposion_project/static/symposion/less/bootstrap/grid.less @@ -0,0 +1,5 @@ +// Fixed (940px) +#grid > .core(@gridColumnWidth, @gridGutterWidth); + +// Fluid (940px) +#grid > .fluid(@fluidGridColumnWidth, @fluidGridGutterWidth); \ No newline at end of file diff --git a/symposion_project/static/symposion/less/bootstrap/hero-unit.less b/symposion_project/static/symposion/less/bootstrap/hero-unit.less new file mode 100644 index 00000000..0ffe8296 --- /dev/null +++ b/symposion_project/static/symposion/less/bootstrap/hero-unit.less @@ -0,0 +1,22 @@ +// HERO UNIT +// --------- + +.hero-unit { + padding: 60px; + margin-bottom: 30px; + background-color: @heroUnitBackground; + .border-radius(6px); + h1 { + margin-bottom: 0; + font-size: 60px; + line-height: 1; + color: @heroUnitHeadingColor; + letter-spacing: -1px; + } + p { + font-size: 18px; + font-weight: 200; + line-height: @baseLineHeight * 1.5; + color: @heroUnitLeadColor; + } +} diff --git a/symposion_project/static/symposion/less/bootstrap/labels-badges.less b/symposion_project/static/symposion/less/bootstrap/labels-badges.less new file mode 100644 index 00000000..0fbd7bbc --- /dev/null +++ b/symposion_project/static/symposion/less/bootstrap/labels-badges.less @@ -0,0 +1,55 @@ +// LABELS & BADGES +// --------------- + +// Base classes +.label, +.badge { + font-size: @baseFontSize * .846; + font-weight: bold; + line-height: 14px; // ensure proper line-height if floated + color: @white; + vertical-align: baseline; + white-space: nowrap; + text-shadow: 0 -1px 0 rgba(0,0,0,.25); + background-color: @grayLight; +} +// Set unique padding and border-radii +.label { + padding: 1px 4px 2px; + .border-radius(3px); +} +.badge { + padding: 1px 9px 2px; + .border-radius(9px); +} + +// Hover state, but only for links +a { + &.label:hover, + &.badge:hover { + color: @white; + text-decoration: none; + cursor: pointer; + } +} + +// Colors +// Only give background-color difference to links (and to simplify, we don't qualifty with `a` but [href] attribute) +.label, +.badge { + // Important (red) + &-important { background-color: @errorText; } + &-important[href] { background-color: darken(@errorText, 10%); } + // Warnings (orange) + &-warning { background-color: @orange; } + &-warning[href] { background-color: darken(@orange, 10%); } + // Success (green) + &-success { background-color: @successText; } + &-success[href] { background-color: darken(@successText, 10%); } + // Info (turquoise) + &-info { background-color: @infoText; } + &-info[href] { background-color: darken(@infoText, 10%); } + // Inverse (black) + &-inverse { background-color: @grayDark; } + &-inverse[href] { background-color: darken(@grayDark, 10%); } +} diff --git a/symposion_project/static/symposion/less/bootstrap/layouts.less b/symposion_project/static/symposion/less/bootstrap/layouts.less new file mode 100644 index 00000000..cc53627f --- /dev/null +++ b/symposion_project/static/symposion/less/bootstrap/layouts.less @@ -0,0 +1,17 @@ +// +// Layouts +// Fixed-width and fluid (with sidebar) layouts +// -------------------------------------------- + + +// Container (centered, fixed-width layouts) +.container { + .container-fixed(); +} + +// Fluid layouts (left aligned, with sidebar, min- & max-width content) +.container-fluid { + padding-right: @gridGutterWidth; + padding-left: @gridGutterWidth; + .clearfix(); +} \ No newline at end of file diff --git a/symposion_project/static/symposion/less/bootstrap/mixins.less b/symposion_project/static/symposion/less/bootstrap/mixins.less new file mode 100644 index 00000000..c3b57ed1 --- /dev/null +++ b/symposion_project/static/symposion/less/bootstrap/mixins.less @@ -0,0 +1,646 @@ +// Mixins.less +// Snippets of reusable CSS to develop faster and keep code readable +// ----------------------------------------------------------------- + + +// UTILITY MIXINS +// -------------------------------------------------- + +// Clearfix +// -------- +// For clearing floats like a boss h5bp.com/q +.clearfix { + *zoom: 1; + &:before, + &:after { + display: table; + content: ""; + } + &:after { + clear: both; + } +} + +// Webkit-style focus +// ------------------ +.tab-focus() { + // Default + outline: thin dotted #333; + // Webkit + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} + +// Center-align a block level element +// ---------------------------------- +.center-block() { + display: block; + margin-left: auto; + margin-right: auto; +} + +// IE7 inline-block +// ---------------- +.ie7-inline-block() { + *display: inline; /* IE7 inline-block hack */ + *zoom: 1; +} + +// IE7 likes to collapse whitespace on either side of the inline-block elements. +// Ems because we're attempting to match the width of a space character. Left +// version is for form buttons, which typically come after other elements, and +// right version is for icons, which come before. Applying both is ok, but it will +// mean that space between those elements will be .6em (~2 space characters) in IE7, +// instead of the 1 space in other browsers. +.ie7-restore-left-whitespace() { + *margin-left: .3em; + + &:first-child { + *margin-left: 0; + } +} + +.ie7-restore-right-whitespace() { + *margin-right: .3em; + + &:last-child { + *margin-left: 0; + } +} + +// Sizing shortcuts +// ------------------------- +.size(@height, @width) { + width: @width; + height: @height; +} +.square(@size) { + .size(@size, @size); +} + +// Placeholder text +// ------------------------- +.placeholder(@color: @placeholderText) { + &:-moz-placeholder { + color: @color; + } + &:-ms-input-placeholder { + color: @color; + } + &::-webkit-input-placeholder { + color: @color; + } +} + +// Text overflow +// ------------------------- +// Requires inline-block or block for proper styling +.text-overflow() { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +// CSS image replacement +// ------------------------- +// Source: https://github.com/h5bp/html5-boilerplate/commit/aa0396eae757 +.hide-text { + font: 0/0 a; + color: transparent; + text-shadow: none; + background-color: transparent; + border: 0; +} + + +// FONTS +// -------------------------------------------------- + +#font { + #family { + .serif() { + font-family: @serifFontFamily; + } + .sans-serif() { + font-family: @sansFontFamily; + } + .monospace() { + font-family: @monoFontFamily; + } + } + .shorthand(@size: @baseFontSize, @weight: normal, @lineHeight: @baseLineHeight) { + font-size: @size; + font-weight: @weight; + line-height: @lineHeight; + } + .serif(@size: @baseFontSize, @weight: normal, @lineHeight: @baseLineHeight) { + #font > #family > .serif; + #font > .shorthand(@size, @weight, @lineHeight); + } + .sans-serif(@size: @baseFontSize, @weight: normal, @lineHeight: @baseLineHeight) { + #font > #family > .sans-serif; + #font > .shorthand(@size, @weight, @lineHeight); + } + .monospace(@size: @baseFontSize, @weight: normal, @lineHeight: @baseLineHeight) { + #font > #family > .monospace; + #font > .shorthand(@size, @weight, @lineHeight); + } +} + + +// FORMS +// -------------------------------------------------- + +// Block level inputs +.input-block-level { + display: block; + width: 100%; + min-height: 28px; // Make inputs at least the height of their button counterpart + .box-sizing(border-box); // Makes inputs behave like true block-level elements +} + + +// Mixin for form field states +.formFieldState(@textColor: #555, @borderColor: #ccc, @backgroundColor: #f5f5f5) { + // Set the text color + > label, + .help-block, + .help-inline { + color: @textColor; + } + // Style inputs accordingly + .checkbox, + .radio, + input, + select, + textarea { + color: @textColor; + border-color: @borderColor; + &:focus { + border-color: darken(@borderColor, 10%); + .box-shadow(0 0 6px lighten(@borderColor, 20%)); + } + } + // Give a small background color for input-prepend/-append + .input-prepend .add-on, + .input-append .add-on { + color: @textColor; + background-color: @backgroundColor; + border-color: @textColor; + } +} + + + +// CSS3 PROPERTIES +// -------------------------------------------------- + +// Border Radius +.border-radius(@radius) { + -webkit-border-radius: @radius; + -moz-border-radius: @radius; + border-radius: @radius; +} + +// Drop shadows +.box-shadow(@shadow) { + -webkit-box-shadow: @shadow; + -moz-box-shadow: @shadow; + box-shadow: @shadow; +} + +// Transitions +.transition(@transition) { + -webkit-transition: @transition; + -moz-transition: @transition; + -ms-transition: @transition; + -o-transition: @transition; + transition: @transition; +} + +// Transformations +.rotate(@degrees) { + -webkit-transform: rotate(@degrees); + -moz-transform: rotate(@degrees); + -ms-transform: rotate(@degrees); + -o-transform: rotate(@degrees); + transform: rotate(@degrees); +} +.scale(@ratio) { + -webkit-transform: scale(@ratio); + -moz-transform: scale(@ratio); + -ms-transform: scale(@ratio); + -o-transform: scale(@ratio); + transform: scale(@ratio); +} +.translate(@x, @y) { + -webkit-transform: translate(@x, @y); + -moz-transform: translate(@x, @y); + -ms-transform: translate(@x, @y); + -o-transform: translate(@x, @y); + transform: translate(@x, @y); +} +.skew(@x, @y) { + -webkit-transform: skew(@x, @y); + -moz-transform: skew(@x, @y); + -ms-transform: skew(@x, @y); + -o-transform: skew(@x, @y); + transform: skew(@x, @y); +} +.translate3d(@x, @y, @z) { + -webkit-transform: translate(@x, @y, @z); + -moz-transform: translate(@x, @y, @z); + -ms-transform: translate(@x, @y, @z); + -o-transform: translate(@x, @y, @z); + transform: translate(@x, @y, @z); +} + +// Backface visibility +// Prevent browsers from flickering when using CSS 3D transforms. +// Default value is `visible`, but can be changed to `hidden +// See git pull https://github.com/dannykeane/bootstrap.git backface-visibility for examples +.backface-visibility(@visibility){ + -webkit-backface-visibility: @visibility; + -moz-backface-visibility: @visibility; + -ms-backface-visibility: @visibility; + backface-visibility: @visibility; +} + +// Background clipping +// Heads up: FF 3.6 and under need "padding" instead of "padding-box" +.background-clip(@clip) { + -webkit-background-clip: @clip; + -moz-background-clip: @clip; + background-clip: @clip; +} + +// Background sizing +.background-size(@size){ + -webkit-background-size: @size; + -moz-background-size: @size; + -o-background-size: @size; + background-size: @size; +} + + +// Box sizing +.box-sizing(@boxmodel) { + -webkit-box-sizing: @boxmodel; + -moz-box-sizing: @boxmodel; + -ms-box-sizing: @boxmodel; + box-sizing: @boxmodel; +} + +// User select +// For selecting text on the page +.user-select(@select) { + -webkit-user-select: @select; + -moz-user-select: @select; + -ms-user-select: @select; + -o-user-select: @select; + user-select: @select; +} + +// Resize anything +.resizable(@direction) { + resize: @direction; // Options: horizontal, vertical, both + overflow: auto; // Safari fix +} + +// CSS3 Content Columns +.content-columns(@columnCount, @columnGap: @gridGutterWidth) { + -webkit-column-count: @columnCount; + -moz-column-count: @columnCount; + column-count: @columnCount; + -webkit-column-gap: @columnGap; + -moz-column-gap: @columnGap; + column-gap: @columnGap; +} + +// Optional hyphenation +.hyphens(@mode: auto) { + word-wrap: break-word; + -webkit-hyphens: @mode; + -moz-hyphens: @mode; + -ms-hyphens: @mode; + -o-hyphens: @mode; + hyphens: @mode; +} + +// Opacity +.opacity(@opacity) { + opacity: @opacity / 100; + filter: ~"alpha(opacity=@{opacity})"; +} + + + +// BACKGROUNDS +// -------------------------------------------------- + +// Add an alphatransparency value to any background or border color (via Elyse Holladay) +#translucent { + .background(@color: @white, @alpha: 1) { + background-color: hsla(hue(@color), saturation(@color), lightness(@color), @alpha); + } + .border(@color: @white, @alpha: 1) { + border-color: hsla(hue(@color), saturation(@color), lightness(@color), @alpha); + .background-clip(padding-box); + } +} + +// Gradient Bar Colors for buttons and alerts +.gradientBar(@primaryColor, @secondaryColor) { + #gradient > .vertical(@primaryColor, @secondaryColor); + border-color: @secondaryColor @secondaryColor darken(@secondaryColor, 15%); + border-color: rgba(0,0,0,.1) rgba(0,0,0,.1) fadein(rgba(0,0,0,.1), 15%); +} + +// Gradients +#gradient { + .horizontal(@startColor: #555, @endColor: #333) { + background-color: @endColor; + background-image: -moz-linear-gradient(left, @startColor, @endColor); // FF 3.6+ + background-image: -ms-linear-gradient(left, @startColor, @endColor); // IE10 + background-image: -webkit-gradient(linear, 0 0, 100% 0, from(@startColor), to(@endColor)); // Safari 4+, Chrome 2+ + background-image: -webkit-linear-gradient(left, @startColor, @endColor); // Safari 5.1+, Chrome 10+ + background-image: -o-linear-gradient(left, @startColor, @endColor); // Opera 11.10 + background-image: linear-gradient(left, @startColor, @endColor); // Le standard + background-repeat: repeat-x; + filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)",@startColor,@endColor)); // IE9 and down + } + .vertical(@startColor: #555, @endColor: #333) { + background-color: mix(@startColor, @endColor, 60%); + background-image: -moz-linear-gradient(top, @startColor, @endColor); // FF 3.6+ + background-image: -ms-linear-gradient(top, @startColor, @endColor); // IE10 + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(@startColor), to(@endColor)); // Safari 4+, Chrome 2+ + background-image: -webkit-linear-gradient(top, @startColor, @endColor); // Safari 5.1+, Chrome 10+ + background-image: -o-linear-gradient(top, @startColor, @endColor); // Opera 11.10 + background-image: linear-gradient(top, @startColor, @endColor); // The standard + background-repeat: repeat-x; + filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)",@startColor,@endColor)); // IE9 and down + } + .directional(@startColor: #555, @endColor: #333, @deg: 45deg) { + background-color: @endColor; + background-repeat: repeat-x; + background-image: -moz-linear-gradient(@deg, @startColor, @endColor); // FF 3.6+ + background-image: -ms-linear-gradient(@deg, @startColor, @endColor); // IE10 + background-image: -webkit-linear-gradient(@deg, @startColor, @endColor); // Safari 5.1+, Chrome 10+ + background-image: -o-linear-gradient(@deg, @startColor, @endColor); // Opera 11.10 + background-image: linear-gradient(@deg, @startColor, @endColor); // The standard + } + .vertical-three-colors(@startColor: #00b3ee, @midColor: #7a43b6, @colorStop: 50%, @endColor: #c3325f) { + background-color: mix(@midColor, @endColor, 80%); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(@startColor), color-stop(@colorStop, @midColor), to(@endColor)); + background-image: -webkit-linear-gradient(@startColor, @midColor @colorStop, @endColor); + background-image: -moz-linear-gradient(top, @startColor, @midColor @colorStop, @endColor); + background-image: -ms-linear-gradient(@startColor, @midColor @colorStop, @endColor); + background-image: -o-linear-gradient(@startColor, @midColor @colorStop, @endColor); + background-image: linear-gradient(@startColor, @midColor @colorStop, @endColor); + background-repeat: no-repeat; + filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)",@startColor,@endColor)); // IE9 and down, gets no color-stop at all for proper fallback + } + .radial(@innerColor: #555, @outerColor: #333) { + background-color: @outerColor; + background-image: -webkit-gradient(radial, center center, 0, center center, 460, from(@innerColor), to(@outerColor)); + background-image: -webkit-radial-gradient(circle, @innerColor, @outerColor); + background-image: -moz-radial-gradient(circle, @innerColor, @outerColor); + background-image: -ms-radial-gradient(circle, @innerColor, @outerColor); + background-image: -o-radial-gradient(circle, @innerColor, @outerColor); + background-repeat: no-repeat; + } + .striped(@color, @angle: -45deg) { + background-color: @color; + background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(.25, rgba(255,255,255,.15)), color-stop(.25, transparent), color-stop(.5, transparent), color-stop(.5, rgba(255,255,255,.15)), color-stop(.75, rgba(255,255,255,.15)), color-stop(.75, transparent), to(transparent)); + background-image: -webkit-linear-gradient(@angle, rgba(255,255,255,.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,.15) 50%, rgba(255,255,255,.15) 75%, transparent 75%, transparent); + background-image: -moz-linear-gradient(@angle, rgba(255,255,255,.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,.15) 50%, rgba(255,255,255,.15) 75%, transparent 75%, transparent); + background-image: -ms-linear-gradient(@angle, rgba(255,255,255,.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,.15) 50%, rgba(255,255,255,.15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(@angle, rgba(255,255,255,.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,.15) 50%, rgba(255,255,255,.15) 75%, transparent 75%, transparent); + background-image: linear-gradient(@angle, rgba(255,255,255,.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,.15) 50%, rgba(255,255,255,.15) 75%, transparent 75%, transparent); + } +} +// Reset filters for IE +.reset-filter() { + filter: e(%("progid:DXImageTransform.Microsoft.gradient(enabled = false)")); +} + + + +// COMPONENT MIXINS +// -------------------------------------------------- + +// Horizontal dividers +// ------------------------- +// Dividers (basically an hr) within dropdowns and nav lists +.nav-divider(@top: #e5e5e5, @bottom: @white) { + // IE7 needs a set width since we gave a height. Restricting just + // to IE7 to keep the 1px left/right space in other browsers. + // It is unclear where IE is getting the extra space that we need + // to negative-margin away, but so it goes. + *width: 100%; + height: 1px; + margin: ((@baseLineHeight / 2) - 1) 1px; // 8px 1px + *margin: -5px 0 5px; + overflow: hidden; + background-color: @top; + border-bottom: 1px solid @bottom; +} + +// Button backgrounds +// ------------------ +.buttonBackground(@startColor, @endColor) { + // gradientBar will set the background to a pleasing blend of these, to support IE<=9 + .gradientBar(@startColor, @endColor); + *background-color: @endColor; /* Darken IE7 buttons by default so they stand out more given they won't have borders */ + .reset-filter(); + + // in these cases the gradient won't cover the background, so we override + &:hover, &:active, &.active, &.disabled, &[disabled] { + background-color: @endColor; + *background-color: darken(@endColor, 5%); + } + + // IE 7 + 8 can't handle box-shadow to show active, so we darken a bit ourselves + &:active, + &.active { + background-color: darken(@endColor, 10%) e("\9"); + } +} + +// Navbar vertical align +// ------------------------- +// Vertically center elements in the navbar. +// Example: an element has a height of 30px, so write out `.navbarVerticalAlign(30px);` to calculate the appropriate top margin. +.navbarVerticalAlign(@elementHeight) { + margin-top: (@navbarHeight - @elementHeight) / 2; +} + +// Popover arrows +// ------------------------- +// For tipsies and popovers +#popoverArrow { + .top(@arrowWidth: 5px, @color: @black) { + bottom: 0; + left: 50%; + margin-left: -@arrowWidth; + border-left: @arrowWidth solid transparent; + border-right: @arrowWidth solid transparent; + border-top: @arrowWidth solid @color; + } + .left(@arrowWidth: 5px, @color: @black) { + top: 50%; + right: 0; + margin-top: -@arrowWidth; + border-top: @arrowWidth solid transparent; + border-bottom: @arrowWidth solid transparent; + border-left: @arrowWidth solid @color; + } + .bottom(@arrowWidth: 5px, @color: @black) { + top: 0; + left: 50%; + margin-left: -@arrowWidth; + border-left: @arrowWidth solid transparent; + border-right: @arrowWidth solid transparent; + border-bottom: @arrowWidth solid @color; + } + .right(@arrowWidth: 5px, @color: @black) { + top: 50%; + left: 0; + margin-top: -@arrowWidth; + border-top: @arrowWidth solid transparent; + border-bottom: @arrowWidth solid transparent; + border-right: @arrowWidth solid @color; + } +} + +// Grid System +// ----------- + +// Centered container element +.container-fixed() { + margin-right: auto; + margin-left: auto; + .clearfix(); +} + +// Table columns +.tableColumns(@columnSpan: 1) { + float: none; // undo default grid column styles + width: ((@gridColumnWidth) * @columnSpan) + (@gridGutterWidth * (@columnSpan - 1)) - 16; // 16 is total padding on left and right of table cells + margin-left: 0; // undo default grid column styles +} + +// Make a Grid +// Use .makeRow and .makeColumn to assign semantic layouts grid system behavior +.makeRow() { + margin-left: @gridGutterWidth * -1; + .clearfix(); +} +.makeColumn(@columns: 1, @offset: 0) { + float: left; + margin-left: (@gridColumnWidth * @offset) + (@gridGutterWidth * (@offset - 1)) + (@gridGutterWidth * 2); + width: (@gridColumnWidth * @columns) + (@gridGutterWidth * (@columns - 1)); +} + +// The Grid +#grid { + + .core (@gridColumnWidth, @gridGutterWidth) { + + .spanX (@index) when (@index > 0) { + (~".span@{index}") { .span(@index); } + .spanX(@index - 1); + } + .spanX (0) {} + + .offsetX (@index) when (@index > 0) { + (~".offset@{index}") { .offset(@index); } + .offsetX(@index - 1); + } + .offsetX (0) {} + + .offset (@columns) { + margin-left: (@gridColumnWidth * @columns) + (@gridGutterWidth * (@columns + 1)); + } + + .span (@columns) { + width: (@gridColumnWidth * @columns) + (@gridGutterWidth * (@columns - 1)); + } + + .row { + margin-left: @gridGutterWidth * -1; + .clearfix(); + } + + [class*="span"] { + float: left; + margin-left: @gridGutterWidth; + } + + // Set the container width, and override it for fixed navbars in media queries + .container, + .navbar-fixed-top .container, + .navbar-fixed-bottom .container { .span(@gridColumns); } + + // generate .spanX and .offsetX + .spanX (@gridColumns); + .offsetX (@gridColumns); + + } + + .fluid (@fluidGridColumnWidth, @fluidGridGutterWidth) { + + .spanX (@index) when (@index > 0) { + (~".span@{index}") { .span(@index); } + .spanX(@index - 1); + } + .spanX (0) {} + + .span (@columns) { + width: (@fluidGridColumnWidth * @columns) + (@fluidGridGutterWidth * (@columns - 1)); + *width: (@fluidGridColumnWidth * @columns) + (@fluidGridGutterWidth * (@columns - 1)) - (.5 / @gridRowWidth * 100 * 1%); + } + + .row-fluid { + width: 100%; + .clearfix(); + [class*="span"] { + .input-block-level(); + float: left; + margin-left: @fluidGridGutterWidth; + *margin-left: @fluidGridGutterWidth - (.5 / @gridRowWidth * 100 * 1%); + } + [class*="span"]:first-child { + margin-left: 0; + } + + // generate .spanX + .spanX (@gridColumns); + } + + } + + .input(@gridColumnWidth, @gridGutterWidth) { + + .spanX (@index) when (@index > 0) { + (~"input.span@{index}, textarea.span@{index}, .uneditable-input.span@{index}") { .span(@index); } + .spanX(@index - 1); + } + .spanX (0) {} + + .span(@columns) { + width: ((@gridColumnWidth) * @columns) + (@gridGutterWidth * (@columns - 1)) - 10; + } + + input, + textarea, + .uneditable-input { + margin-left: 0; // override margin-left from core grid system + } + + // generate .spanX + .spanX (@gridColumns); + + } + +} diff --git a/symposion_project/static/symposion/less/bootstrap/modals.less b/symposion_project/static/symposion/less/bootstrap/modals.less new file mode 100644 index 00000000..870ad0df --- /dev/null +++ b/symposion_project/static/symposion/less/bootstrap/modals.less @@ -0,0 +1,90 @@ +// MODALS +// ------ + +// Recalculate z-index where appropriate +.modal-open { + .dropdown-menu { z-index: @zindexDropdown + @zindexModal; } + .dropdown.open { *z-index: @zindexDropdown + @zindexModal; } + .popover { z-index: @zindexPopover + @zindexModal; } + .tooltip { z-index: @zindexTooltip + @zindexModal; } +} + +// Background +.modal-backdrop { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: @zindexModalBackdrop; + background-color: @black; + // Fade for backdrop + &.fade { opacity: 0; } +} + +.modal-backdrop, +.modal-backdrop.fade.in { + .opacity(80); +} + +// Base modal +.modal { + position: fixed; + top: 50%; + left: 50%; + z-index: @zindexModal; + overflow: auto; + width: 560px; + margin: -250px 0 0 -280px; + background-color: @white; + border: 1px solid #999; + border: 1px solid rgba(0,0,0,.3); + *border: 1px solid #999; /* IE6-7 */ + .border-radius(6px); + .box-shadow(0 3px 7px rgba(0,0,0,0.3)); + .background-clip(padding-box); + &.fade { + .transition(e('opacity .3s linear, top .3s ease-out')); + top: -25%; + } + &.fade.in { top: 50%; } +} +.modal-header { + padding: 9px 15px; + border-bottom: 1px solid #eee; + // Close icon + .close { margin-top: 2px; } +} + +// Body (where all modal content resides) +.modal-body { + overflow-y: auto; + max-height: 400px; + padding: 15px; +} +// Remove bottom margin if need be +.modal-form { + margin-bottom: 0; +} + +// Footer (for actions) +.modal-footer { + padding: 14px 15px 15px; + margin-bottom: 0; + text-align: right; // right align buttons + background-color: #f5f5f5; + border-top: 1px solid #ddd; + .border-radius(0 0 6px 6px); + .box-shadow(inset 0 1px 0 @white); + .clearfix(); // clear it in case folks use .pull-* classes on buttons + + // Properly space out buttons + .btn + .btn { + margin-left: 5px; + margin-bottom: 0; // account for input[type="submit"] which gets the bottom margin like all other inputs + } + // but override that for button groups + .btn-group .btn + .btn { + margin-left: -1px; + } +} diff --git a/symposion_project/static/symposion/less/bootstrap/navbar.less b/symposion_project/static/symposion/less/bootstrap/navbar.less new file mode 100644 index 00000000..818f8a5a --- /dev/null +++ b/symposion_project/static/symposion/less/bootstrap/navbar.less @@ -0,0 +1,358 @@ +// NAVBAR (FIXED AND STATIC) +// ------------------------- + + +// COMMON STYLES +// ------------- + +.navbar { + // Fix for IE7's bad z-indexing so dropdowns don't appear below content that follows the navbar + *position: relative; + *z-index: 2; + + overflow: visible; + margin-bottom: @baseLineHeight; +} + +// Gradient is applied to it's own element because overflow visible is not honored by IE when filter is present +.navbar-inner { + min-height: @navbarHeight; + padding-left: 20px; + padding-right: 20px; + #gradient > .vertical(@navbarBackgroundHighlight, @navbarBackground); + .border-radius(4px); + .box-shadow(~"0 1px 3px rgba(0,0,0,.25), inset 0 -1px 0 rgba(0,0,0,.1)"); +} + +// Set width to auto for default container +// We then reset it for fixed navbars in the #gridSystem mixin +.navbar .container { + width: auto; +} + +// Override the default collapsed state +.nav-collapse.collapse { + height: auto; +} + + +// Brand, links, text, and buttons +.navbar { + color: @navbarText; + // Hover and active states + .brand:hover { + text-decoration: none; + } + // Website or project name + .brand { + float: left; + display: block; + // Vertically center the text given @navbarHeight + @elementHeight: 20px; + padding: ((@navbarHeight - @elementHeight) / 2 - 2) 20px ((@navbarHeight - @elementHeight) / 2 + 2); + margin-left: -20px; // negative indent to left-align the text down the page + font-size: 20px; + font-weight: 200; + line-height: 1; + color: @navbarBrandColor; + } + // Plain text in topbar + .navbar-text { + margin-bottom: 0; + line-height: @navbarHeight; + } + // Janky solution for now to account for links outside the .nav + .navbar-link { + color: @navbarLinkColor; + &:hover { + color: @navbarLinkColorHover; + } + } + // Buttons in navbar + .btn, + .btn-group { + .navbarVerticalAlign(30px); // Vertically center in navbar + } + .btn-group .btn { + margin: 0; // then undo the margin here so we don't accidentally double it + } +} + +// Navbar forms +.navbar-form { + margin-bottom: 0; // remove default bottom margin + .clearfix(); + input, + select, + .radio, + .checkbox { + .navbarVerticalAlign(30px); // Vertically center in navbar + } + input, + select { + display: inline-block; + margin-bottom: 0; + } + input[type="image"], + input[type="checkbox"], + input[type="radio"] { + margin-top: 3px; + } + .input-append, + .input-prepend { + margin-top: 6px; + white-space: nowrap; // preven two items from separating within a .navbar-form that has .pull-left + input { + margin-top: 0; // remove the margin on top since it's on the parent + } + } +} + +// Navbar search +.navbar-search { + position: relative; + float: left; + .navbarVerticalAlign(28px); // Vertically center in navbar + margin-bottom: 0; + .search-query { + padding: 4px 9px; + #font > .sans-serif(13px, normal, 1); + color: @white; + background-color: @navbarSearchBackground; + border: 1px solid @navbarSearchBorder; + .box-shadow(~"inset 0 1px 2px rgba(0,0,0,.1), 0 1px 0 rgba(255,255,255,.15)"); + .transition(none); + + .placeholder(@navbarSearchPlaceholderColor); + + // Focus states (we use .focused since IE7-8 and down doesn't support :focus) + &:focus, + &.focused { + padding: 5px 10px; + color: @grayDark; + text-shadow: 0 1px 0 @white; + background-color: @navbarSearchBackgroundFocus; + border: 0; + .box-shadow(0 0 3px rgba(0,0,0,.15)); + outline: 0; + } + } +} + + + +// FIXED NAVBAR +// ------------ + +// Shared (top/bottom) styles +.navbar-fixed-top, +.navbar-fixed-bottom { + position: fixed; + right: 0; + left: 0; + z-index: @zindexFixedNavbar; + margin-bottom: 0; // remove 18px margin for static navbar +} +.navbar-fixed-top .navbar-inner, +.navbar-fixed-bottom .navbar-inner { + padding-left: 0; + padding-right: 0; + .border-radius(0); +} + +.navbar-fixed-top .container, +.navbar-fixed-bottom .container { + #grid > .core > .span(@gridColumns); +} + +// Fixed to top +.navbar-fixed-top { + top: 0; +} + +// Fixed to bottom +.navbar-fixed-bottom { + bottom: 0; +} + + + +// NAVIGATION +// ---------- + +.navbar .nav { + position: relative; + left: 0; + display: block; + float: left; + margin: 0 10px 0 0; +} +.navbar .nav.pull-right { + float: right; // redeclare due to specificity +} +.navbar .nav > li { + display: block; + float: left; +} + +// Links +.navbar .nav > li > a { + float: none; + // Vertically center the text given @navbarHeight + @elementHeight: 20px; + padding: ((@navbarHeight - @elementHeight) / 2 - 1) 10px ((@navbarHeight - @elementHeight) / 2 + 1); + line-height: 19px; + color: @navbarLinkColor; + text-decoration: none; + text-shadow: 0 -1px 0 rgba(0,0,0,.25); +} +// Buttons +.navbar .btn { + display: inline-block; + padding: 4px 10px 4px; + // Vertically center the button given @navbarHeight + @elementHeight: 28px; + margin: ((@navbarHeight - @elementHeight) / 2 - 1) 5px ((@navbarHeight - @elementHeight) / 2); + line-height: @baseLineHeight; +} +.navbar .btn-group { + margin: 0; + // Vertically center the button given @navbarHeight + @elementHeight: 28px; + padding: ((@navbarHeight - @elementHeight) / 2 - 1) 5px ((@navbarHeight - @elementHeight) / 2); +} +// Hover +.navbar .nav > li > a:hover { + background-color: @navbarLinkBackgroundHover; // "transparent" is default to differentiate :hover from .active + color: @navbarLinkColorHover; + text-decoration: none; +} + +// Active nav items +.navbar .nav .active > a, +.navbar .nav .active > a:hover { + color: @navbarLinkColorActive; + text-decoration: none; + background-color: @navbarLinkBackgroundActive; +} + +// Dividers (basically a vertical hr) +.navbar .divider-vertical { + height: @navbarHeight; + width: 1px; + margin: 0 9px; + overflow: hidden; + background-color: @navbarBackground; + border-right: 1px solid @navbarBackgroundHighlight; +} + +// Secondary (floated right) nav in topbar +.navbar .nav.pull-right { + margin-left: 10px; + margin-right: 0; +} + +// Navbar button for toggling navbar items in responsive layouts +// These definitions need to come after '.navbar .btn' +.navbar .btn-navbar { + display: none; + float: right; + padding: 7px 10px; + margin-left: 5px; + margin-right: 5px; + .buttonBackground(@navbarBackgroundHighlight, @navbarBackground); + .box-shadow(~"inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.075)"); +} +.navbar .btn-navbar .icon-bar { + display: block; + width: 18px; + height: 2px; + background-color: #f5f5f5; + .border-radius(1px); + .box-shadow(0 1px 0 rgba(0,0,0,.25)); +} +.btn-navbar .icon-bar + .icon-bar { + margin-top: 3px; +} + + +// Dropdown menus +// -------------- + +// Menu position and menu carets +.navbar .dropdown-menu { + &:before { + content: ''; + display: inline-block; + border-left: 7px solid transparent; + border-right: 7px solid transparent; + border-bottom: 7px solid #ccc; + border-bottom-color: @dropdownBorder; + position: absolute; + top: -7px; + left: 9px; + } + &:after { + content: ''; + display: inline-block; + border-left: 6px solid transparent; + border-right: 6px solid transparent; + border-bottom: 6px solid @dropdownBackground; + position: absolute; + top: -6px; + left: 10px; + } +} +// Menu position and menu caret support for dropups via extra dropup class +.navbar-fixed-bottom .dropdown-menu { + &:before { + border-top: 7px solid #ccc; + border-top-color: @dropdownBorder; + border-bottom: 0; + bottom: -7px; + top: auto; + } + &:after { + border-top: 6px solid @dropdownBackground; + border-bottom: 0; + bottom: -6px; + top: auto; + } +} +// Dropdown toggle caret +.navbar .nav li.dropdown .dropdown-toggle .caret, +.navbar .nav li.dropdown.open .caret { + border-top-color: @white; + border-bottom-color: @white; +} +.navbar .nav li.dropdown.active .caret { + .opacity(100); +} + +// Remove background color from open dropdown +.navbar .nav li.dropdown.open > .dropdown-toggle, +.navbar .nav li.dropdown.active > .dropdown-toggle, +.navbar .nav li.dropdown.open.active > .dropdown-toggle { + background-color: transparent; +} + +// Dropdown link on hover +.navbar .nav li.dropdown.active > .dropdown-toggle:hover { + color: @white; +} + +// Right aligned menus need alt position +// TODO: rejigger this at some point to simplify the selectors +.navbar .pull-right .dropdown-menu, +.navbar .dropdown-menu.pull-right { + left: auto; + right: 0; + &:before { + left: auto; + right: 12px; + } + &:after { + left: auto; + right: 13px; + } +} \ No newline at end of file diff --git a/symposion_project/static/symposion/less/bootstrap/navs.less b/symposion_project/static/symposion/less/bootstrap/navs.less new file mode 100644 index 00000000..5cb9f9f3 --- /dev/null +++ b/symposion_project/static/symposion/less/bootstrap/navs.less @@ -0,0 +1,363 @@ +// NAVIGATIONS +// ----------- + + + +// BASE CLASS +// ---------- + +.nav { + margin-left: 0; + margin-bottom: @baseLineHeight; + list-style: none; +} + +// Make links block level +.nav > li > a { + display: block; +} +.nav > li > a:hover { + text-decoration: none; + background-color: @grayLighter; +} + +// Redeclare pull classes because of specifity +.nav > .pull-right { + float: right; +} + +// Nav headers (for dropdowns and lists) +.nav .nav-header { + display: block; + padding: 3px 15px; + font-size: 11px; + font-weight: bold; + line-height: @baseLineHeight; + color: @grayLight; + text-shadow: 0 1px 0 rgba(255,255,255,.5); + text-transform: uppercase; +} +// Space them out when they follow another list item (link) +.nav li + .nav-header { + margin-top: 9px; +} + + +// NAV LIST +// -------- + +.nav-list { + padding-left: 15px; + padding-right: 15px; + margin-bottom: 0; +} +.nav-list > li > a, +.nav-list .nav-header { + margin-left: -15px; + margin-right: -15px; + text-shadow: 0 1px 0 rgba(255,255,255,.5); +} +.nav-list > li > a { + padding: 3px 15px; +} +.nav-list > .active > a, +.nav-list > .active > a:hover { + color: @white; + text-shadow: 0 -1px 0 rgba(0,0,0,.2); + background-color: @linkColor; +} +.nav-list [class^="icon-"] { + margin-right: 2px; +} +// Dividers (basically an hr) within the dropdown +.nav-list .divider { + .nav-divider(); +} + + + +// TABS AND PILLS +// ------------- + +// Common styles +.nav-tabs, +.nav-pills { + .clearfix(); +} +.nav-tabs > li, +.nav-pills > li { + float: left; +} +.nav-tabs > li > a, +.nav-pills > li > a { + padding-right: 12px; + padding-left: 12px; + margin-right: 2px; + line-height: 14px; // keeps the overall height an even number +} + +// TABS +// ---- + +// Give the tabs something to sit on +.nav-tabs { + border-bottom: 1px solid #ddd; +} +// Make the list-items overlay the bottom border +.nav-tabs > li { + margin-bottom: -1px; +} +// Actual tabs (as links) +.nav-tabs > li > a { + padding-top: 8px; + padding-bottom: 8px; + line-height: @baseLineHeight; + border: 1px solid transparent; + .border-radius(4px 4px 0 0); + &:hover { + border-color: @grayLighter @grayLighter #ddd; + } +} +// Active state, and it's :hover to override normal :hover +.nav-tabs > .active > a, +.nav-tabs > .active > a:hover { + color: @gray; + background-color: @white; + border: 1px solid #ddd; + border-bottom-color: transparent; + cursor: default; +} + + +// PILLS +// ----- + +// Links rendered as pills +.nav-pills > li > a { + padding-top: 8px; + padding-bottom: 8px; + margin-top: 2px; + margin-bottom: 2px; + .border-radius(5px); +} + +// Active state +.nav-pills > .active > a, +.nav-pills > .active > a:hover { + color: @white; + background-color: @linkColor; +} + + + +// STACKED NAV +// ----------- + +// Stacked tabs and pills +.nav-stacked > li { + float: none; +} +.nav-stacked > li > a { + margin-right: 0; // no need for the gap between nav items +} + +// Tabs +.nav-tabs.nav-stacked { + border-bottom: 0; +} +.nav-tabs.nav-stacked > li > a { + border: 1px solid #ddd; + .border-radius(0); +} +.nav-tabs.nav-stacked > li:first-child > a { + .border-radius(4px 4px 0 0); +} +.nav-tabs.nav-stacked > li:last-child > a { + .border-radius(0 0 4px 4px); +} +.nav-tabs.nav-stacked > li > a:hover { + border-color: #ddd; + z-index: 2; +} + +// Pills +.nav-pills.nav-stacked > li > a { + margin-bottom: 3px; +} +.nav-pills.nav-stacked > li:last-child > a { + margin-bottom: 1px; // decrease margin to match sizing of stacked tabs +} + + + +// DROPDOWNS +// --------- + +.nav-tabs .dropdown-menu { + .border-radius(0 0 5px 5px); // remove the top rounded corners here since there is a hard edge above the menu +} +.nav-pills .dropdown-menu { + .border-radius(4px); // make rounded corners match the pills +} + +// Default dropdown links +// ------------------------- +// Make carets use linkColor to start +.nav-tabs .dropdown-toggle .caret, +.nav-pills .dropdown-toggle .caret { + border-top-color: @linkColor; + border-bottom-color: @linkColor; + margin-top: 6px; +} +.nav-tabs .dropdown-toggle:hover .caret, +.nav-pills .dropdown-toggle:hover .caret { + border-top-color: @linkColorHover; + border-bottom-color: @linkColorHover; +} + +// Active dropdown links +// ------------------------- +.nav-tabs .active .dropdown-toggle .caret, +.nav-pills .active .dropdown-toggle .caret { + border-top-color: @grayDark; + border-bottom-color: @grayDark; +} + +// Active:hover dropdown links +// ------------------------- +.nav > .dropdown.active > a:hover { + color: @black; + cursor: pointer; +} + +// Open dropdowns +// ------------------------- +.nav-tabs .open .dropdown-toggle, +.nav-pills .open .dropdown-toggle, +.nav > li.dropdown.open.active > a:hover { + color: @white; + background-color: @grayLight; + border-color: @grayLight; +} +.nav li.dropdown.open .caret, +.nav li.dropdown.open.active .caret, +.nav li.dropdown.open a:hover .caret { + border-top-color: @white; + border-bottom-color: @white; + .opacity(100); +} + +// Dropdowns in stacked tabs +.tabs-stacked .open > a:hover { + border-color: @grayLight; +} + + + +// TABBABLE +// -------- + + +// COMMON STYLES +// ------------- + +// Clear any floats +.tabbable { + .clearfix(); +} +.tab-content { + overflow: auto; // prevent content from running below tabs +} + +// Remove border on bottom, left, right +.tabs-below > .nav-tabs, +.tabs-right > .nav-tabs, +.tabs-left > .nav-tabs { + border-bottom: 0; +} + +// Show/hide tabbable areas +.tab-content > .tab-pane, +.pill-content > .pill-pane { + display: none; +} +.tab-content > .active, +.pill-content > .active { + display: block; +} + + +// BOTTOM +// ------ + +.tabs-below > .nav-tabs { + border-top: 1px solid #ddd; +} +.tabs-below > .nav-tabs > li { + margin-top: -1px; + margin-bottom: 0; +} +.tabs-below > .nav-tabs > li > a { + .border-radius(0 0 4px 4px); + &:hover { + border-bottom-color: transparent; + border-top-color: #ddd; + } +} +.tabs-below > .nav-tabs > .active > a, +.tabs-below > .nav-tabs > .active > a:hover { + border-color: transparent #ddd #ddd #ddd; +} + +// LEFT & RIGHT +// ------------ + +// Common styles +.tabs-left > .nav-tabs > li, +.tabs-right > .nav-tabs > li { + float: none; +} +.tabs-left > .nav-tabs > li > a, +.tabs-right > .nav-tabs > li > a { + min-width: 74px; + margin-right: 0; + margin-bottom: 3px; +} + +// Tabs on the left +.tabs-left > .nav-tabs { + float: left; + margin-right: 19px; + border-right: 1px solid #ddd; +} +.tabs-left > .nav-tabs > li > a { + margin-right: -1px; + .border-radius(4px 0 0 4px); +} +.tabs-left > .nav-tabs > li > a:hover { + border-color: @grayLighter #ddd @grayLighter @grayLighter; +} +.tabs-left > .nav-tabs .active > a, +.tabs-left > .nav-tabs .active > a:hover { + border-color: #ddd transparent #ddd #ddd; + *border-right-color: @white; +} + +// Tabs on the right +.tabs-right > .nav-tabs { + float: right; + margin-left: 19px; + border-left: 1px solid #ddd; +} +.tabs-right > .nav-tabs > li > a { + margin-left: -1px; + .border-radius(0 4px 4px 0); +} +.tabs-right > .nav-tabs > li > a:hover { + border-color: @grayLighter @grayLighter @grayLighter #ddd; +} +.tabs-right > .nav-tabs .active > a, +.tabs-right > .nav-tabs .active > a:hover { + border-color: #ddd #ddd #ddd transparent; + *border-left-color: @white; +} diff --git a/symposion_project/static/symposion/less/bootstrap/pager.less b/symposion_project/static/symposion/less/bootstrap/pager.less new file mode 100644 index 00000000..4244b5eb --- /dev/null +++ b/symposion_project/static/symposion/less/bootstrap/pager.less @@ -0,0 +1,36 @@ +// PAGER +// ----- + +.pager { + margin-left: 0; + margin-bottom: @baseLineHeight; + list-style: none; + text-align: center; + .clearfix(); +} +.pager li { + display: inline; +} +.pager a { + display: inline-block; + padding: 5px 14px; + background-color: #fff; + border: 1px solid #ddd; + .border-radius(15px); +} +.pager a:hover { + text-decoration: none; + background-color: #f5f5f5; +} +.pager .next a { + float: right; +} +.pager .previous a { + float: left; +} +.pager .disabled a, +.pager .disabled a:hover { + color: @grayLight; + background-color: #fff; + cursor: default; +} \ No newline at end of file diff --git a/symposion_project/static/symposion/less/bootstrap/pagination.less b/symposion_project/static/symposion/less/bootstrap/pagination.less new file mode 100644 index 00000000..38cf65cc --- /dev/null +++ b/symposion_project/static/symposion/less/bootstrap/pagination.less @@ -0,0 +1,56 @@ +// PAGINATION +// ---------- + +.pagination { + height: @baseLineHeight * 2; + margin: @baseLineHeight 0; + } +.pagination ul { + display: inline-block; + .ie7-inline-block(); + margin-left: 0; + margin-bottom: 0; + .border-radius(3px); + .box-shadow(0 1px 2px rgba(0,0,0,.05)); +} +.pagination li { + display: inline; + } +.pagination a { + float: left; + padding: 0 14px; + line-height: (@baseLineHeight * 2) - 2; + text-decoration: none; + border: 1px solid #ddd; + border-left-width: 0; +} +.pagination a:hover, +.pagination .active a { + background-color: #f5f5f5; +} +.pagination .active a { + color: @grayLight; + cursor: default; +} +.pagination .disabled span, +.pagination .disabled a, +.pagination .disabled a:hover { + color: @grayLight; + background-color: transparent; + cursor: default; +} +.pagination li:first-child a { + border-left-width: 1px; + .border-radius(3px 0 0 3px); +} +.pagination li:last-child a { + .border-radius(0 3px 3px 0); +} + +// Centered +.pagination-centered { + text-align: center; +} +.pagination-right { + text-align: right; +} diff --git a/symposion_project/static/symposion/less/bootstrap/popovers.less b/symposion_project/static/symposion/less/bootstrap/popovers.less new file mode 100644 index 00000000..558d99ec --- /dev/null +++ b/symposion_project/static/symposion/less/bootstrap/popovers.less @@ -0,0 +1,49 @@ +// POPOVERS +// -------- + +.popover { + position: absolute; + top: 0; + left: 0; + z-index: @zindexPopover; + display: none; + padding: 5px; + &.top { margin-top: -5px; } + &.right { margin-left: 5px; } + &.bottom { margin-top: 5px; } + &.left { margin-left: -5px; } + &.top .arrow { #popoverArrow > .top(); } + &.right .arrow { #popoverArrow > .right(); } + &.bottom .arrow { #popoverArrow > .bottom(); } + &.left .arrow { #popoverArrow > .left(); } + .arrow { + position: absolute; + width: 0; + height: 0; + } +} +.popover-inner { + padding: 3px; + width: 280px; + overflow: hidden; + background: @black; // has to be full background declaration for IE fallback + background: rgba(0,0,0,.8); + .border-radius(6px); + .box-shadow(0 3px 7px rgba(0,0,0,0.3)); +} +.popover-title { + padding: 9px 15px; + line-height: 1; + background-color: #f5f5f5; + border-bottom:1px solid #eee; + .border-radius(3px 3px 0 0); +} +.popover-content { + padding: 14px; + background-color: @white; + .border-radius(0 0 3px 3px); + .background-clip(padding-box); + p, ul, ol { + margin-bottom: 0; + } +} diff --git a/symposion_project/static/symposion/less/bootstrap/progress-bars.less b/symposion_project/static/symposion/less/bootstrap/progress-bars.less new file mode 100644 index 00000000..3b47e648 --- /dev/null +++ b/symposion_project/static/symposion/less/bootstrap/progress-bars.less @@ -0,0 +1,117 @@ +// PROGRESS BARS +// ------------- + + +// ANIMATIONS +// ---------- + +// Webkit +@-webkit-keyframes progress-bar-stripes { + from { background-position: 40px 0; } + to { background-position: 0 0; } +} + +// Firefox +@-moz-keyframes progress-bar-stripes { + from { background-position: 40px 0; } + to { background-position: 0 0; } +} + +// IE9 +@-ms-keyframes progress-bar-stripes { + from { background-position: 40px 0; } + to { background-position: 0 0; } +} + +// Opera +@-o-keyframes progress-bar-stripes { + from { background-position: 0 0; } + to { background-position: 40px 0; } +} + +// Spec +@keyframes progress-bar-stripes { + from { background-position: 40px 0; } + to { background-position: 0 0; } +} + + + +// THE BARS +// -------- + +// Outer container +.progress { + overflow: hidden; + height: 18px; + margin-bottom: 18px; + #gradient > .vertical(#f5f5f5, #f9f9f9); + .box-shadow(inset 0 1px 2px rgba(0,0,0,.1)); + .border-radius(4px); +} + +// Bar of progress +.progress .bar { + width: 0%; + height: 18px; + color: @white; + font-size: 12px; + text-align: center; + text-shadow: 0 -1px 0 rgba(0,0,0,.25); + #gradient > .vertical(#149bdf, #0480be); + .box-shadow(inset 0 -1px 0 rgba(0,0,0,.15)); + .box-sizing(border-box); + .transition(width .6s ease); +} + +// Striped bars +.progress-striped .bar { + #gradient > .striped(#149bdf); + .background-size(40px 40px); +} + +// Call animation for the active one +.progress.active .bar { + -webkit-animation: progress-bar-stripes 2s linear infinite; + -moz-animation: progress-bar-stripes 2s linear infinite; + -ms-animation: progress-bar-stripes 2s linear infinite; + -o-animation: progress-bar-stripes 2s linear infinite; + animation: progress-bar-stripes 2s linear infinite; +} + + + +// COLORS +// ------ + +// Danger (red) +.progress-danger .bar { + #gradient > .vertical(#ee5f5b, #c43c35); +} +.progress-danger.progress-striped .bar { + #gradient > .striped(#ee5f5b); +} + +// Success (green) +.progress-success .bar { + #gradient > .vertical(#62c462, #57a957); +} +.progress-success.progress-striped .bar { + #gradient > .striped(#62c462); +} + +// Info (teal) +.progress-info .bar { + #gradient > .vertical(#5bc0de, #339bb9); +} +.progress-info.progress-striped .bar { + #gradient > .striped(#5bc0de); +} + +// Warning (orange) +.progress-warning .bar { + #gradient > .vertical(lighten(@orange, 15%), @orange); +} +.progress-warning.progress-striped .bar { + #gradient > .striped(lighten(@orange, 15%)); +} diff --git a/symposion_project/static/symposion/less/bootstrap/reset.less b/symposion_project/static/symposion/less/bootstrap/reset.less new file mode 100644 index 00000000..2332e03c --- /dev/null +++ b/symposion_project/static/symposion/less/bootstrap/reset.less @@ -0,0 +1,131 @@ +// Reset.less +// Adapted from Normalize.css http://github.com/necolas/normalize.css +// ------------------------------------------------------------------------ + +// Display in IE6-9 and FF3 +// ------------------------- + +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +nav, +section { + display: block; +} + +// Display block in IE6-9 and FF3 +// ------------------------- + +audio, +canvas, +video { + display: inline-block; + *display: inline; + *zoom: 1; +} + +// Prevents modern browsers from displaying 'audio' without controls +// ------------------------- + +audio:not([controls]) { + display: none; +} + +// Base settings +// ------------------------- + +html { + font-size: 100%; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; +} +// Focus states +a:focus { + .tab-focus(); +} +// Hover & Active +a:hover, +a:active { + outline: 0; +} + +// Prevents sub and sup affecting line-height in all browsers +// ------------------------- + +sub, +sup { + position: relative; + font-size: 75%; + line-height: 0; + vertical-align: baseline; +} +sup { + top: -0.5em; +} +sub { + bottom: -0.25em; +} + +// Img border in a's and image quality +// ------------------------- + +img { + max-width: 100%; // Make images inherently responsive + vertical-align: middle; + border: 0; + -ms-interpolation-mode: bicubic; +} + +// Prevent max-width from affecting Google Maps +#map_canvas img { + max-width: none; +} + +// Forms +// ------------------------- + +// Font size in all browsers, margin changes, misc consistency +button, +input, +select, +textarea { + margin: 0; + font-size: 100%; + vertical-align: middle; +} +button, +input { + *overflow: visible; // Inner spacing ie IE6/7 + line-height: normal; // FF3/4 have !important on line-height in UA stylesheet +} +button::-moz-focus-inner, +input::-moz-focus-inner { // Inner padding and border oddities in FF3/4 + padding: 0; + border: 0; +} +button, +input[type="button"], +input[type="reset"], +input[type="submit"] { + cursor: pointer; // Cursors on all buttons applied consistently + -webkit-appearance: button; // Style clickable inputs in iOS +} +input[type="search"] { // Appearance in Safari/Chrome + -webkit-box-sizing: content-box; + -moz-box-sizing: content-box; + box-sizing: content-box; + -webkit-appearance: textfield; +} +input[type="search"]::-webkit-search-decoration, +input[type="search"]::-webkit-search-cancel-button { + -webkit-appearance: none; // Inner-padding issues in Chrome OSX, Safari 5 +} +textarea { + overflow: auto; // Remove vertical scrollbar in IE6-9 + vertical-align: top; // Readability and alignment cross-browser +} diff --git a/symposion_project/static/symposion/less/bootstrap/responsive-1200px-min.less b/symposion_project/static/symposion/less/bootstrap/responsive-1200px-min.less new file mode 100644 index 00000000..a7c9f4e0 --- /dev/null +++ b/symposion_project/static/symposion/less/bootstrap/responsive-1200px-min.less @@ -0,0 +1,26 @@ +// LARGE DESKTOP & UP +// ------------------ + +@media (min-width: 1200px) { + + // Fixed grid + #grid > .core(70px, 30px); + + // Fluid grid + #grid > .fluid(5.982905983%, 2.564102564%); + + // Input grid + #grid > .input(70px, 30px); + + // Thumbnails + .thumbnails { + margin-left: -30px; + } + .thumbnails > li { + margin-left: 30px; + } + .row-fluid .thumbnails { + margin-left: 0; + } + +} diff --git a/symposion_project/static/symposion/less/bootstrap/responsive-767px-max.less b/symposion_project/static/symposion/less/bootstrap/responsive-767px-max.less new file mode 100644 index 00000000..614c6905 --- /dev/null +++ b/symposion_project/static/symposion/less/bootstrap/responsive-767px-max.less @@ -0,0 +1,149 @@ +// UP TO LANDSCAPE PHONE +// --------------------- + +@media (max-width: 480px) { + + // Smooth out the collapsing/expanding nav + .nav-collapse { + -webkit-transform: translate3d(0, 0, 0); // activate the GPU + } + + // Block level the page header small tag for readability + .page-header h1 small { + display: block; + line-height: @baseLineHeight; + } + + // Update checkboxes for iOS + input[type="checkbox"], + input[type="radio"] { + border: 1px solid #ccc; + } + + // Remove the horizontal form styles + .form-horizontal .control-group > label { + float: none; + width: auto; + padding-top: 0; + text-align: left; + } + // Move over all input controls and content + .form-horizontal .controls { + margin-left: 0; + } + // Move the options list down to align with labels + .form-horizontal .control-list { + padding-top: 0; // has to be padding because margin collaspes + } + // Move over buttons in .form-actions to align with .controls + .form-horizontal .form-actions { + padding-left: 10px; + padding-right: 10px; + } + + // Modals + .modal { + position: absolute; + top: 10px; + left: 10px; + right: 10px; + width: auto; + margin: 0; + &.fade.in { top: auto; } + } + .modal-header .close { + padding: 10px; + margin: -10px; + } + + // Carousel + .carousel-caption { + position: static; + } + +} + + + +// LANDSCAPE PHONE TO SMALL DESKTOP & PORTRAIT TABLET +// -------------------------------------------------- + +@media (max-width: 767px) { + + // Padding to set content in a bit + body { + padding-left: 20px; + padding-right: 20px; + } + // Negative indent the now static "fixed" navbar + .navbar-fixed-top, + .navbar-fixed-bottom { + margin-left: -20px; + margin-right: -20px; + } + // Remove padding on container given explicit padding set on body + .container-fluid { + padding: 0; + } + + // TYPOGRAPHY + // ---------- + // Reset horizontal dl + .dl-horizontal { + dt { + float: none; + clear: none; + width: auto; + text-align: left; + } + dd { + margin-left: 0; + } + } + + // GRID & CONTAINERS + // ----------------- + // Remove width from containers + .container { + width: auto; + } + // Fluid rows + .row-fluid { + width: 100%; + } + // Undo negative margin on rows and thumbnails + .row, + .thumbnails { + margin-left: 0; + } + // Make all grid-sized elements block level again + [class*="span"], + .row-fluid [class*="span"] { + float: none; + display: block; + width: auto; + margin-left: 0; + } + + // FORM FIELDS + // ----------- + // Make span* classes full width + .input-large, + .input-xlarge, + .input-xxlarge, + input[class*="span"], + select[class*="span"], + textarea[class*="span"], + .uneditable-input { + .input-block-level(); + } + // But don't let it screw up prepend/append inputs + .input-prepend input, + .input-append input, + .input-prepend input[class*="span"], + .input-append input[class*="span"] { + display: inline-block; // redeclare so they don't wrap to new lines + width: auto; + } + +} diff --git a/symposion_project/static/symposion/less/bootstrap/responsive-768px-979px.less b/symposion_project/static/symposion/less/bootstrap/responsive-768px-979px.less new file mode 100644 index 00000000..76f4f6df --- /dev/null +++ b/symposion_project/static/symposion/less/bootstrap/responsive-768px-979px.less @@ -0,0 +1,17 @@ +// PORTRAIT TABLET TO DEFAULT DESKTOP +// ---------------------------------- + +@media (min-width: 768px) and (max-width: 979px) { + + // Fixed grid + #grid > .core(42px, 20px); + + // Fluid grid + #grid > .fluid(5.801104972%, 2.762430939%); + + // Input grid + #grid > .input(42px, 20px); + + // No need to reset .thumbnails here since it's the same @gridGutterWidth + +} diff --git a/symposion_project/static/symposion/less/bootstrap/responsive-navbar.less b/symposion_project/static/symposion/less/bootstrap/responsive-navbar.less new file mode 100644 index 00000000..8d31db53 --- /dev/null +++ b/symposion_project/static/symposion/less/bootstrap/responsive-navbar.less @@ -0,0 +1,153 @@ +// TABLETS AND BELOW +// ----------------- +@media (max-width: 979px) { + + // UNFIX THE TOPBAR + // ---------------- + // Remove any padding from the body + body { + padding-top: 0; + } + // Unfix the navbar + .navbar-fixed-top, + .navbar-fixed-bottom { + position: static; + } + .navbar-fixed-top { + margin-bottom: @baseLineHeight; + } + .navbar-fixed-bottom { + margin-top: @baseLineHeight; + } + .navbar-fixed-top .navbar-inner, + .navbar-fixed-bottom .navbar-inner { + padding: 5px; + } + .navbar .container { + width: auto; + padding: 0; + } + // Account for brand name + .navbar .brand { + padding-left: 10px; + padding-right: 10px; + margin: 0 0 0 -5px; + } + + // COLLAPSIBLE NAVBAR + // ------------------ + // Nav collapse clears brand + .nav-collapse { + clear: both; + } + // Block-level the nav + .nav-collapse .nav { + float: none; + margin: 0 0 (@baseLineHeight / 2); + } + .nav-collapse .nav > li { + float: none; + } + .nav-collapse .nav > li > a { + margin-bottom: 2px; + } + .nav-collapse .nav > .divider-vertical { + display: none; + } + .nav-collapse .nav .nav-header { + color: @navbarText; + text-shadow: none; + } + // Nav and dropdown links in navbar + .nav-collapse .nav > li > a, + .nav-collapse .dropdown-menu a { + padding: 6px 15px; + font-weight: bold; + color: @navbarLinkColor; + .border-radius(3px); + } + // Buttons + .nav-collapse .btn { + padding: 4px 10px 4px; + font-weight: normal; + .border-radius(4px); + } + .nav-collapse .dropdown-menu li + li a { + margin-bottom: 2px; + } + .nav-collapse .nav > li > a:hover, + .nav-collapse .dropdown-menu a:hover { + background-color: @navbarBackground; + } + // Buttons in the navbar + .nav-collapse.in .btn-group { + margin-top: 5px; + padding: 0; + } + // Dropdowns in the navbar + .nav-collapse .dropdown-menu { + position: static; + top: auto; + left: auto; + float: none; + display: block; + max-width: none; + margin: 0 15px; + padding: 0; + background-color: transparent; + border: none; + .border-radius(0); + .box-shadow(none); + } + .nav-collapse .dropdown-menu:before, + .nav-collapse .dropdown-menu:after { + display: none; + } + .nav-collapse .dropdown-menu .divider { + display: none; + } + // Forms in navbar + .nav-collapse .navbar-form, + .nav-collapse .navbar-search { + float: none; + padding: (@baseLineHeight / 2) 15px; + margin: (@baseLineHeight / 2) 0; + border-top: 1px solid @navbarBackground; + border-bottom: 1px solid @navbarBackground; + .box-shadow(~"inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.1)"); + } + // Pull right (secondary) nav content + .navbar .nav-collapse .nav.pull-right { + float: none; + margin-left: 0; + } + // Hide everything in the navbar save .brand and toggle button */ + .nav-collapse, + .nav-collapse.collapse { + overflow: hidden; + height: 0; + } + // Navbar button + .navbar .btn-navbar { + display: block; + } + + // STATIC NAVBAR + // ------------- + .navbar-static .navbar-inner { + padding-left: 10px; + padding-right: 10px; + } +} + + +// DEFAULT DESKTOP +// --------------- + +// Required to make the collapsing navbar work on regular desktops +@media (min-width: 980px) { + .nav-collapse.collapse { + height: auto !important; + overflow: visible !important; + } +} \ No newline at end of file diff --git a/symposion_project/static/symposion/less/bootstrap/responsive-utilities.less b/symposion_project/static/symposion/less/bootstrap/responsive-utilities.less new file mode 100644 index 00000000..572846c0 --- /dev/null +++ b/symposion_project/static/symposion/less/bootstrap/responsive-utilities.less @@ -0,0 +1,41 @@ +// RESPONSIVE CLASSES +// ------------------ + +// Hide from screenreaders and browsers +// Credit: HTML5 Boilerplate +.hidden { + display: none; + visibility: hidden; +} + +// Visibility utilities + +// For desktops +.visible-phone { display: none !important; } +.visible-tablet { display: none !important; } +.visible-desktop { } // Don't set initially +.hidden-phone { } +.hidden-tablet { } +.hidden-desktop { display: none !important; } + +// Phones only +@media (max-width: 767px) { + // Show + .visible-phone { display: inherit !important; } // Use inherit to restore previous behavior + // Hide + .hidden-phone { display: none !important; } + // Hide everything else + .hidden-desktop { display: inherit !important; } + .visible-desktop { display: none !important; } +} + +// Tablets & small desktops only +@media (min-width: 768px) and (max-width: 979px) { + // Show + .visible-tablet { display: inherit !important; } + // Hide + .hidden-tablet { display: none !important; } + // Hide everything else + .hidden-desktop { display: inherit !important; } + .visible-desktop { display: none !important ; } +} diff --git a/symposion_project/static/symposion/less/bootstrap/responsive.less b/symposion_project/static/symposion/less/bootstrap/responsive.less new file mode 100644 index 00000000..734b1988 --- /dev/null +++ b/symposion_project/static/symposion/less/bootstrap/responsive.less @@ -0,0 +1,48 @@ +/*! + * Bootstrap Responsive v2.0.4 + * + * Copyright 2012 Twitter, Inc + * Licensed under the Apache License v2.0 + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Designed and built with all the love in the world @twitter by @mdo and @fat. + */ + + +// Responsive.less +// For phone and tablet devices +// ------------------------------------------------------------- + + +// REPEAT VARIABLES & MIXINS +// ------------------------- +// Required since we compile the responsive stuff separately + +@import "variables.less"; // Modify this for custom colors, font-sizes, etc +@import "mixins.less"; + + +// RESPONSIVE CLASSES +// ------------------ + +@import "responsive-utilities.less"; + + +// MEDIA QUERIES +// ------------------ + +// Phones to portrait tablets and narrow desktops +@import "responsive-767px-max.less"; + +// Tablets to regular desktops +@import "responsive-768px-979px.less"; + +// Large desktops +@import "responsive-1200px-min.less"; + + +// RESPONSIVE NAVBAR +// ------------------ + +// From 979px and below, show a button to toggle navbar contents +@import "responsive-navbar.less"; diff --git a/symposion_project/static/symposion/less/bootstrap/scaffolding.less b/symposion_project/static/symposion/less/bootstrap/scaffolding.less new file mode 100644 index 00000000..57c74eda --- /dev/null +++ b/symposion_project/static/symposion/less/bootstrap/scaffolding.less @@ -0,0 +1,29 @@ +// Scaffolding +// Basic and global styles for generating a grid system, structural layout, and page templates +// ------------------------------------------------------------------------------------------- + + +// Body reset +// ---------- + +body { + margin: 0; + font-family: @baseFontFamily; + font-size: @baseFontSize; + line-height: @baseLineHeight; + color: @textColor; + background-color: @bodyBackground; +} + + +// Links +// ----- + +a { + color: @linkColor; + text-decoration: none; +} +a:hover { + color: @linkColorHover; + text-decoration: underline; +} diff --git a/symposion_project/static/symposion/less/bootstrap/sprites.less b/symposion_project/static/symposion/less/bootstrap/sprites.less new file mode 100644 index 00000000..72a3a916 --- /dev/null +++ b/symposion_project/static/symposion/less/bootstrap/sprites.less @@ -0,0 +1,191 @@ +// SPRITES +// Glyphs and icons for buttons, nav, and more +// ------------------------------------------- + + +// ICONS +// ----- + +// All icons receive the styles of the tag with a base class +// of .i and are then given a unique class to add width, height, +// and background-position. Your resulting HTML will look like +// . + +// For the white version of the icons, just add the .icon-white class: +// + +[class^="icon-"], +[class*=" icon-"] { + display: inline-block; + width: 14px; + height: 14px; + .ie7-restore-right-whitespace(); + line-height: 14px; + vertical-align: text-top; + background-image: url("@{iconSpritePath}"); + background-position: 14px 14px; + background-repeat: no-repeat; + +} +.icon-white { + background-image: url("@{iconWhiteSpritePath}"); +} + +.icon-glass { background-position: 0 0; } +.icon-music { background-position: -24px 0; } +.icon-search { background-position: -48px 0; } +.icon-envelope { background-position: -72px 0; } +.icon-heart { background-position: -96px 0; } +.icon-star { background-position: -120px 0; } +.icon-star-empty { background-position: -144px 0; } +.icon-user { background-position: -168px 0; } +.icon-film { background-position: -192px 0; } +.icon-th-large { background-position: -216px 0; } +.icon-th { background-position: -240px 0; } +.icon-th-list { background-position: -264px 0; } +.icon-ok { background-position: -288px 0; } +.icon-remove { background-position: -312px 0; } +.icon-zoom-in { background-position: -336px 0; } +.icon-zoom-out { background-position: -360px 0; } +.icon-off { background-position: -384px 0; } +.icon-signal { background-position: -408px 0; } +.icon-cog { background-position: -432px 0; } +.icon-trash { background-position: -456px 0; } + +.icon-home { background-position: 0 -24px; } +.icon-file { background-position: -24px -24px; } +.icon-time { background-position: -48px -24px; } +.icon-road { background-position: -72px -24px; } +.icon-download-alt { background-position: -96px -24px; } +.icon-download { background-position: -120px -24px; } +.icon-upload { background-position: -144px -24px; } +.icon-inbox { background-position: -168px -24px; } +.icon-play-circle { background-position: -192px -24px; } +.icon-repeat { background-position: -216px -24px; } +.icon-refresh { background-position: -240px -24px; } +.icon-list-alt { background-position: -264px -24px; } +.icon-lock { background-position: -287px -24px; } // 1px off +.icon-flag { background-position: -312px -24px; } +.icon-headphones { background-position: -336px -24px; } +.icon-volume-off { background-position: -360px -24px; } +.icon-volume-down { background-position: -384px -24px; } +.icon-volume-up { background-position: -408px -24px; } +.icon-qrcode { background-position: -432px -24px; } +.icon-barcode { background-position: -456px -24px; } + +.icon-tag { background-position: 0 -48px; } +.icon-tags { background-position: -25px -48px; } // 1px off +.icon-book { background-position: -48px -48px; } +.icon-bookmark { background-position: -72px -48px; } +.icon-print { background-position: -96px -48px; } +.icon-camera { background-position: -120px -48px; } +.icon-font { background-position: -144px -48px; } +.icon-bold { background-position: -167px -48px; } // 1px off +.icon-italic { background-position: -192px -48px; } +.icon-text-height { background-position: -216px -48px; } +.icon-text-width { background-position: -240px -48px; } +.icon-align-left { background-position: -264px -48px; } +.icon-align-center { background-position: -288px -48px; } +.icon-align-right { background-position: -312px -48px; } +.icon-align-justify { background-position: -336px -48px; } +.icon-list { background-position: -360px -48px; } +.icon-indent-left { background-position: -384px -48px; } +.icon-indent-right { background-position: -408px -48px; } +.icon-facetime-video { background-position: -432px -48px; } +.icon-picture { background-position: -456px -48px; } + +.icon-pencil { background-position: 0 -72px; } +.icon-map-marker { background-position: -24px -72px; } +.icon-adjust { background-position: -48px -72px; } +.icon-tint { background-position: -72px -72px; } +.icon-edit { background-position: -96px -72px; } +.icon-share { background-position: -120px -72px; } +.icon-check { background-position: -144px -72px; } +.icon-move { background-position: -168px -72px; } +.icon-step-backward { background-position: -192px -72px; } +.icon-fast-backward { background-position: -216px -72px; } +.icon-backward { background-position: -240px -72px; } +.icon-play { background-position: -264px -72px; } +.icon-pause { background-position: -288px -72px; } +.icon-stop { background-position: -312px -72px; } +.icon-forward { background-position: -336px -72px; } +.icon-fast-forward { background-position: -360px -72px; } +.icon-step-forward { background-position: -384px -72px; } +.icon-eject { background-position: -408px -72px; } +.icon-chevron-left { background-position: -432px -72px; } +.icon-chevron-right { background-position: -456px -72px; } + +.icon-plus-sign { background-position: 0 -96px; } +.icon-minus-sign { background-position: -24px -96px; } +.icon-remove-sign { background-position: -48px -96px; } +.icon-ok-sign { background-position: -72px -96px; } +.icon-question-sign { background-position: -96px -96px; } +.icon-info-sign { background-position: -120px -96px; } +.icon-screenshot { background-position: -144px -96px; } +.icon-remove-circle { background-position: -168px -96px; } +.icon-ok-circle { background-position: -192px -96px; } +.icon-ban-circle { background-position: -216px -96px; } +.icon-arrow-left { background-position: -240px -96px; } +.icon-arrow-right { background-position: -264px -96px; } +.icon-arrow-up { background-position: -289px -96px; } // 1px off +.icon-arrow-down { background-position: -312px -96px; } +.icon-share-alt { background-position: -336px -96px; } +.icon-resize-full { background-position: -360px -96px; } +.icon-resize-small { background-position: -384px -96px; } +.icon-plus { background-position: -408px -96px; } +.icon-minus { background-position: -433px -96px; } +.icon-asterisk { background-position: -456px -96px; } + +.icon-exclamation-sign { background-position: 0 -120px; } +.icon-gift { background-position: -24px -120px; } +.icon-leaf { background-position: -48px -120px; } +.icon-fire { background-position: -72px -120px; } +.icon-eye-open { background-position: -96px -120px; } +.icon-eye-close { background-position: -120px -120px; } +.icon-warning-sign { background-position: -144px -120px; } +.icon-plane { background-position: -168px -120px; } +.icon-calendar { background-position: -192px -120px; } +.icon-random { background-position: -216px -120px; } +.icon-comment { background-position: -240px -120px; } +.icon-magnet { background-position: -264px -120px; } +.icon-chevron-up { background-position: -288px -120px; } +.icon-chevron-down { background-position: -313px -119px; } // 1px, 1px off +.icon-retweet { background-position: -336px -120px; } +.icon-shopping-cart { background-position: -360px -120px; } +.icon-folder-close { background-position: -384px -120px; } +.icon-folder-open { background-position: -408px -120px; } +.icon-resize-vertical { background-position: -432px -119px; } // 1px, 1px off +.icon-resize-horizontal { background-position: -456px -118px; } // 1px, 2px off + +.icon-hdd { background-position: 0 -144px; } +.icon-bullhorn { background-position: -24px -144px; } +.icon-bell { background-position: -48px -144px; } +.icon-certificate { background-position: -72px -144px; } +.icon-thumbs-up { background-position: -96px -144px; } +.icon-thumbs-down { background-position: -120px -144px; } +.icon-hand-right { background-position: -144px -144px; } +.icon-hand-left { background-position: -168px -144px; } +.icon-hand-up { background-position: -192px -144px; } +.icon-hand-down { background-position: -216px -144px; } +.icon-circle-arrow-right { background-position: -240px -144px; } +.icon-circle-arrow-left { background-position: -264px -144px; } +.icon-circle-arrow-up { background-position: -288px -144px; } +.icon-circle-arrow-down { background-position: -312px -144px; } +.icon-globe { background-position: -336px -144px; } +.icon-wrench { background-position: -360px -144px; } +.icon-tasks { background-position: -384px -144px; } +.icon-filter { background-position: -408px -144px; } +.icon-briefcase { background-position: -432px -144px; } +.icon-fullscreen { background-position: -456px -144px; } + + + + + + + + + + + + diff --git a/symposion_project/static/symposion/less/bootstrap/tables.less b/symposion_project/static/symposion/less/bootstrap/tables.less new file mode 100644 index 00000000..b4f6027b --- /dev/null +++ b/symposion_project/static/symposion/less/bootstrap/tables.less @@ -0,0 +1,176 @@ +// +// Tables.less +// Tables for, you guessed it, tabular data +// ---------------------------------------- + + +// BASE TABLES +// ----------------- + +table { + max-width: 100%; + background-color: @tableBackground; + border-collapse: collapse; + border-spacing: 0; +} + +// BASELINE STYLES +// --------------- + +.table { + width: 100%; + margin-bottom: @baseLineHeight; + // Cells + th, + td { + padding: 8px; + line-height: @baseLineHeight; + text-align: left; + vertical-align: top; + border-top: 1px solid @tableBorder; + } + th { + font-weight: bold; + } + // Bottom align for column headings + thead th { + vertical-align: bottom; + } + // Remove top border from thead by default + caption + thead tr:first-child th, + caption + thead tr:first-child td, + colgroup + thead tr:first-child th, + colgroup + thead tr:first-child td, + thead:first-child tr:first-child th, + thead:first-child tr:first-child td { + border-top: 0; + } + // Account for multiple tbody instances + tbody + tbody { + border-top: 2px solid @tableBorder; + } +} + + + +// CONDENSED TABLE W/ HALF PADDING +// ------------------------------- + +.table-condensed { + th, + td { + padding: 4px 5px; + } +} + + +// BORDERED VERSION +// ---------------- + +.table-bordered { + border: 1px solid @tableBorder; + border-collapse: separate; // Done so we can round those corners! + *border-collapse: collapsed; // IE7 can't round corners anyway + border-left: 0; + .border-radius(4px); + th, + td { + border-left: 1px solid @tableBorder; + } + // Prevent a double border + caption + thead tr:first-child th, + caption + tbody tr:first-child th, + caption + tbody tr:first-child td, + colgroup + thead tr:first-child th, + colgroup + tbody tr:first-child th, + colgroup + tbody tr:first-child td, + thead:first-child tr:first-child th, + tbody:first-child tr:first-child th, + tbody:first-child tr:first-child td { + border-top: 0; + } + // For first th or td in the first row in the first thead or tbody + thead:first-child tr:first-child th:first-child, + tbody:first-child tr:first-child td:first-child { + -webkit-border-top-left-radius: 4px; + border-top-left-radius: 4px; + -moz-border-radius-topleft: 4px; + } + thead:first-child tr:first-child th:last-child, + tbody:first-child tr:first-child td:last-child { + -webkit-border-top-right-radius: 4px; + border-top-right-radius: 4px; + -moz-border-radius-topright: 4px; + } + // For first th or td in the first row in the first thead or tbody + thead:last-child tr:last-child th:first-child, + tbody:last-child tr:last-child td:first-child { + .border-radius(0 0 0 4px); + -webkit-border-bottom-left-radius: 4px; + border-bottom-left-radius: 4px; + -moz-border-radius-bottomleft: 4px; + } + thead:last-child tr:last-child th:last-child, + tbody:last-child tr:last-child td:last-child { + -webkit-border-bottom-right-radius: 4px; + border-bottom-right-radius: 4px; + -moz-border-radius-bottomright: 4px; + } +} + + +// ZEBRA-STRIPING +// -------------- + +// Default zebra-stripe styles (alternating gray and transparent backgrounds) +.table-striped { + tbody { + tr:nth-child(odd) td, + tr:nth-child(odd) th { + background-color: @tableBackgroundAccent; + } + } +} + + +// HOVER EFFECT +// ------------ +// Placed here since it has to come after the potential zebra striping +.table { + tbody tr:hover td, + tbody tr:hover th { + background-color: @tableBackgroundHover; + } +} + + +// TABLE CELL SIZING +// ----------------- + +// Change the columns +table { + .span1 { .tableColumns(1); } + .span2 { .tableColumns(2); } + .span3 { .tableColumns(3); } + .span4 { .tableColumns(4); } + .span5 { .tableColumns(5); } + .span6 { .tableColumns(6); } + .span7 { .tableColumns(7); } + .span8 { .tableColumns(8); } + .span9 { .tableColumns(9); } + .span10 { .tableColumns(10); } + .span11 { .tableColumns(11); } + .span12 { .tableColumns(12); } + .span13 { .tableColumns(13); } + .span14 { .tableColumns(14); } + .span15 { .tableColumns(15); } + .span16 { .tableColumns(16); } + .span17 { .tableColumns(17); } + .span18 { .tableColumns(18); } + .span19 { .tableColumns(19); } + .span20 { .tableColumns(20); } + .span21 { .tableColumns(21); } + .span22 { .tableColumns(22); } + .span23 { .tableColumns(23); } + .span24 { .tableColumns(24); } +} diff --git a/symposion_project/static/symposion/less/bootstrap/tests/css-tests.css b/symposion_project/static/symposion/less/bootstrap/tests/css-tests.css new file mode 100644 index 00000000..e0870be4 --- /dev/null +++ b/symposion_project/static/symposion/less/bootstrap/tests/css-tests.css @@ -0,0 +1,52 @@ +/*! + * Bootstrap CSS Tests + */ + + +/* Remove background image */ +body { + background-image: none; +} + +/* Space out subhead */ +.subhead { + margin-bottom: 36px; +} +h4 { + margin-bottom: 5px; +} + + +/* colgroup tests */ +.col1 { + background-color: rgba(255,0,0,.1); +} +.col2 { + background-color: rgba(0,255,0,.1); +} +.col3 { + background-color: rgba(0,0,255,.1); +} + + +/* Fluid row inputs */ +#rowInputs .row > [class*=span], +#fluidRowInputs .row-fluid > [class*=span] { + background-color: rgba(255,0,0,.1); +} + + +/* Fluid grid */ +.fluid-grid { + margin-bottom: 45px; +} +.fluid-grid .row { + height: 40px; + padding-top: 10px; + margin-top: 10px; + color: #ddd; + text-align: center; +} +.fluid-grid .span1 { + background-color: #999; +} diff --git a/symposion_project/static/symposion/less/bootstrap/tests/css-tests.html b/symposion_project/static/symposion/less/bootstrap/tests/css-tests.html new file mode 100644 index 00000000..b7177e5c --- /dev/null +++ b/symposion_project/static/symposion/less/bootstrap/tests/css-tests.html @@ -0,0 +1,917 @@ + + + + + CSS Tests · Twitter Bootstrap + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + +
    +

    CSS Tests

    +

    One stop shop for quick debugging and edge-case tests of CSS.

    +
    + + + + + + + + +
    +
    +
    12 +
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    +
    +
    +
    +
    11 +
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    +
    +
    1 +
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    +
    +
    +
    +
    10 +
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    +
    +
    2 +
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    +
    +
    +
    +
    9 +
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    +
    +
    3 +
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    +
    +
    +
    +
    8 +
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    +
    +
    4 +
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    +
    +
    +
    +
    7 +
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    +
    +
    5 +
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    +
    +
    +
    +
    6 +
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    +
    +
    6 +
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    +
    +
    +
    + + + + + + + +
    +
    +

    Bordered without thead

    + + + + + + + + + + + + + + + + + + +
    123
    123
    123
    +

    Bordered without thead, with caption

    + + + + + + + + + + + + + + + + + + + +
    Table caption
    123
    123
    123
    +

    Bordered without thead, with colgroup

    + + + + + + + + + + + + + + + + + + + + + + + +
    123
    123
    123
    +

    Bordered with thead, with colgroup

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    123
    123
    123
    123
    +
    +
    +

    Bordered with thead and caption

    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Table caption
    123
    123
    123
    123
    +

    Bordered with rowspan and colspan

    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    123
    1 and 23
    123
    13
    2 and 3
    +
    +
    + + + + + + + +
    +
    +

    Prepend and append on inputs

    +
    +
    +
    + @ +
    +
    +
    +
    + @ +
    +
    +
    +
    + $.00 +
    +
    +
    +
    +
    +

    Prepend and append with uneditable

    +
    +
    + $Some value here +
    +
    + Some value here.00 +
    +
    +
    +
    + +

    Fluid prepended and appended inputs

    +
    +
    +
    +
    +
    + @ +
    +
    +
    +
    + @ +
    +
    +
    +
    + $.00 +
    +
    +
    +
    +
    + +

    Fixed row with inputs

    +

    Inputs should not extend past the light red background, set on their parent, a .span* column.

    + +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    + +

    Fluid row with inputs

    +

    Inputs should not extend past the light red background, set on their parent, a .span* column.

    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    + + + + + + +

    Dropdown link with hash URL

    + + +

    Dropdown link with custom URL and data-target

    + + +

    Dropdown on a button

    + + +
    + + + + + + +

    Default thumbnails (no grid sizing)

    +
      +
    • + +
    • +
    • + +
    • +
    • + +
    • +
    • + +
    • +
    + + + +

    Standard grid sizing

    + + +

    Fluid thumbnails

    +
    +
    + +
    +
    + + + + + + + +
    + +
    +
    +

    I'm in Section 1.

    + +
    + +
    +
    +

    I'm in Section 1.1.

    +
    +
    +

    I'm in Section 1.2.

    +
    +
    +

    I'm in Section 1.3.

    +
    +
    +
    +
    +
    +

    Howdy, I'm in Section 2.

    +
    +
    +

    What up girl, this is Section 3.

    +
    +
    +
    + + + + + + + +
    +
    +

    Inline label

    +

    Cras justo odio, dapibus ac facilisis in, egestas eget quam. Maecenas sed diam Label name eget risus varius blandit sit amet non magna. Fusce .class-name dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus.

    +
    +
    +
    + + Hey! Read this. +
    +
    +
    + +
    +
    + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/symposion_project/static/symposion/less/bootstrap/tests/forms.html b/symposion_project/static/symposion/less/bootstrap/tests/forms.html new file mode 100644 index 00000000..a63d728a --- /dev/null +++ b/symposion_project/static/symposion/less/bootstrap/tests/forms.html @@ -0,0 +1,179 @@ + + + + + Bootstrap, from Twitter + + + + + + + + + + + + + + + + + + + + + + +
    + + + +
    +
    + + + + +
    + + + + +
    + + + + +
    + + + + +
    + + + + +
    + + + + +
    + + + + +
    + + + + +
    + + + + +
    +
    + + + + +
    + + + + +
    + + + + +
    + + + + +
    + + + + +
    + + + + +
    + + + + +
    + + + + +
    + + + + +
    +
    + + + + +
    + + + + +
    + + + + +
    + + + + +
    + + + + +
    + + + + +
    + + + + +
    +
    + +
    + + + diff --git a/symposion_project/static/symposion/less/bootstrap/tests/navbar.html b/symposion_project/static/symposion/less/bootstrap/tests/navbar.html new file mode 100644 index 00000000..17754bf6 --- /dev/null +++ b/symposion_project/static/symposion/less/bootstrap/tests/navbar.html @@ -0,0 +1,108 @@ + + + + + Bootstrap, from Twitter + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +

    Navbar example

    +

    This example is a quick exercise to illustrate how the default, static navbar and fixed to top navbar work. It includes the responsive CSS and HTML, so it also adapts to your viewport and device.

    +

    + View navbar docs » +

    +
    + +
    + + + + + + + + + + + + + + + + + + + diff --git a/symposion_project/static/symposion/less/bootstrap/thumbnails.less b/symposion_project/static/symposion/less/bootstrap/thumbnails.less new file mode 100644 index 00000000..aa69f8e3 --- /dev/null +++ b/symposion_project/static/symposion/less/bootstrap/thumbnails.less @@ -0,0 +1,47 @@ +// THUMBNAILS +// ---------- +// Note: `.thumbnails` and `.thumbnails > li` are overriden in responsive files + +// Make wrapper ul behave like the grid +.thumbnails { + margin-left: -@gridGutterWidth; + list-style: none; + .clearfix(); +} +// Fluid rows have no left margin +.row-fluid .thumbnails { + margin-left: 0; +} + +// Float li to make thumbnails appear in a row +.thumbnails > li { + float: left; // Explicity set the float since we don't require .span* classes + margin-bottom: @baseLineHeight; + margin-left: @gridGutterWidth; +} + +// The actual thumbnail (can be `a` or `div`) +.thumbnail { + display: block; + padding: 4px; + line-height: 1; + border: 1px solid #ddd; + .border-radius(4px); + .box-shadow(0 1px 1px rgba(0,0,0,.075)); +} +// Add a hover state for linked versions only +a.thumbnail:hover { + border-color: @linkColor; + .box-shadow(0 1px 4px rgba(0,105,214,.25)); +} + +// Images and captions +.thumbnail > img { + display: block; + max-width: 100%; + margin-left: auto; + margin-right: auto; +} +.thumbnail .caption { + padding: 9px; +} diff --git a/symposion_project/static/symposion/less/bootstrap/tooltip.less b/symposion_project/static/symposion/less/bootstrap/tooltip.less new file mode 100644 index 00000000..5111a193 --- /dev/null +++ b/symposion_project/static/symposion/less/bootstrap/tooltip.less @@ -0,0 +1,35 @@ +// TOOLTIP +// ------= + +.tooltip { + position: absolute; + z-index: @zindexTooltip; + display: block; + visibility: visible; + padding: 5px; + font-size: 11px; + .opacity(0); + &.in { .opacity(80); } + &.top { margin-top: -2px; } + &.right { margin-left: 2px; } + &.bottom { margin-top: 2px; } + &.left { margin-left: -2px; } + &.top .tooltip-arrow { #popoverArrow > .top(); } + &.left .tooltip-arrow { #popoverArrow > .left(); } + &.bottom .tooltip-arrow { #popoverArrow > .bottom(); } + &.right .tooltip-arrow { #popoverArrow > .right(); } +} +.tooltip-inner { + max-width: 200px; + padding: 3px 8px; + color: @white; + text-align: center; + text-decoration: none; + background-color: @black; + .border-radius(4px); +} +.tooltip-arrow { + position: absolute; + width: 0; + height: 0; +} diff --git a/symposion_project/static/symposion/less/bootstrap/type.less b/symposion_project/static/symposion/less/bootstrap/type.less new file mode 100644 index 00000000..bdf8e605 --- /dev/null +++ b/symposion_project/static/symposion/less/bootstrap/type.less @@ -0,0 +1,232 @@ +// Typography.less +// Headings, body text, lists, code, and more for a versatile and durable typography system +// ---------------------------------------------------------------------------------------- + + +// BODY TEXT +// --------- + +p { + margin: 0 0 @baseLineHeight / 2; + small { + font-size: @baseFontSize - 2; + color: @grayLight; + } +} +.lead { + margin-bottom: @baseLineHeight; + font-size: 20px; + font-weight: 200; + line-height: @baseLineHeight * 1.5; +} + +// HEADINGS +// -------- + +h1, h2, h3, h4, h5, h6 { + margin: 0; + font-family: @headingsFontFamily; + font-weight: @headingsFontWeight; + color: @headingsColor; + text-rendering: optimizelegibility; // Fix the character spacing for headings + small { + font-weight: normal; + color: @grayLight; + } +} +h1 { + font-size: 30px; + line-height: @baseLineHeight * 2; + small { + font-size: 18px; + } +} +h2 { + font-size: 24px; + line-height: @baseLineHeight * 2; + small { + font-size: 18px; + } +} +h3 { + font-size: 18px; + line-height: @baseLineHeight * 1.5; + small { + font-size: 14px; + } +} +h4, h5, h6 { + line-height: @baseLineHeight; +} +h4 { + font-size: 14px; + small { + font-size: 12px; + } +} +h5 { + font-size: 12px; +} +h6 { + font-size: 11px; + color: @grayLight; + text-transform: uppercase; +} + +// Page header +.page-header { + padding-bottom: @baseLineHeight - 1; + margin: @baseLineHeight 0; + border-bottom: 1px solid @grayLighter; +} +.page-header h1 { + line-height: 1; +} + + + +// LISTS +// ----- + +// Unordered and Ordered lists +ul, ol { + padding: 0; + margin: 0 0 @baseLineHeight / 2 25px; +} +ul ul, +ul ol, +ol ol, +ol ul { + margin-bottom: 0; +} +ul { + list-style: disc; +} +ol { + list-style: decimal; +} +li { + line-height: @baseLineHeight; +} +ul.unstyled, +ol.unstyled { + margin-left: 0; + list-style: none; +} + +// Description Lists +dl { + margin-bottom: @baseLineHeight; +} +dt, +dd { + line-height: @baseLineHeight; +} +dt { + font-weight: bold; + line-height: @baseLineHeight - 1; // fix jank Helvetica Neue font bug +} +dd { + margin-left: @baseLineHeight / 2; +} +// Horizontal layout (like forms) +.dl-horizontal { + dt { + float: left; + width: 120px; + clear: left; + text-align: right; + .text-overflow(); + } + dd { + margin-left: 130px; + } +} + +// MISC +// ---- + +// Horizontal rules +hr { + margin: @baseLineHeight 0; + border: 0; + border-top: 1px solid @hrBorder; + border-bottom: 1px solid @white; +} + +// Emphasis +strong { + font-weight: bold; +} +em { + font-style: italic; +} +.muted { + color: @grayLight; +} + +// Abbreviations and acronyms +abbr[title] { + cursor: help; + border-bottom: 1px dotted @grayLight; +} +abbr.initialism { + font-size: 90%; + text-transform: uppercase; +} + +// Blockquotes +blockquote { + padding: 0 0 0 15px; + margin: 0 0 @baseLineHeight; + border-left: 5px solid @grayLighter; + p { + margin-bottom: 0; + #font > .shorthand(16px,300,@baseLineHeight * 1.25); + } + small { + display: block; + line-height: @baseLineHeight; + color: @grayLight; + &:before { + content: '\2014 \00A0'; + } + } + + // Float right with text-align: right + &.pull-right { + float: right; + padding-right: 15px; + padding-left: 0; + border-right: 5px solid @grayLighter; + border-left: 0; + p, + small { + text-align: right; + } + } +} + +// Quotes +q:before, +q:after, +blockquote:before, +blockquote:after { + content: ""; +} + +// Addresses +address { + display: block; + margin-bottom: @baseLineHeight; + font-style: normal; + line-height: @baseLineHeight; +} + +// Misc +small { + font-size: 100%; +} +cite { + font-style: normal; +} diff --git a/symposion_project/static/symposion/less/bootstrap/utilities.less b/symposion_project/static/symposion/less/bootstrap/utilities.less new file mode 100644 index 00000000..d60d2203 --- /dev/null +++ b/symposion_project/static/symposion/less/bootstrap/utilities.less @@ -0,0 +1,23 @@ +// UTILITY CLASSES +// --------------- + +// Quick floats +.pull-right { + float: right; +} +.pull-left { + float: left; +} + +// Toggling content +.hide { + display: none; +} +.show { + display: block; +} + +// Visibility +.invisible { + visibility: hidden; +} diff --git a/symposion_project/static/symposion/less/bootstrap/variables.less b/symposion_project/static/symposion/less/bootstrap/variables.less new file mode 100644 index 00000000..b931d3d2 --- /dev/null +++ b/symposion_project/static/symposion/less/bootstrap/variables.less @@ -0,0 +1,206 @@ +// Variables.less +// Variables to customize the look and feel of Bootstrap +// ----------------------------------------------------- + + + +// GLOBAL VALUES +// -------------------------------------------------- + + +// Grays +// ------------------------- +@black: #000; +@grayDarker: #222; +@grayDark: #333; +@gray: #555; +@grayLight: #999; +@grayLighter: #eee; +@white: #fff; + + +// Accent colors +// ------------------------- +@blue: #049cdb; +@blueDark: #0064cd; +@green: #46a546; +@red: #9d261d; +@yellow: #ffc40d; +@orange: #f89406; +@pink: #c3325f; +@purple: #7a43b6; + + +// Scaffolding +// ------------------------- +@bodyBackground: @white; +@textColor: @grayDark; + + +// Links +// ------------------------- +@linkColor: #08c; +@linkColorHover: darken(@linkColor, 15%); + + +// Typography +// ------------------------- +@sansFontFamily: "Helvetica Neue", Helvetica, Arial, sans-serif; +@serifFontFamily: Georgia, "Times New Roman", Times, serif; +@monoFontFamily: Menlo, Monaco, Consolas, "Courier New", monospace; + +@baseFontSize: 13px; +@baseFontFamily: @sansFontFamily; +@baseLineHeight: 18px; +@altFontFamily: @serifFontFamily; + +@headingsFontFamily: inherit; // empty to use BS default, @baseFontFamily +@headingsFontWeight: bold; // instead of browser default, bold +@headingsColor: inherit; // empty to use BS default, @textColor + + +// Tables +// ------------------------- +@tableBackground: transparent; // overall background-color +@tableBackgroundAccent: #f9f9f9; // for striping +@tableBackgroundHover: #f5f5f5; // for hover +@tableBorder: #ddd; // table and cell border + + +// Buttons +// ------------------------- +@btnBackground: @white; +@btnBackgroundHighlight: darken(@white, 10%); +@btnBorder: #ccc; + +@btnPrimaryBackground: @linkColor; +@btnPrimaryBackgroundHighlight: spin(@btnPrimaryBackground, 15%); + +@btnInfoBackground: #5bc0de; +@btnInfoBackgroundHighlight: #2f96b4; + +@btnSuccessBackground: #62c462; +@btnSuccessBackgroundHighlight: #51a351; + +@btnWarningBackground: lighten(@orange, 15%); +@btnWarningBackgroundHighlight: @orange; + +@btnDangerBackground: #ee5f5b; +@btnDangerBackgroundHighlight: #bd362f; + +@btnInverseBackground: @gray; +@btnInverseBackgroundHighlight: @grayDarker; + + +// Forms +// ------------------------- +@inputBackground: @white; +@inputBorder: #ccc; +@inputBorderRadius: 3px; +@inputDisabledBackground: @grayLighter; +@formActionsBackground: #f5f5f5; + +// Dropdowns +// ------------------------- +@dropdownBackground: @white; +@dropdownBorder: rgba(0,0,0,.2); +@dropdownLinkColor: @grayDark; +@dropdownLinkColorHover: @white; +@dropdownLinkBackgroundHover: @linkColor; +@dropdownDividerTop: #e5e5e5; +@dropdownDividerBottom: @white; + + + +// COMPONENT VARIABLES +// -------------------------------------------------- + +// Z-index master list +// ------------------------- +// Used for a bird's eye view of components dependent on the z-axis +// Try to avoid customizing these :) +@zindexDropdown: 1000; +@zindexPopover: 1010; +@zindexTooltip: 1020; +@zindexFixedNavbar: 1030; +@zindexModalBackdrop: 1040; +@zindexModal: 1050; + + +// Sprite icons path +// ------------------------- +@iconSpritePath: "../img/glyphicons-halflings.png"; +@iconWhiteSpritePath: "../img/glyphicons-halflings-white.png"; + + +// Input placeholder text color +// ------------------------- +@placeholderText: @grayLight; + + +// Hr border color +// ------------------------- +@hrBorder: @grayLighter; + + +// Navbar +// ------------------------- +@navbarHeight: 40px; +@navbarBackground: @grayDarker; +@navbarBackgroundHighlight: @grayDark; + +@navbarText: @grayLight; +@navbarLinkColor: @grayLight; +@navbarLinkColorHover: @white; +@navbarLinkColorActive: @navbarLinkColorHover; +@navbarLinkBackgroundHover: transparent; +@navbarLinkBackgroundActive: @navbarBackground; + +@navbarSearchBackground: lighten(@navbarBackground, 25%); +@navbarSearchBackgroundFocus: @white; +@navbarSearchBorder: darken(@navbarSearchBackground, 30%); +@navbarSearchPlaceholderColor: #ccc; +@navbarBrandColor: @navbarLinkColor; + + +// Hero unit +// ------------------------- +@heroUnitBackground: @grayLighter; +@heroUnitHeadingColor: inherit; +@heroUnitLeadColor: inherit; + + +// Form states and alerts +// ------------------------- +@warningText: #c09853; +@warningBackground: #fcf8e3; +@warningBorder: darken(spin(@warningBackground, -10), 3%); + +@errorText: #b94a48; +@errorBackground: #f2dede; +@errorBorder: darken(spin(@errorBackground, -10), 3%); + +@successText: #468847; +@successBackground: #dff0d8; +@successBorder: darken(spin(@successBackground, -10), 5%); + +@infoText: #3a87ad; +@infoBackground: #d9edf7; +@infoBorder: darken(spin(@infoBackground, -10), 7%); + + + +// GRID +// -------------------------------------------------- + +// Default 940px grid +// ------------------------- +@gridColumns: 12; +@gridColumnWidth: 60px; +@gridGutterWidth: 20px; +@gridRowWidth: (@gridColumns * @gridColumnWidth) + (@gridGutterWidth * (@gridColumns - 1)); + +// Fluid grid +// ------------------------- +@fluidGridColumnWidth: 6.382978723%; +@fluidGridGutterWidth: 2.127659574%; diff --git a/symposion_project/static/symposion/less/bootstrap/wells.less b/symposion_project/static/symposion/less/bootstrap/wells.less new file mode 100644 index 00000000..9300061a --- /dev/null +++ b/symposion_project/static/symposion/less/bootstrap/wells.less @@ -0,0 +1,27 @@ +// WELLS +// ----- + +.well { + min-height: 20px; + padding: 19px; + margin-bottom: 20px; + background-color: #f5f5f5; + border: 1px solid #eee; + border: 1px solid rgba(0,0,0,.05); + .border-radius(4px); + .box-shadow(inset 0 1px 1px rgba(0,0,0,.05)); + blockquote { + border-color: #ddd; + border-color: rgba(0,0,0,.15); + } +} + +// Sizes +.well-large { + padding: 24px; + .border-radius(6px); +} +.well-small { + padding: 9px; + .border-radius(3px); +} diff --git a/symposion_project/static/symposion/less/bootswatch.less b/symposion_project/static/symposion/less/bootswatch.less new file mode 100644 index 00000000..4cce22fc --- /dev/null +++ b/symposion_project/static/symposion/less/bootswatch.less @@ -0,0 +1,193 @@ +// Bootswatch.less +// Swatch: Spacelab +// Version: 2.0.4 +// ----------------------------------------------------- + +// NAVBAR +// ----------------------------------------------------- + +.navbar { + .icon-bar { + color: @linkColor; + } + //border-bottom: 1px solid #CACACA; + + .navbar-inner { + //.box-shadow(0 1px 0 rgba(255,255,255,0.4)~", "0 0 10px rgba(0,0,0,0.1)); + } + + .brand { + font-weight: bold; + + &:hover { + color: @linkColor; + } + } + + .navbar-text { + line-height: 38px; + padding: 0 10px; + } + + .nav > li > a { + font-weight: bold; + text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); + } + + .nav .active > a { + background-color: transparent; + color: @textColor; + + &:hover { + background-color: transparent; + color: @linkColor; + } + } + + .nav > li > a:hover, + .nav li.open.dropdown .dropdown-toggle { + color: @linkColor; + } + + .nav .dropdown-toggle .caret { + border-top-color: @textColor; + opacity: 1; + } + + .navbar-search .search-query, + .navbar-search .search-query:hover { + border: none; + color: @grayLight; + .placeholder(@grayLight); + .box-shadow(inset 0 1px 2px rgba(0, 0, 0, 0.5)); + + &:focus, + &.focused { + .box-shadow(inset 0 1px 2px rgba(0, 0, 0, 0.5)); + color: @textColor; + } + } + + .nav-collapse.in .navbar-search { + border-top: none; + border-bottom: none; + } + + .nav-collapse.in > .nav li > a { + + color: @textColor; + + .caret { + border-top-color: @grayLight; + } + } + + .nav-collapse.in > .nav li > a:hover { + text-shadow: none; + color: @linkColor; + background-color: transparent; + + .caret { + border-top-color: @white; + } + } +} + +.nav li.open.dropdown .caret, +.nav .dropdown-toggle:hover .caret { + border-top-color: @linkColor; +} + +div.subnav { + + .nav > li > a { + font-weight: bold; + color: @textColor; + + &:hover { + color: @linkColor; + } + } + + .nav > li.active > a { + color: @textColor; + + &:hover { + color: @grayDark; + } + } + + .nav > li > .dropdown-toggle { + background-color: transparent; + } + + .nav > li.dropdown.open > .dropdown-toggle { + border-left: 1px solid whiteSmoke; + border-right: 1px solid #E5E5E5; + color: @linkColor; + } + + .nav > .open > .dropdown-toggle:hover .caret { + border-top-color: @linkColor; + } +} + +// BUTTON +// ----------------------------------------------------- + +.btn { + .buttonBackground(#F4F4F4, #ECECEC); +} + +.btn-primary { + .buttonBackground(#909090, #3F3F3F); +} + +.btn-warning { + .buttonBackground(lighten(@yellow, 15%), @yellow); +} + +.btn-danger { + .buttonBackground(lighten(#DA2D2D, 15%), #DA2D2D); +} + +.btn-success { + .buttonBackground(#8ADD6D, #60B044); +} + +.btn-info { + .buttonBackground(lighten(#4488BB, 15%), #4488BB); +} + +.btn-inverse { + .buttonBackground(lighten(@purple, 5%), @purple); +} + +// FORMS +// ----------------------------------------------------- + +.control-group.warning { + .formFieldState(#E29235, #E29235, @warningBackground); +} + +.control-group.error { + .formFieldState(#C00, #C00, @errorBackground); +} + +.control-group.success { + .formFieldState(#2BA949, #2BA949, @successBackground); +} + + +// MISC +// ----------------------------------------------------- + +.label-important { background-color: #BD2C00; } +.label-warning { background-color: #E3E84D; } +.label-success { background-color: #6CC644; } +.label-info { background-color: #4183C4; } + +.hero-unit { + .box-shadow(inset 0 1px 1px rgba(0,0,0,.05)); + border: 1px solid rgba(0,0,0,.05); +} \ No newline at end of file diff --git a/symposion_project/static/symposion/less/custom.less b/symposion_project/static/symposion/less/custom.less new file mode 100644 index 00000000..c320e71a --- /dev/null +++ b/symposion_project/static/symposion/less/custom.less @@ -0,0 +1,23 @@ +body { + padding-top: 24px; +} + +header { + position: relative; + margin-bottom: 24px; + + + h1 { + margin-bottom: 9px; + font-size: 48px; + letter-spacing: -1px; + line-height: 1; + -webkit-margin-before: 0; + -webkit-margin-after: 0; + } + + p { + font-size: 30px; + line-height: 36px; + } +} diff --git a/symposion_project/static/symposion/less/symposion.less b/symposion_project/static/symposion/less/symposion.less new file mode 100644 index 00000000..92783a58 --- /dev/null +++ b/symposion_project/static/symposion/less/symposion.less @@ -0,0 +1,92 @@ +/*! + * Bootstrap v2.0.4 + * + * Copyright 2012 Twitter, Inc + * Licensed under the Apache License v2.0 + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Designed and built with all the love in the world @twitter by @mdo and @fat. + */ + +// CSS Reset +@import "bootstrap/reset.less"; + +// Core variables and mixins +@import "variables.less"; // Modify this for custom colors, font-sizes, etc +@import "bootstrap/mixins.less"; + +// Grid system and page structure +@import "bootstrap/scaffolding.less"; +@import "bootstrap/grid.less"; +@import "bootstrap/layouts.less"; + +// Base CSS +@import "bootstrap/type.less"; +@import "bootstrap/code.less"; +@import "bootstrap/forms.less"; +@import "bootstrap/tables.less"; + +// Components: common +@import "bootstrap/sprites.less"; +@import "bootstrap/dropdowns.less"; +@import "bootstrap/wells.less"; +@import "bootstrap/component-animations.less"; +@import "bootstrap/close.less"; + +// Components: Buttons & Alerts +@import "bootstrap/buttons.less"; +@import "bootstrap/button-groups.less"; +@import "bootstrap/alerts.less"; // Note: alerts share common CSS with buttons and thus have styles in buttons.less + +// Components: Nav +@import "bootstrap/navs.less"; +@import "bootstrap/navbar.less"; +@import "bootstrap/breadcrumbs.less"; +@import "bootstrap/pagination.less"; +@import "bootstrap/pager.less"; + +// Components: Popovers +@import "bootstrap/modals.less"; +@import "bootstrap/tooltip.less"; +@import "bootstrap/popovers.less"; + +// Components: Misc +@import "bootstrap/thumbnails.less"; +@import "bootstrap/labels-badges.less"; +@import "bootstrap/progress-bars.less"; +@import "bootstrap/accordion.less"; +@import "bootstrap/carousel.less"; +@import "bootstrap/hero-unit.less"; + +// Utility classes +@import "bootstrap/utilities.less"; // Has to be last to override when necessary + +// RESPONSIVE CLASSES +// ------------------ + +@import "bootstrap/responsive-utilities.less"; + + +// MEDIA QUERIES +// ------------------ + +// Phones to portrait tablets and narrow desktops +@import "bootstrap/responsive-767px-max.less"; + +// Tablets to regular desktops +@import "bootstrap/responsive-768px-979px.less"; + +// Large desktops +@import "bootstrap/responsive-1200px-min.less"; + + +// RESPONSIVE NAVBAR +// ------------------ + +// From 979px and below, show a button to toggle navbar contents +@import "bootstrap/responsive-navbar.less"; + +// Bootswatch +@import "bootswatch.less"; + +@import "custom.less"; diff --git a/symposion_project/static/symposion/less/variables.less b/symposion_project/static/symposion/less/variables.less new file mode 100644 index 00000000..dc3859a4 --- /dev/null +++ b/symposion_project/static/symposion/less/variables.less @@ -0,0 +1,206 @@ +// Variables.less +// Variables to customize the look and feel of Bootstrap +// Swatch: Spacelab +// Version: 2.0.4 +// ----------------------------------------------------- + +// GLOBAL VALUES +// -------------------------------------------------- + + +// Grays +// ------------------------- +@black: #000; +@grayDarker: #222; +@grayDark: #333; +@gray: #555; +@grayLight: #999; +@grayLighter: #F5F5F5; +@white: #fff; + + +// Accent colors +// ------------------------- +@blue: #4183C4; +@blueDark: #405A6A; +@green: #84DE81; +@red: #E5322C; +@yellow: #F4CA00; +@orange: #FF7D00; +@pink: #F44B8C; +@purple: #405A6A; + + +// Scaffolding +// ------------------------- +@bodyBackground: @white; +@textColor: @grayDark; + + +// Links +// ------------------------- +@linkColor: #4183C4; +@linkColorHover: @linkColor; + + +// Typography +// ------------------------- +@sansFontFamily: "Helvetica Neue", Helvetica, Arial, sans-serif; +@serifFontFamily: Georgia, "Times New Roman", Times, serif; +@monoFontFamily: Menlo, Monaco, Consolas, "Courier New", monospace; + +@baseFontSize: 13px; +@baseFontFamily: @sansFontFamily; +@baseLineHeight: 18px; +@altFontFamily: @serifFontFamily; + +@headingsFontFamily: inherit; // empty to use BS default, @baseFontFamily +@headingsFontWeight: bold; // instead of browser default, bold +@headingsColor: inherit; // empty to use BS default, @textColor + + +// Tables +// ------------------------- +@tableBackground: transparent; // overall background-color +@tableBackgroundAccent: #f9f9f9; // for striping +@tableBackgroundHover: #f5f5f5; // for hover +@tableBorder: #ddd; // table and cell border + + +// Buttons +// ------------------------- +@btnBackground: @white; +@btnBackgroundHighlight: darken(@white, 10%); +@btnBorder: darken(@white, 20%); + +@btnPrimaryBackground: @linkColor; +@btnPrimaryBackgroundHighlight: spin(@btnPrimaryBackground, 15%); + +@btnInfoBackground: #5bc0de; +@btnInfoBackgroundHighlight: #2f96b4; + +@btnSuccessBackground: #62c462; +@btnSuccessBackgroundHighlight: #51a351; + +@btnWarningBackground: lighten(@orange, 15%); +@btnWarningBackgroundHighlight: @orange; + +@btnDangerBackground: #ee5f5b; +@btnDangerBackgroundHighlight: #bd362f; + +@btnInverseBackground: @gray; +@btnInverseBackgroundHighlight: @grayDarker; + + +// Forms +// ------------------------- +@inputBackground: @white; +@inputBorder: #ccc; +@inputBorderRadius: 3px; +@inputDisabledBackground: @grayLighter; +@formActionsBackground: #f5f5f5; + +// Dropdowns +// ------------------------- +@dropdownBackground: @white; +@dropdownBorder: rgba(0,0,0,.2); +@dropdownLinkColor: @grayDark; +@dropdownLinkColorHover: @white; +@dropdownLinkBackgroundHover: @linkColor; +@dropdownDividerTop: #e5e5e5; +@dropdownDividerBottom: @white; + + + +// COMPONENT VARIABLES +// -------------------------------------------------- + +// Z-index master list +// ------------------------- +// Used for a bird's eye view of components dependent on the z-axis +// Try to avoid customizing these :) +@zindexDropdown: 1000; +@zindexPopover: 1010; +@zindexTooltip: 1020; +@zindexFixedNavbar: 1030; +@zindexModalBackdrop: 1040; +@zindexModal: 1050; + + +// Sprite icons path +// ------------------------- +@iconSpritePath: "../../bootstrap/img/glyphicons-halflings.png"; +@iconWhiteSpritePath: "../../bootstrap/img/glyphicons-halflings-white.png"; + + +// Input placeholder text color +// ------------------------- +@placeholderText: @grayLight; + + +// Hr border color +// ------------------------- +@hrBorder: @grayLighter; + + +// Navbar +// ------------------------- +@navbarHeight: 40px; +@navbarBackground: #EAEAEA; +@navbarBackgroundHighlight: lighten(@navbarBackground, 15%); + +@navbarText: @grayDarker; +@navbarLinkColor: @grayDarker; +@navbarLinkColorHover: @linkColor; +@navbarLinkColorActive: @navbarLinkColorHover; +@navbarLinkBackgroundHover: transparent; +@navbarLinkBackgroundActive: @navbarBackground; + +@navbarSearchBackground: lighten(@navbarBackground, 25%); +@navbarSearchBackgroundFocus: @white; +@navbarSearchBorder: darken(@navbarSearchBackground, 30%); +@navbarSearchPlaceholderColor: #ccc; +@navbarBrandColor: @grayDark; + + +// Hero unit +// ------------------------- +@heroUnitBackground: @grayLighter; +@heroUnitHeadingColor: inherit; +@heroUnitLeadColor: inherit; + + +// Form states and alerts +// ------------------------- +@warningText: #393939; +@warningBackground: #F5F3B4; +@warningBorder: darken(spin(@warningBackground, -10), 3%); + +@errorText: @warningText; +@errorBackground: #FFE9E9; +@errorBorder: darken(spin(@errorBackground, -10), 3%); + +@successText: #333; +@successBackground: #BEDEBE; +@successBorder: darken(spin(@successBackground, -10), 5%); + +@infoText: #1B3650; +@infoBackground: #E4F0FF; +@infoBorder: darken(spin(@infoBackground, -10), 7%); + + + +// GRID +// -------------------------------------------------- + +// Default 940px grid +// ------------------------- +@gridColumns: 12; +@gridColumnWidth: 60px; +@gridGutterWidth: 20px; +@gridRowWidth: (@gridColumns * @gridColumnWidth) + (@gridGutterWidth * (@gridColumns - 1)); + +// Fluid grid +// ------------------------- +@fluidGridColumnWidth: 6.382978723%; +@fluidGridGutterWidth: 2.127659574%; diff --git a/symposion_project/templates/site_base.html b/symposion_project/templates/site_base.html index 64b95710..81e4d6aa 100644 --- a/symposion_project/templates/site_base.html +++ b/symposion_project/templates/site_base.html @@ -6,6 +6,11 @@ {% load markitup_tags %} {% load static %} +{% block style_base %} + + {% block extra_style %}{% endblock %} +{% endblock %} + {% block extra_head_base %} From 625f5b1024bf2a1affc544fd47bc691b9708df84 Mon Sep 17 00:00:00 2001 From: Luke Hatcher Date: Thu, 12 Jul 2012 23:10:10 -0400 Subject: [PATCH 078/751] update front page --- symposion_project/templates/homepage.html | 52 +++++++++++------------ 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/symposion_project/templates/homepage.html b/symposion_project/templates/homepage.html index 2365f67a..e82d969e 100644 --- a/symposion_project/templates/homepage.html +++ b/symposion_project/templates/homepage.html @@ -1,4 +1,4 @@ -{% extends "banner_base.html" %} +{% extends "site_base.html" %} {% load i18n %} @@ -6,34 +6,32 @@ {% block body_class %}home{% endblock %} -{% block banner %} -

    {% trans "Welcome to Pinax" %}

    +{% block body %} +

    Out of this world

    - {% blocktrans %} - Pinax is a Django - project intended to provide a starting point for websites. By - integrating numerous reusable Django apps to take care of the - things that many sites have in common, it lets you focus on what - makes your site different. - {% endblocktrans %} + Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod + tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, + quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo + consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse + cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non + proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

    - -

    About Account Project

    +

    Pets are welcome, too

    - {% blocktrans %} - This project takes the zero_project and adds basic account management - functionality such as sign up, log in, password change/reset and email - confirmation. It is a foundation suitable for most sites that have user - accounts. - {% endblocktrans %} + Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod + tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, + quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo + consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse + cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non + proident, sunt in culpa qui officia deserunt mollit anim id est laborum. +

    +

    Remember to bring extra socks

    +

    + Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod + tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, + quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo + consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse + cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non + proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

    - - {% if user.is_authenticated %} - {% url what_next as what_next_url %} -

    {% blocktrans %}Wondering What Next?{% endblocktrans %}

    - {% else %} - {% url account_login as login_url %} - {% url account_signup as signup_url %} -

    {% blocktrans %}You can Log In or Sign Up to try out the site.{% endblocktrans %}

    - {% endif %} {% endblock %} From 7c838a526a075b0a4d27f0c869743a751acb53df Mon Sep 17 00:00:00 2001 From: Luke Hatcher Date: Thu, 12 Jul 2012 23:10:25 -0400 Subject: [PATCH 079/751] add sidebar base --- .../templates/symposion_sidebar_base.html | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 symposion_project/templates/symposion_sidebar_base.html diff --git a/symposion_project/templates/symposion_sidebar_base.html b/symposion_project/templates/symposion_sidebar_base.html new file mode 100644 index 00000000..bdac7556 --- /dev/null +++ b/symposion_project/templates/symposion_sidebar_base.html @@ -0,0 +1,24 @@ +{% extends "theme_base.html" %} +{% load sitetree %} + +{% block body_base %} +
    + + {% include "_messages.html" %} + + {% block breadcrumbs %} + {% sitetree_breadcrumbs from "main" %} + {% endblock %} + +
    +
    + {% block body %} + {% endblock %} +
    +
    + {% block sidebar %} + {% endblock %} +
    +
    +
    +{% endblock %} \ No newline at end of file From d4d7acabf44b449244eb7ac5a13eee4ac83632aa Mon Sep 17 00:00:00 2001 From: Luke Hatcher Date: Thu, 12 Jul 2012 23:36:00 -0400 Subject: [PATCH 080/751] clickable header --- symposion_project/static/symposion/css/symposion.css | 3 ++- symposion_project/static/symposion/less/custom.less | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/symposion_project/static/symposion/css/symposion.css b/symposion_project/static/symposion/css/symposion.css index bd9b1624..cc01299b 100644 --- a/symposion_project/static/symposion/css/symposion.css +++ b/symposion_project/static/symposion/css/symposion.css @@ -764,5 +764,6 @@ div.subnav .nav>.open>.dropdown-toggle:hover .caret{border-top-color:#4183c4;} .label-info{background-color:#4183C4;} .hero-unit{-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);border:1px solid rgba(0, 0, 0, 0.05);} body{padding-top:24px;} -header{position:relative;margin-bottom:24px;z-index:999;}header h1{margin-bottom:9px;font-size:48px;letter-spacing:-1px;line-height:1;-webkit-margin-before:0;-webkit-margin-after:0;} +header{position:relative;margin-bottom:24px;}header h1{margin-bottom:9px;font-size:48px;letter-spacing:-1px;line-height:1;-webkit-margin-before:0;-webkit-margin-after:0;} header p{font-size:30px;line-height:36px;} +header a,header a:hover,header a:visited{color:#333333;text-decoration:none;} diff --git a/symposion_project/static/symposion/less/custom.less b/symposion_project/static/symposion/less/custom.less index c320e71a..f42585cd 100644 --- a/symposion_project/static/symposion/less/custom.less +++ b/symposion_project/static/symposion/less/custom.less @@ -20,4 +20,9 @@ header { font-size: 30px; line-height: 36px; } + + a, a:hover, a:visited { + color: @grayDark; + text-decoration: none; + } } From 732c8db23eaaeb4bef87957c7013cb273944e00b Mon Sep 17 00:00:00 2001 From: Luke Hatcher Date: Thu, 12 Jul 2012 23:36:52 -0400 Subject: [PATCH 081/751] use symposion sidebar base --- symposion_project/templates/site_base.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/symposion_project/templates/site_base.html b/symposion_project/templates/site_base.html index 81e4d6aa..f32d524e 100644 --- a/symposion_project/templates/site_base.html +++ b/symposion_project/templates/site_base.html @@ -1,4 +1,4 @@ -{% extends "theme_base.html" %} +{% extends "symposion_sidebar_base.html" %} {% load metron_tags %} {% load i18n %} From 907f0a5fd4aeca48e028236a95c811f72002a323 Mon Sep 17 00:00:00 2001 From: Luke Hatcher Date: Thu, 12 Jul 2012 23:37:43 -0400 Subject: [PATCH 082/751] add sidebar block --- symposion_project/templates/site_base.html | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/symposion_project/templates/site_base.html b/symposion_project/templates/site_base.html index f32d524e..f068003a 100644 --- a/symposion_project/templates/site_base.html +++ b/symposion_project/templates/site_base.html @@ -30,11 +30,20 @@ {% sitetree_breadcrumbs from "main" %} {% endblock %} - {% block page_title %} - {% endblock %} - - {% block body %} - {% endblock %} +
    +
    + {% block body %} + {% endblock %} +
    +
    +

    Sponsors

    +

    +

    +

    +

    +

    +
    +
  • {% endblock %} From 8d979bf4af471127ac4e6d82d384333cf578a286 Mon Sep 17 00:00:00 2001 From: Luke Hatcher Date: Thu, 12 Jul 2012 23:38:19 -0400 Subject: [PATCH 083/751] add new header --- symposion_project/templates/site_base.html | 39 ++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/symposion_project/templates/site_base.html b/symposion_project/templates/site_base.html index f068003a..67f671da 100644 --- a/symposion_project/templates/site_base.html +++ b/symposion_project/templates/site_base.html @@ -18,8 +18,43 @@ {% block extra_head %}{% endblock %} {% endblock %} -{% block nav %} - {% sitetree_menu from "main" include "trunk" %} +{% block topbar_base %} +
    +
    +
    + {% if request.user.is_authenticated %} +
    +
    + Dashboard + Settings + {% csrf_token %} +
    +
    + {% else %} + Log in / Sign up + {% endif %} +
    + +

    MoonCon 2019

    +

    Sea of Tranquility, July 16th–24th

    +
    + +
    {% endblock %} {% block body_base %} From c8e0d667d51f330d4521f7d7a92e6bf3785d9bb3 Mon Sep 17 00:00:00 2001 From: Luke Hatcher Date: Fri, 13 Jul 2012 00:33:50 -0400 Subject: [PATCH 084/751] update proposal templates to use site_base --- symposion_project/templates/proposals/document_create.html | 2 +- symposion_project/templates/proposals/proposal_submit.html | 2 +- symposion_project/templates/proposals/proposal_submit_kind.html | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/symposion_project/templates/proposals/document_create.html b/symposion_project/templates/proposals/document_create.html index 37ca0566..a18cd207 100644 --- a/symposion_project/templates/proposals/document_create.html +++ b/symposion_project/templates/proposals/document_create.html @@ -1,4 +1,4 @@ -{% extends "auth_base.html" %} +{% extends "site_base.html" %} {% load bootstrap_tags %} diff --git a/symposion_project/templates/proposals/proposal_submit.html b/symposion_project/templates/proposals/proposal_submit.html index 7978a08b..ffc365e9 100644 --- a/symposion_project/templates/proposals/proposal_submit.html +++ b/symposion_project/templates/proposals/proposal_submit.html @@ -1,4 +1,4 @@ -{% extends "auth_base.html" %} +{% extends "site_base.html" %} {% load boxes_tags %} {% load i18n %} diff --git a/symposion_project/templates/proposals/proposal_submit_kind.html b/symposion_project/templates/proposals/proposal_submit_kind.html index 443eb01d..4b81140d 100644 --- a/symposion_project/templates/proposals/proposal_submit_kind.html +++ b/symposion_project/templates/proposals/proposal_submit_kind.html @@ -1,4 +1,4 @@ -{% extends "auth_base.html" %} +{% extends "site_base.html" %} {% load bootstrap_tags %} {% load boxes_tags %} From e126be16e39322a5c2a9be01fa0310a46216986b Mon Sep 17 00:00:00 2001 From: Luke Hatcher Date: Fri, 13 Jul 2012 00:34:07 -0400 Subject: [PATCH 085/751] switch speaker forms to vertical form --- symposion_project/templates/speakers/speaker_create.html | 2 +- symposion_project/templates/speakers/speaker_edit.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/symposion_project/templates/speakers/speaker_create.html b/symposion_project/templates/speakers/speaker_create.html index 5762adc2..2750d29f 100644 --- a/symposion_project/templates/speakers/speaker_create.html +++ b/symposion_project/templates/speakers/speaker_create.html @@ -9,7 +9,7 @@ {% block body %} {% box "speaker-profile" %} -
    + {% csrf_token %} {% trans "Create Speaker Profile" %}
    diff --git a/symposion_project/templates/speakers/speaker_edit.html b/symposion_project/templates/speakers/speaker_edit.html index a8433bb9..53b4bd65 100644 --- a/symposion_project/templates/speakers/speaker_edit.html +++ b/symposion_project/templates/speakers/speaker_edit.html @@ -9,7 +9,7 @@ {% block body %} {% box "speaker-profile" %} - + {% csrf_token %} {% trans "Edit Speaker Profile" %}
    From a5dc78cf7162397ceae3aec9e89c1859f204bfd4 Mon Sep 17 00:00:00 2001 From: Luke Hatcher Date: Fri, 13 Jul 2012 00:34:16 -0400 Subject: [PATCH 086/751] comment out breadcrumbs for now --- symposion_project/templates/site_base.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/symposion_project/templates/site_base.html b/symposion_project/templates/site_base.html index 67f671da..187b1e26 100644 --- a/symposion_project/templates/site_base.html +++ b/symposion_project/templates/site_base.html @@ -61,9 +61,9 @@
    {% include "_messages.html" %} - {% block breadcrumbs %} - {% sitetree_breadcrumbs from "main" %} - {% endblock %} + {# {% block breadcrumbs %} #} + {# {% sitetree_breadcrumbs from "main" %} #} + {# {% endblock %} #}
    From 9ae82e45efad43af49a658a2ae5da4d8f52f7b31 Mon Sep 17 00:00:00 2001 From: Luke Hatcher Date: Fri, 13 Jul 2012 00:34:24 -0400 Subject: [PATCH 087/751] bold labels --- symposion_project/static/symposion/css/symposion.css | 1 + symposion_project/static/symposion/less/custom.less | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/symposion_project/static/symposion/css/symposion.css b/symposion_project/static/symposion/css/symposion.css index cc01299b..ea81f9d5 100644 --- a/symposion_project/static/symposion/css/symposion.css +++ b/symposion_project/static/symposion/css/symposion.css @@ -764,6 +764,7 @@ div.subnav .nav>.open>.dropdown-toggle:hover .caret{border-top-color:#4183c4;} .label-info{background-color:#4183C4;} .hero-unit{-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);border:1px solid rgba(0, 0, 0, 0.05);} body{padding-top:24px;} +label:not(.checkbox):not(.radio){font-weight:bold;} header{position:relative;margin-bottom:24px;}header h1{margin-bottom:9px;font-size:48px;letter-spacing:-1px;line-height:1;-webkit-margin-before:0;-webkit-margin-after:0;} header p{font-size:30px;line-height:36px;} header a,header a:hover,header a:visited{color:#333333;text-decoration:none;} diff --git a/symposion_project/static/symposion/less/custom.less b/symposion_project/static/symposion/less/custom.less index f42585cd..6247a002 100644 --- a/symposion_project/static/symposion/less/custom.less +++ b/symposion_project/static/symposion/less/custom.less @@ -2,6 +2,10 @@ body { padding-top: 24px; } +label:not(.checkbox):not(.radio) { + font-weight: bold; +} + header { position: relative; margin-bottom: 24px; From c05ec5da46a886454a30942714315dfb29222f9c Mon Sep 17 00:00:00 2001 From: Luke Hatcher Date: Fri, 13 Jul 2012 03:01:47 -0400 Subject: [PATCH 088/751] bump site tree version --- requirements/base.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/base.txt b/requirements/base.txt index 8ab68abd..83a325f2 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -29,4 +29,4 @@ django-taggit==0.9.3 django-reversion==1.6.1 django-markitup==1.0.0 markdown==2.1.1 -django-sitetree==0.6 +django-sitetree==0.8 From 343699bc315e36366709c4bdc74c5a1db8469667 Mon Sep 17 00:00:00 2001 From: Luke Hatcher Date: Fri, 13 Jul 2012 03:07:13 -0400 Subject: [PATCH 089/751] style edit boxes --- .../static/symposion/css/symposion.css | 5 +++ .../static/symposion/less/custom.less | 32 +++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/symposion_project/static/symposion/css/symposion.css b/symposion_project/static/symposion/css/symposion.css index ea81f9d5..04b9ad5c 100644 --- a/symposion_project/static/symposion/css/symposion.css +++ b/symposion_project/static/symposion/css/symposion.css @@ -768,3 +768,8 @@ label:not(.checkbox):not(.radio){font-weight:bold;} header{position:relative;margin-bottom:24px;}header h1{margin-bottom:9px;font-size:48px;letter-spacing:-1px;line-height:1;-webkit-margin-before:0;-webkit-margin-after:0;} header p{font-size:30px;line-height:36px;} header a,header a:hover,header a:visited{color:#333333;text-decoration:none;} +div.box-content .markItUpEditor{font-family:Menlo,Monaco,Consolas,"Courier New",monospace;padding:5px;width:600px;height:320px;clear:both;display:block;overflow:auto;} +.modal div.markItUp{width:500px;} +.modal textarea.markItUpEditor{width:500px;height:250px;} +.content-box.editable{border:1px dashed #bbb;padding:5px;min-height:30px;} +.edit-toggle{position:relative;float:left;left:-20px;} diff --git a/symposion_project/static/symposion/less/custom.less b/symposion_project/static/symposion/less/custom.less index 6247a002..de809330 100644 --- a/symposion_project/static/symposion/less/custom.less +++ b/symposion_project/static/symposion/less/custom.less @@ -30,3 +30,35 @@ header { text-decoration: none; } } + +// Markitup +div.box-content .markItUpEditor { + font-family: @monoFontFamily; + padding:5px; + width:600px; + height:320px; + clear:both; display:block; + overflow:auto; +} + + +// Markitup Modal +.modal div.markItUp { + width: 500px; +} +.modal textarea.markItUpEditor { + width: 500px; + height: 250px; +} + +// Boxes +.content-box.editable { + border: 1px dashed #bbb; + padding: 5px; + min-height: 30px; +} +.edit-toggle { + position: relative; + float: left; + left: -20px; +} From bce0ced774918333fa6c2a081e8d49a901f68d2b Mon Sep 17 00:00:00 2001 From: Luke Hatcher Date: Fri, 13 Jul 2012 03:07:35 -0400 Subject: [PATCH 090/751] use site name in template --- symposion_project/templates/site_base.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/symposion_project/templates/site_base.html b/symposion_project/templates/site_base.html index 187b1e26..0cd0359d 100644 --- a/symposion_project/templates/site_base.html +++ b/symposion_project/templates/site_base.html @@ -35,8 +35,8 @@ {% endif %}
    -

    MoonCon 2019

    -

    Sea of Tranquility, July 16th–24th

    +

    {{ SITE_NAME }}

    +

    Some amazing location, February 30th–31st

    + + +
    + +
    +

    Designed for everyone, everywhere.

    + +
    +
    + +

    Built for and by nerds

    +

    Like you, we love building awesome products on the web. We love it so much, we decided to help people just like us do it easier, better, and faster. Bootstrap is built for you.

    +
    +
    + +

    For all skill levels

    +

    Bootstrap is designed to help people of all skill levels—designer or developer, huge nerd or early beginner. Use it as a complete kit or use to start something more complex.

    +
    +
    + +

    Cross-everything

    +

    Originally built with only modern browsers in mind, Bootstrap has evolved to include support for all major browsers (even IE7!) and, with Bootstrap 2, tablets and smartphones, too.

    +
    +
    +
    +
    + +

    12-column grid

    +

    Grid systems aren't everything, but having a durable and flexible one at the core of your work can make development much simpler. Use our built-in grid classes or roll your own.

    +
    +
    + +

    Responsive design

    +

    With Bootstrap 2, we've gone fully responsive. Our components are scaled according to a range of resolutions and devices to provide a consistent experience, no matter what.

    +
    +
    + +

    Styleguide docs

    +

    Unlike other front-end toolkits, Bootstrap was designed first and foremost as a styleguide to document not only our features, but best practices and living, coded examples.

    +
    +
    +
    +
    + +

    Growing library

    +

    Despite being only 10kb (gzipped), Bootstrap is one of the most complete front-end toolkits out there with dozens of fully functional components ready to be put to use.

    +
    +
    + +

    Custom jQuery plugins

    +

    What good is an awesome design component without easy-to-use, proper, and extensible interactions? With Bootstrap, you get custom-built jQuery plugins to bring your projects to life.

    +
    +
    + +

    Built on LESS

    +

    Where vanilla CSS falters, LESS excels. Variables, nesting, operations, and mixins in LESS makes coding CSS faster and more efficient with minimal overhead.

    +
    +
    +
    +
    + +

    HTML5

    +

    Built to support new HTML5 elements and syntax.

    +
    +
    + +

    CSS3

    +

    Progressively enhanced components for ultimate style.

    +
    +
    + +

    Open-source

    +

    Built for and maintained by the community via GitHub.

    +
    +
    + +

    Made at Twitter

    +

    Brought to you by an experienced engineer and designer.

    +
    +
    + +
    + +

    Built with Bootstrap.

    + + + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/symposion_project/static/bootstrap/docs/javascript.html b/symposion_project/static/bootstrap/docs/javascript.html new file mode 100644 index 00000000..6d06ff6d --- /dev/null +++ b/symposion_project/static/bootstrap/docs/javascript.html @@ -0,0 +1,1520 @@ + + + + + Javascript · Twitter Bootstrap + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +
    +

    Javascript for Bootstrap

    +

    Bring Bootstrap's components to life—now with 12 custom jQuery plugins. +

    +
    + + + +
    + +
    +
    +

    Modals

    +

    A streamlined, but flexible, take on the traditional javascript modal plugin with only the minimum required functionality and smart defaults.

    +
    +
    +

    Dropdowns

    +

    Add dropdown menus to nearly anything in Bootstrap with this simple plugin. Bootstrap features full dropdown menu support on in the navbar, tabs, and pills.

    +
    +
    +

    Scrollspy

    +

    Use scrollspy to automatically update the links in your navbar to show the current active link based on scroll position.

    +
    +
    +

    Togglable tabs

    +

    Use this plugin to make tabs and pills more useful by allowing them to toggle through tabbable panes of local content.

    +
    +
    +
    +
    +

    Tooltips

    +

    A new take on the jQuery Tipsy plugin, Tooltips don't rely on images—they use CSS3 for animations and data-attributes for local title storage.

    +
    +
    +

    Popovers *

    +

    Add small overlays of content, like those on the iPad, to any element for housing secondary information.

    +

    * Requires Tooltips to be included

    +
    +
    +

    Alert messages

    +

    The alert plugin is a tiny class for adding close functionality to alerts.

    +
    +
    +

    Buttons

    +

    Do more with buttons. Control button states or create groups of buttons for more components like toolbars.

    +
    +
    +
    +
    +

    Collapse

    +

    Get base styles and flexible support for collapsible components like accordions and navigation.

    +
    +
    +

    Carousel

    +

    Create a merry-go-round of any content you wish to provide an interactive slideshow of content.

    +
    +
    +

    Typeahead

    +

    A basic, easily extended plugin for quickly creating elegant typeaheads with any form text input.

    +
    +
    +

    Transitions *

    +

    For simple transition effects, include bootstrap-transition.js once to slide in modals or fade out alerts.

    +

    * Required for animation in plugins

    +
    +
    +
    Heads up! All javascript plugins require the latest version of jQuery.
    +
    + + + + +
    + +
    +
    +

    About modals

    +

    A streamlined, but flexible, take on the traditional javascript modal plugin with only the minimum required functionality and smart defaults.

    + Download file +
    +
    +

    Static example

    +

    Below is a statically rendered modal.

    + + +

    Live demo

    +

    Toggle a modal via javascript by clicking the button below. It will slide down and fade in from the top of the page.

    + + + Launch demo modal + +
    + +

    Using bootstrap-modal

    +

    Call the modal via javascript:

    +
    $('#myModal').modal(options)
    +

    Options

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Nametypedefaultdescription
    backdropbooleantrueIncludes a modal-backdrop element. Alternatively, specify static for a backdrop which doesn't close the modal on click.
    keyboardbooleantrueCloses the modal when escape key is pressed
    showbooleantrueShows the modal when initialized.
    +

    Markup

    +

    You can activate modals on your page easily without having to write a single line of javascript. Just set data-toggle="modal" on a controller element with a data-target="#foo" or href="#foo" which corresponds to a modal element id, and when clicked, it will launch your modal.

    +

    Also, to add options to your modal instance, just include them as additional data attributes on either the control element or the modal markup itself.

    +
    +<a class="btn" data-toggle="modal" href="#myModal" >Launch Modal</a>
    +
    + +
    +<div class="modal hide" id="myModal">
    +  <div class="modal-header">
    +    <button type="button" class="close" data-dismiss="modal">×</button>
    +    <h3>Modal header</h3>
    +  </div>
    +  <div class="modal-body">
    +    <p>One fine body…</p>
    +  </div>
    +  <div class="modal-footer">
    +    <a href="#" class="btn" data-dismiss="modal">Close</a>
    +    <a href="#" class="btn btn-primary">Save changes</a>
    +  </div>
    +</div>
    +
    +
    + Heads up! If you want your modal to animate in and out, just add a .fade class to the .modal element (refer to the demo to see this in action) and include bootstrap-transition.js. +
    +

    Methods

    +

    .modal(options)

    +

    Activates your content as a modal. Accepts an optional options object.

    +
    +$('#myModal').modal({
    +  keyboard: false
    +})
    +

    .modal('toggle')

    +

    Manually toggles a modal.

    +
    $('#myModal').modal('toggle')
    +

    .modal('show')

    +

    Manually opens a modal.

    +
    $('#myModal').modal('show')
    +

    .modal('hide')

    +

    Manually hides a modal.

    +
    $('#myModal').modal('hide')
    +

    Events

    +

    Bootstrap's modal class exposes a few events for hooking into modal functionality.

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    EventDescription
    showThis event fires immediately when the show instance method is called.
    shownThis event is fired when the modal has been made visible to the user (will wait for css transitions to complete).
    hideThis event is fired immediately when the hide instance method has been called.
    hiddenThis event is fired when the modal has finished being hidden from the user (will wait for css transitions to complete).
    + +
    +$('#myModal').on('hidden', function () {
    +  // do something…
    +})
    +
    +
    +
    + + + + + + + + + +
    + +
    +
    +

    The ScrollSpy plugin is for automatically updating nav targets based on scroll position.

    + Download file +
    +
    +

    Example navbar with scrollspy

    +

    Scroll the area below and watch the navigation update. The dropdown sub items will be highlighted as well. Try it!

    + +
    +

    @fat

    +

    + Ad leggings keytar, brunch id art party dolor labore. Pitchfork yr enim lo-fi before they sold out qui. Tumblr farm-to-table bicycle rights whatever. Anim keffiyeh carles cardigan. Velit seitan mcsweeney's photo booth 3 wolf moon irure. Cosby sweater lomo jean shorts, williamsburg hoodie minim qui you probably haven't heard of them et cardigan trust fund culpa biodiesel wes anderson aesthetic. Nihil tattooed accusamus, cred irony biodiesel keffiyeh artisan ullamco consequat. +

    +

    @mdo

    +

    + Veniam marfa mustache skateboard, adipisicing fugiat velit pitchfork beard. Freegan beard aliqua cupidatat mcsweeney's vero. Cupidatat four loko nisi, ea helvetica nulla carles. Tattooed cosby sweater food truck, mcsweeney's quis non freegan vinyl. Lo-fi wes anderson +1 sartorial. Carles non aesthetic exercitation quis gentrify. Brooklyn adipisicing craft beer vice keytar deserunt. +

    +

    one

    +

    + Occaecat commodo aliqua delectus. Fap craft beer deserunt skateboard ea. Lomo bicycle rights adipisicing banh mi, velit ea sunt next level locavore single-origin coffee in magna veniam. High life id vinyl, echo park consequat quis aliquip banh mi pitchfork. Vero VHS est adipisicing. Consectetur nisi DIY minim messenger bag. Cred ex in, sustainable delectus consectetur fanny pack iphone. +

    +

    two

    +

    + In incididunt echo park, officia deserunt mcsweeney's proident master cleanse thundercats sapiente veniam. Excepteur VHS elit, proident shoreditch +1 biodiesel laborum craft beer. Single-origin coffee wayfarers irure four loko, cupidatat terry richardson master cleanse. Assumenda you probably haven't heard of them art party fanny pack, tattooed nulla cardigan tempor ad. Proident wolf nesciunt sartorial keffiyeh eu banh mi sustainable. Elit wolf voluptate, lo-fi ea portland before they sold out four loko. Locavore enim nostrud mlkshk brooklyn nesciunt. +

    +

    three

    +

    + Ad leggings keytar, brunch id art party dolor labore. Pitchfork yr enim lo-fi before they sold out qui. Tumblr farm-to-table bicycle rights whatever. Anim keffiyeh carles cardigan. Velit seitan mcsweeney's photo booth 3 wolf moon irure. Cosby sweater lomo jean shorts, williamsburg hoodie minim qui you probably haven't heard of them et cardigan trust fund culpa biodiesel wes anderson aesthetic. Nihil tattooed accusamus, cred irony biodiesel keffiyeh artisan ullamco consequat. +

    +

    Keytar twee blog, culpa messenger bag marfa whatever delectus food truck. Sapiente synth id assumenda. Locavore sed helvetica cliche irony, thundercats you probably haven't heard of them consequat hoodie gluten-free lo-fi fap aliquip. Labore elit placeat before they sold out, terry richardson proident brunch nesciunt quis cosby sweater pariatur keffiyeh ut helvetica artisan. Cardigan craft beer seitan readymade velit. VHS chambray laboris tempor veniam. Anim mollit minim commodo ullamco thundercats. +

    +
    +
    +

    Using bootstrap-scrollspy.js

    +

    Call the scrollspy via javascript:

    +
    $('#navbar').scrollspy()
    +

    Markup

    +

    To easily add scrollspy behavior to your topbar navigation, just add data-spy="scroll" to the element you want to spy on (most typically this would be the body).

    +
    <body data-spy="scroll" >...</body>
    +
    + Heads up! + Navbar links must have resolvable id targets. For example, a <a href="#home">home</a> must correspond to something in the dom like <div id="home"></div>. +
    +

    Methods

    +

    .scrollspy('refresh')

    +

    When using scrollspy in conjunction with adding or removing of elements from the DOM, you'll need to call the refresh method like so:

    +
    +$('[data-spy="scroll"]').each(function () {
    +  var $spy = $(this).scrollspy('refresh')
    +});
    +
    +

    Options

    + + + + + + + + + + + + + + + + + +
    Nametypedefaultdescription
    offsetnumber10Pixels to offset from top when calculating position of scroll.
    +

    Events

    + + + + + + + + + + + + + +
    EventDescription
    activateThis event fires whenever a new item becomes activated by the scrollspy.
    +
    +
    +
    + + + + +
    + +
    +
    +

    This plugin adds quick, dynamic tab and pill functionality for transitioning through local content.

    + Download file +
    +
    +

    Example tabs

    +

    Click the tabs below to toggle between hidden panes, even via dropdown menus.

    + +
    +
    +

    Raw denim you probably haven't heard of them jean shorts Austin. Nesciunt tofu stumptown aliqua, retro synth master cleanse. Mustache cliche tempor, williamsburg carles vegan helvetica. Reprehenderit butcher retro keffiyeh dreamcatcher synth. Cosby sweater eu banh mi, qui irure terry richardson ex squid. Aliquip placeat salvia cillum iphone. Seitan aliquip quis cardigan american apparel, butcher voluptate nisi qui.

    +
    +
    +

    Food truck fixie locavore, accusamus mcsweeney's marfa nulla single-origin coffee squid. Exercitation +1 labore velit, blog sartorial PBR leggings next level wes anderson artisan four loko farm-to-table craft beer twee. Qui photo booth letterpress, commodo enim craft beer mlkshk aliquip jean shorts ullamco ad vinyl cillum PBR. Homo nostrud organic, assumenda labore aesthetic magna delectus mollit. Keytar helvetica VHS salvia yr, vero magna velit sapiente labore stumptown. Vegan fanny pack odio cillum wes anderson 8-bit, sustainable jean shorts beard ut DIY ethical culpa terry richardson biodiesel. Art party scenester stumptown, tumblr butcher vero sint qui sapiente accusamus tattooed echo park.

    +
    + + +
    +
    +

    Using bootstrap-tab.js

    +

    Enable tabbable tabs via javascript (each tab needs to be activated individually):

    +
    +$('#myTab a').click(function (e) {
    +  e.preventDefault();
    +  $(this).tab('show');
    +})
    +

    You can activate individual tabs in several ways:

    +
    +$('#myTab a[href="#profile"]').tab('show'); // Select tab by name
    +$('#myTab a:first').tab('show'); // Select first tab
    +$('#myTab a:last').tab('show'); // Select last tab
    +$('#myTab li:eq(2) a').tab('show'); // Select third tab (0-indexed)
    +
    +

    Markup

    +

    You can activate a tab or pill navigation without writing any javascript by simply specifying data-toggle="tab" or data-toggle="pill" on an element. Adding the nav and nav-tabs classes to the tab ul will apply the bootstrap tab styling.

    +
    +<ul class="nav nav-tabs">
    +  <li><a href="#home" data-toggle="tab">Home</a></li>
    +  <li><a href="#profile" data-toggle="tab">Profile</a></li>
    +  <li><a href="#messages" data-toggle="tab">Messages</a></li>
    +  <li><a href="#settings" data-toggle="tab">Settings</a></li>
    +</ul>
    +

    Methods

    +

    $().tab

    +

    + Activates a tab element and content container. Tab should have either a data-target or an href targeting a container node in the DOM. +

    +
    +<ul class="nav nav-tabs" id="myTab">
    +  <li class="active"><a href="#home">Home</a></li>
    +  <li><a href="#profile">Profile</a></li>
    +  <li><a href="#messages">Messages</a></li>
    +  <li><a href="#settings">Settings</a></li>
    +</ul>
    +
    +<div class="tab-content">
    +  <div class="tab-pane active" id="home">...</div>
    +  <div class="tab-pane" id="profile">...</div>
    +  <div class="tab-pane" id="messages">...</div>
    +  <div class="tab-pane" id="settings">...</div>
    +</div>
    +
    +<script>
    +  $(function () {
    +    $('#myTab a:last').tab('show');
    +  })
    +</script>
    +

    Events

    + + + + + + + + + + + + + + + + + +
    EventDescription
    showThis event fires on tab show, but before the new tab has been shown. Use event.target and event.relatedTarget to target the active tab and the previous active tab (if available) respectively.
    shownThis event fires on tab show after a tab has been shown. Use event.target and event.relatedTarget to target the active tab and the previous active tab (if available) respectively.
    + +
    +$('a[data-toggle="tab"]').on('shown', function (e) {
    +  e.target // activated tab
    +  e.relatedTarget // previous tab
    +})
    +
    +
    +
    + + + +
    + +
    +
    +

    About Tooltips

    +

    Inspired by the excellent jQuery.tipsy plugin written by Jason Frame; Tooltips are an updated version, which don't rely on images, use css3 for animations, and data-attributes for local title storage.

    + Download file +
    +
    +

    Example use of Tooltips

    +

    Hover over the links below to see tooltips:

    +
    +

    Tight pants next level keffiyeh you probably haven't heard of them. Photo booth beard raw denim letterpress vegan messenger bag stumptown. Farm-to-table seitan, mcsweeney's fixie sustainable quinoa 8-bit american apparel have a terry richardson vinyl chambray. Beard stumptown, cardigans banh mi lomo thundercats. Tofu biodiesel williamsburg marfa, four loko mcsweeney's cleanse vegan chambray. A really ironic artisan whatever keytar, scenester farm-to-table banksy Austin twitter handle freegan cred raw denim single-origin coffee viral. +

    +
    +
    +

    Using bootstrap-tooltip.js

    +

    Trigger the tooltip via javascript:

    +
    $('#example').tooltip(options)
    +

    Options

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Nametypedefaultdescription
    animationbooleantrueapply a css fade transition to the tooltip
    placementstring|function'top'how to position the tooltip - top | bottom | left | right
    selectorstringfalseIf a selector is provided, tooltip objects will be delegated to the specified targets.
    titlestring | function''default title value if `title` tag isn't present
    triggerstring'hover'how tooltip is triggered - hover | focus | manual
    delaynumber | object0 +

    delay showing and hiding the tooltip (ms) - does not apply to manual trigger type

    +

    If a number is supplied, delay is applied to both hide/show

    +

    Object structure is: delay: { show: 500, hide: 100 }

    +
    +
    + Heads up! + Options for individual tooltips can alternatively be specified through the use of data attributes. +
    +

    Markup

    +

    For performance reasons, the Tooltip and Popover data-apis are opt in. If you would like to use them just specify a selector option.

    +
    +<a href="#" rel="tooltip" title="first tooltip">hover over me</a>
    +
    +

    Methods

    +

    $().tooltip(options)

    +

    Attaches a tooltip handler to an element collection.

    +

    .tooltip('show')

    +

    Reveals an element's tooltip.

    +
    $('#element').tooltip('show')
    +

    .tooltip('hide')

    +

    Hides an element's tooltip.

    +
    $('#element').tooltip('hide')
    +

    .tooltip('toggle')

    +

    Toggles an element's tooltip.

    +
    $('#element').tooltip('toggle')
    +
    +
    +
    + + + + +
    + +
    +
    +

    About popovers

    +

    Add small overlays of content, like those on the iPad, to any element for housing secondary information.

    +

    * Requires Tooltip to be included

    + Download file +
    +
    +

    Example hover popover

    +

    Hover over the button to trigger the popover.

    + +
    +

    Using bootstrap-popover.js

    +

    Enable popovers via javascript:

    +
    $('#example').popover(options)
    +

    Options

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Nametypedefaultdescription
    animationbooleantrueapply a css fade transition to the tooltip
    placementstring|function'right'how to position the popover - top | bottom | left | right
    selectorstringfalseif a selector is provided, tooltip objects will be delegated to the specified targets
    triggerstring'hover'how tooltip is triggered - hover | focus | manual
    titlestring | function''default title value if `title` attribute isn't present
    contentstring | function''default content value if `data-content` attribute isn't present
    delaynumber | object0 +

    delay showing and hiding the popover (ms) - does not apply to manual trigger type

    +

    If a number is supplied, delay is applied to both hide/show

    +

    Object structure is: delay: { show: 500, hide: 100 }

    +
    +
    + Heads up! + Options for individual popovers can alternatively be specified through the use of data attributes. +
    +

    Markup

    +

    + For performance reasons, the Tooltip and Popover data-apis are opt in. If you would like to use them just specify a selector option. +

    +

    Methods

    +

    $().popover(options)

    +

    Initializes popovers for an element collection.

    +

    .popover('show')

    +

    Reveals an elements popover.

    +
    $('#element').popover('show')
    +

    .popover('hide')

    +

    Hides an elements popover.

    +
    $('#element').popover('hide')
    +

    .popover('toggle')

    +

    Toggles an elements popover.

    +
    $('#element').popover('toggle')
    +
    +
    +
    + + + + +
    + +
    +
    +

    About alerts

    +

    The alert plugin is a tiny class for adding close functionality to alerts.

    + Download +
    +
    +

    Example alerts

    +

    The alerts plugin works on regular alert messages, and block messages.

    +
    + + Holy guacamole! Best check yo self, you're not looking too good. +
    +
    + +

    Oh snap! You got an error!

    +

    Change this and that and try again. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Cras mattis consectetur purus sit amet fermentum.

    +

    + Take this action Or do this +

    +
    +
    +

    Using bootstrap-alert.js

    +

    Enable dismissal of an alert via javascript:

    +
    $(".alert").alert()
    +

    Markup

    +

    Just add data-dismiss="alert" to your close button to automatically give an alert close functionality.

    +
    <a class="close" data-dismiss="alert" href="#">&times;</a>
    +

    Methods

    +

    $().alert()

    +

    Wraps all alerts with close functionality. To have your alerts animate out when closed, make sure they have the .fade and .in class already applied to them.

    +

    .alert('close')

    +

    Closes an alert.

    +
    $(".alert").alert('close')
    +

    Events

    +

    Bootstrap's alert class exposes a few events for hooking into alert functionality.

    + + + + + + + + + + + + + + + + + +
    EventDescription
    closeThis event fires immediately when the close instance method is called.
    closedThis event is fired when the alert has been closed (will wait for css transitions to complete).
    +
    +$('#my-alert').bind('closed', function () {
    +  // do something…
    +})
    +
    +
    +
    + + + + +
    + +
    +
    +

    About

    +

    Do more with buttons. Control button states or create groups of buttons for more components like toolbars.

    + Download file +
    +
    +

    Example uses

    +

    Use the buttons plugin for states and toggles.

    + + + + + + + + + + + + + + + + + + + +
    Stateful + +
    Single toggle + +
    Checkbox +
    + + + +
    +
    Radio +
    + + + +
    +
    +
    +

    Using bootstrap-button.js

    +

    Enable buttons via javascript:

    +
    $('.nav-tabs').button()
    +

    Markup

    +

    Data attributes are integral to the button plugin. Check out the example code below for the various markup types.

    +
    +<!-- Add data-toggle="button" to activate toggling on a single button -->
    +<button class="btn" data-toggle="button">Single Toggle</button>
    +
    +<!-- Add data-toggle="buttons-checkbox" for checkbox style toggling on btn-group -->
    +<div class="btn-group" data-toggle="buttons-checkbox">
    +  <button class="btn">Left</button>
    +  <button class="btn">Middle</button>
    +  <button class="btn">Right</button>
    +</div>
    +
    +<!-- Add data-toggle="buttons-radio" for radio style toggling on btn-group -->
    +<div class="btn-group" data-toggle="buttons-radio">
    +  <button class="btn">Left</button>
    +  <button class="btn">Middle</button>
    +  <button class="btn">Right</button>
    +</div>
    +
    +

    Methods

    +

    $().button('toggle')

    +

    Toggles push state. Gives the button the appearance that it has been activated.

    +
    + Heads up! + You can enable auto toggling of a button by using the data-toggle attribute. +
    +
    <button class="btn" data-toggle="button" >…</button>
    +

    $().button('loading')

    +

    Sets button state to loading - disables button and swaps text to loading text. Loading text should be defined on the button element using the data attribute data-loading-text. +

    +
    <button class="btn" data-loading-text="loading stuff..." >...</button>
    +
    + Heads up! + Firefox persists the disabled state across page loads. A workaround for this is to use autocomplete="off". +
    +

    $().button('reset')

    +

    Resets button state - swaps text to original text.

    +

    $().button(string)

    +

    Resets button state - swaps text to any data defined text state.

    +
    <button class="btn" data-complete-text="finished!" >...</button>
    +<script>
    +  $('.btn').button('complete')
    +</script>
    +
    +
    +
    + + + + +
    + +
    +
    +

    About

    +

    Get base styles and flexible support for collapsible components like accordions and navigation.

    + Download file +

    * Requires the Transitions plugin to be included.

    +
    +
    +

    Example accordion

    +

    Using the collapse plugin, we built a simple accordion style widget:

    + +
    +
    + +
    +
    + Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS. +
    +
    +
    +
    + +
    +
    + Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS. +
    +
    +
    +
    + +
    +
    + Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS. +
    +
    +
    +
    + + +
    +

    Using bootstrap-collapse.js

    +

    Enable via javascript:

    +
    $(".collapse").collapse()
    +

    Options

    + + + + + + + + + + + + + + + + + + + + + + + +
    Nametypedefaultdescription
    parentselectorfalseIf selector then all collapsible elements under the specified parent will be closed when this collapsible item is shown. (similar to traditional accordion behavior)
    togglebooleantrueToggles the collapsible element on invocation
    +

    Markup

    +

    Just add data-toggle="collapse" and a data-target to element to automatically assign control of a collapsible element. The data-target attribute accepts a css selector to apply the collapse to. Be sure to add the class collapse to the collapsible element. If you'd like it to default open, add the additional class in.

    +
    +<button class="btn btn-danger" data-toggle="collapse" data-target="#demo">
    +  simple collapsible
    +</button>
    +
    +<div id="demo" class="collapse in"> … </div>
    +
    + Heads up! + To add accordion-like group management to a collapsible control, add the data attribute data-parent="#selector". Refer to the demo to see this in action. +
    +

    Methods

    +

    .collapse(options)

    +

    Activates your content as a collapsible element. Accepts an optional options object. +

    +$('#myCollapsible').collapse({
    +  toggle: false
    +})
    +

    .collapse('toggle')

    +

    Toggles a collapsible element to shown or hidden.

    +

    .collapse('show')

    +

    Shows a collapsible element.

    +

    .collapse('hide')

    +

    Hides a collapsible element.

    +

    Events

    +

    + Bootstrap's collapse class exposes a few events for hooking into collapse functionality. +

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    EventDescription
    showThis event fires immediately when the show instance method is called.
    shownThis event is fired when a collapse element has been made visible to the user (will wait for css transitions to complete).
    hide + This event is fired immediately when the hide method has been called. +
    hiddenThis event is fired when a collapse element has been hidden from the user (will wait for css transitions to complete).
    + +
    +$('#myCollapsible').on('hidden', function () {
    +  // do something…
    +})
    +
    +
    +
    + + + + + + + + + +
    + +
    +
    +

    About

    +

    A basic, easily extended plugin for quickly creating elegant typeaheads with any form text input.

    + Download file +
    +
    +

    Example

    +

    Start typing in the field below to show the typeahead results.

    +
    + +
    +
    +

    Using bootstrap-typeahead.js

    +

    Call the typeahead via javascript:

    +
    $('.typeahead').typeahead()
    +

    Options

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Nametypedefaultdescription
    sourcearray[ ]The data source to query against.
    itemsnumber8The max number of items to display in the dropdown.
    matcherfunctioncase insensitiveThe method used to determine if a query matches an item. Accepts a single argument, the item against which to test the query. Access the current query with this.query. Return a boolean true if query is a match.
    sorterfunctionexact match,
    case sensitive,
    case insensitive
    Method used to sort autocomplete results. Accepts a single argument items and has the scope of the typeahead instance. Reference the current query with this.query.
    highlighterfunctionhighlights all default matchesMethod used to highlight autocomplete results. Accepts a single argument item and has the scope of the typeahead instance. Should return html.
    + +

    Markup

    +

    Add data attributes to register an element with typeahead functionality.

    +
    +<input type="text" data-provide="typeahead">
    +
    +

    Methods

    +

    .typeahead(options)

    +

    Initializes an input with a typeahead.

    +
    +
    +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/symposion_project/static/bootstrap/docs/less.html b/symposion_project/static/bootstrap/docs/less.html new file mode 100644 index 00000000..8f1ebf7a --- /dev/null +++ b/symposion_project/static/bootstrap/docs/less.html @@ -0,0 +1,1060 @@ + + + + + Less · Twitter Bootstrap + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +
    +

    Using LESS with Bootstrap

    +

    Customize and extend Bootstrap with LESS, a CSS preprocessor, to take advantage of the variables, mixins, and more used to build Bootstrap's CSS.

    + +
    + + + + +
    + +
    +
    +

    Why LESS?

    +

    Bootstrap is made with LESS at its core, a dynamic stylesheet language created by our good friend, Alexis Sellier. It makes developing systems-based CSS faster, easier, and more fun.

    +
    +
    +

    What's included?

    +

    As an extension of CSS, LESS includes variables, mixins for reusable snippets of code, operations for simple math, nesting, and even color functions.

    +
    +
    +

    Learn more

    + LESS CSS +

    Visit the official website at http://lesscss.org to learn more.

    +
    +
    +
    +
    +

    Variables

    +

    Managing colors and pixel values in CSS can be a bit of a pain, usually full of copy and paste. Not with LESS though—assign colors or pixel values as variables and change them once.

    +
    +
    +

    Mixins

    +

    Those three border-radius declarations you need to make in regular ol' CSS? Now they're down to one line with the help of mixins, snippets of code you can reuse anywhere.

    +
    +
    +

    Operations

    +

    Make your grid, leading, and more super flexible by doing the math on the fly with operations. Multiply, divide, add, and subtract your way to CSS sanity.

    +
    +
    +
    + + + + +
    + + +

    Scaffolding and links

    + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    @bodyBackground@whitePage background color
    @textColor@grayDarkDefault text color for entire body, headings, and more
    @linkColor#08cDefault link text color
    @linkColorHoverdarken(@linkColor, 15%)Default link text hover color
    +

    Grid system

    + + + + + + + + + + + + + + + + + + + + + + + +
    @gridColumns12
    @gridColumnWidth60px
    @gridGutterWidth20px
    @fluidGridColumnWidth6.382978723%
    @fluidGridGutterWidth2.127659574%
    +

    Typography

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    @sansFontFamily"Helvetica Neue", Helvetica, Arial, sans-serif
    @serifFontFamilyGeorgia, "Times New Roman", Times, serif
    @monoFontFamilyMenlo, Monaco, "Courier New", monospace
    @baseFontSize13pxMust be pixels
    @baseFontFamily@sansFontFamily
    @baseLineHeight18pxMust be pixels
    @altFontFamily@serifFontFamily
    @headingsFontFamilyinherit
    @headingsFontWeightbold
    @headingsColorinherit
    +

    Tables

    + + + + + + + + + + + + + + + + + + + +
    @tableBackgroundtransparent
    @tableBackgroundAccent#f9f9f9
    @tableBackgroundHover#f5f5f5
    @tableBorderddd
    + +

    Grayscale colors

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    @black#000
    @grayDarker#222
    @grayDark#333
    @gray#555
    @grayLight#999
    @grayLighter#eee
    @white#fff
    +

    Accent colors

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    @blue#049cdb
    @green#46a546
    @red#9d261d
    @yellow#ffc40d
    @orange#f89406
    @pink#c3325f
    @purple#7a43b6
    + + +

    Components

    + +

    Buttons

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    @btnBackground@white
    @btnBackgroundHighlightdarken(@white, 10%)
    @btnBorderdarken(@white, 20%)
    @btnPrimaryBackground@linkColor
    @btnPrimaryBackgroundHighlightspin(@btnPrimaryBackground, 15%)
    @btnInfoBackground#5bc0de
    @btnInfoBackgroundHighlight#2f96b4
    @btnSuccessBackground#62c462
    @btnSuccessBackgroundHighlight51a351
    @btnWarningBackgroundlighten(@orange, 15%)
    @btnWarningBackgroundHighlight@orange
    @btnDangerBackground#ee5f5b
    @btnDangerBackgroundHighlight#bd362f
    @btnInverseBackground@gray
    @btnInverseBackgroundHighlight@grayDarker
    +

    Forms

    + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    @placeholderText@grayLight
    @inputBackground@white
    @inputBorder#ccc
    @inputBorderRadius3px
    @inputDisabledBackground@grayLighter
    @formActionsBackground#f5f5f5
    +

    Form states and alerts

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    @warningText#c09853
    @warningBackground#f3edd2
    @errorText#b94a48
    @errorBackground#f2dede
    @successText#468847
    @successBackground#dff0d8
    @infoText#3a87ad
    @infoBackground#d9edf7
    + +

    Navbar

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    @navbarHeight40px
    @navbarBackground@grayDarker
    @navbarBackgroundHighlight@grayDark
    @navbarText@grayLight
    @navbarLinkColor@grayLight
    @navbarLinkColorHover@white
    @navbarLinkColorActive@navbarLinkColorHover
    @navbarLinkBackgroundHovertransparent
    @navbarLinkBackgroundActive@navbarBackground
    @navbarSearchBackgroundlighten(@navbarBackground, 25%)
    @navbarSearchBackgroundFocus@white
    @navbarSearchBorderdarken(@navbarSearchBackground, 30%)
    @navbarSearchPlaceholderColor#ccc
    @navbarBrandColor@navbarLinkColor
    +

    Dropdowns

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    @dropdownBackground@white
    @dropdownBorderrgba(0,0,0,.2)
    @dropdownLinkColor@grayDark
    @dropdownLinkColorHover@white
    @dropdownLinkBackgroundHover@linkColor
    @@dropdownDividerTop#e5e5e5
    @@dropdownDividerBottom@white
    +

    Hero unit

    + + + + + + + + + + + + + + + + + + +
    @heroUnitBackground@grayLighter
    @heroUnitHeadingColorinherit
    @heroUnitLeadColorinhereit
    + + +
    + + + + +
    + +

    About mixins

    +
    +
    +

    Basic mixins

    +

    A basic mixin is essentially an include or a partial for a snippet of CSS. They're written just like a CSS class and can be called anywhere.

    +
    +.element {
    +  .clearfix();
    +}
    +
    +
    +
    +

    Parametric mixins

    +

    A parametric mixin is just like a basic mixin, but it also accepts parameters (hence the name) with optional default values.

    +
    +.element {
    +  .border-radius(4px);
    +}
    +
    +
    +
    +

    Easily add your own

    +

    Nearly all of Bootstrap's mixins are stored in mixins.less, a wonderful utility .less file that enables you to use a mixin in any of the .less files in the toolkit.

    +

    So, go ahead and use the existing ones or feel free to add your own as you need.

    +
    +
    +

    Included mixins

    +

    Utilities

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    MixinParametersUsage
    .clearfix()noneAdd to any parent to clear floats within
    .tab-focus()noneApply the Webkit focus style and round Firefox outline
    .center-block()noneAuto center a block-level element using margin: auto
    .ie7-inline-block()noneUse in addition to regular display: inline-block to get IE7 support
    .size()@height @widthQuickly set the height and width on one line
    .square()@sizeBuilds on .size() to set the width and height as same value
    .opacity()@opacitySet, in whole numbers, the opacity percentage (e.g., "50" or "75")
    +

    Forms

    + + + + + + + + + + + + + + + +
    MixinParametersUsage
    .placeholder()@color: @placeholderTextSet the placeholder text color for inputs
    +

    Typography

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    MixinParametersUsage
    #font > #family > .serif()noneMake an element use a serif font stack
    #font > #family > .sans-serif()noneMake an element use a sans-serif font stack
    #font > #family > .monospace()noneMake an element use a monospace font stack
    #font > .shorthand()@size: @baseFontSize, @weight: normal, @lineHeight: @baseLineHeightEasily set font size, weight, and leading
    #font > .serif()@size: @baseFontSize, @weight: normal, @lineHeight: @baseLineHeightSet font family to serif, and control size, weight, and leading
    #font > .sans-serif()@size: @baseFontSize, @weight: normal, @lineHeight: @baseLineHeightSet font family to sans-serif, and control size, weight, and leading
    #font > .monospace()@size: @baseFontSize, @weight: normal, @lineHeight: @baseLineHeightSet font family to monospace, and control size, weight, and leading
    +

    Grid system

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    MixinParametersUsage
    .container-fixed()noneCreate a horizontally centered container for holding your content
    #grid > .core()@gridColumnWidth, @gridGutterWidthGenerate a pixel grid system (container, row, and columns) with n columns and x pixel wide gutter
    #grid > .fluid()@fluidGridColumnWidth, @fluidGridGutterWidthGenerate a percent grid system with n columns and x % wide gutter
    #grid > .input()@gridColumnWidth, @gridGutterWidthGenerate the pixel grid system for input elements, accounting for padding and borders
    .makeColumn@columns: 1, @offset: 0Turn any div into a grid column without the .span* classes
    +

    CSS3 properties

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    MixinParametersUsage
    .border-radius()@radiusRound the corners of an element. Can be a single value or four space-separated values
    .box-shadow()@shadowAdd a drop shadow to an element
    .transition()@transitionAdd CSS3 transition effect (e.g., all .2s linear)
    .rotate()@degreesRotate an element n degrees
    .scale()@ratioScale an element to n times its original size
    .translate()@x, @yMove an element on the x and y planes
    .background-clip()@clipCrop the background of an element (useful for border-radius)
    .background-size()@sizeControl the size of background images via CSS3
    .box-sizing()@boxmodelChange the box model for an element (e.g., border-box for a full-width input)
    .user-select()@selectControl cursor selection of text on a page
    .backface-visibility()@visibility: visiblePrevent flickering of content when using CSS 3D transforms
    .resizable()@direction: bothMake any element resizable on the right and bottom
    .content-columns()@columnCount, @columnGap: @gridGutterWidthMake the content of any element use CSS3 columns
    .hyphens()@mode: autoCSS3 hyphenation when you want it (includes word-wrap: break-word)
    +

    Backgrounds and gradients

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    MixinParametersUsage
    #translucent > .background()@color: @white, @alpha: 1Give an element a translucent background color
    #translucent > .border()@color: @white, @alpha: 1Give an element a translucent border color
    #gradient > .vertical()@startColor, @endColorCreate a cross-browser vertical background gradient
    #gradient > .horizontal()@startColor, @endColorCreate a cross-browser horizontal background gradient
    #gradient > .directional()@startColor, @endColor, @degCreate a cross-browser directional background gradient
    #gradient > .vertical-three-colors()@startColor, @midColor, @colorStop, @endColorCreate a cross-browser three-color background gradient
    #gradient > .radial()@innerColor, @outerColorCreate a cross-browser radial background gradient
    #gradient > .striped()@color, @angleCreate a cross-browser striped background gradient
    #gradientBar()@primaryColor, @secondaryColorUsed for buttons to assign a gradient and slightly darker border
    +
    + + + + +
    + +
    + Note: If you're submitting a pull request to GitHub with modified CSS, you must recompile the CSS via any of these methods. +
    +

    Tools for compiling

    +
    +
    +

    Node with makefile

    +

    Install the LESS command line compiler, JSHint, Recess, and uglify-js globally with npm by running the following command:

    +
    $ npm install -g less jshint recess uglify-js
    +

    Once installed just run make from the root of your bootstrap directory and you're all set.

    +

    Additionally, if you have watchr installed, you may run make watch to have bootstrap automatically rebuilt every time you edit a file in the bootstrap lib (this isn't required, just a convenience method).

    +
    +
    +

    Command line

    +

    Install the LESS command line tool via Node and run the following command:

    +
    $ lessc ./less/bootstrap.less > bootstrap.css
    +

    Be sure to include --compress in that command if you're trying to save some bytes!

    +
    +
    +

    Javascript

    +

    Download the latest Less.js and include the path to it (and Bootstrap) in the <head>.

    +
    +<link rel="stylesheet/less" href="/path/to/bootstrap.less">
    +<script src="/path/to/less.js"></script>
    +
    +

    To recompile the .less files, just save them and reload your page. Less.js compiles them and stores them in local storage.

    +
    +
    +
    +
    +

    Unofficial Mac app

    +

    The unofficial Mac app watches directories of .less files and compiles the code to local files after every save of a watched .less file.

    +

    If you like, you can toggle preferences in the app for automatic minifying and which directory the compiled files end up in.

    +
    +
    +

    More Mac apps

    +

    Crunch

    +

    Crunch is a great looking LESS editor and compiler built on Adobe Air.

    +

    CodeKit

    +

    Created by the same guy as the unofficial Mac app, CodeKit is a Mac app that compiles LESS, SASS, Stylus, and CoffeeScript.

    +

    Simpless

    +

    Mac, Linux, and PC app for drag and drop compiling of LESS files. Plus, the source code is on GitHub.

    +
    +
    +
    + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/symposion_project/static/bootstrap/docs/scaffolding.html b/symposion_project/static/bootstrap/docs/scaffolding.html new file mode 100644 index 00000000..4d6835ca --- /dev/null +++ b/symposion_project/static/bootstrap/docs/scaffolding.html @@ -0,0 +1,671 @@ + + + + + Scaffolding · Twitter Bootstrap + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +
    +

    Scaffolding

    +

    Bootstrap is built on a responsive 12-column grid. We've also included fixed- and fluid-width layouts based on that system.

    + +
    + + + + + +
    + +
    +
    +

    Requires HTML5 doctype

    +

    Bootstrap makes use of HTML elements and CSS properties that require the use of the HTML5 doctype. Be sure to include it at the beginning of every Bootstrapped page in your project.

    +
    +<!DOCTYPE html>
    +<html lang="en">
    +  ...
    +</html>
    +
    +
    +
    +

    Typography and links

    +

    Within the scaffolding.less file, we set basic global display, typography, and link styles. Specifically, we:

    +
      +
    • Remove margin on the body
    • +
    • Set background-color: white; on the body
    • +
    • Use the @baseFontFamily, @baseFontSize, and @baseLineHeight attributes as our typographyic base
    • +
    • Set the global link color via @linkColor and apply link underlines only on :hover
    • +
    +
    +
    +

    Reset via Normalize

    +

    As of Bootstrap 2, the traditional CSS reset has evolved to make use of elements from Normalize.css, a project by Nicolas Gallagher that also powers the HTML5 Boilerplate.

    +

    The new reset can still be found in reset.less, but with many elements removed for brevity and accuracy.

    +
    +
    +
    + + + + + +
    + + +
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    +
    +
    4
    +
    4
    +
    4
    +
    +
    +
    4
    +
    8
    +
    +
    +
    6
    +
    6
    +
    +
    +
    12
    +
    +
    +
    +

    The default grid system provided in Bootstrap utilizes 12 columns that render out at widths of 724px, 940px (default without responsive CSS included), and 1170px. Below 767px viewports, the columns become fluid and stack vertically.

    +
    +
    +
    +<div class="row">
    +  <div class="span4">...</div>
    +  <div class="span8">...</div>
    +</div>
    +
    +
    +
    +

    As shown here, a basic layout can be created with two "columns", each spanning a number of the 12 foundational columns we defined as part of our grid system.

    +
    +
    + +
    + +

    Offsetting columns

    +
    +
    4
    +
    4 offset 4
    +
    +
    +
    3 offset 3
    +
    3 offset 3
    +
    +
    +
    8 offset 4
    +
    +
    +<div class="row">
    +  <div class="span4">...</div>
    +  <div class="span4 offset4">...</div>
    +</div>
    +
    + +
    + +

    Nesting columns

    +
    +
    +

    With the static (non-fluid) grid system in Bootstrap, nesting is easy. To nest your content, just add a new .row and set of .span* columns within an existing .span* column.

    +

    Example

    +

    Nested rows should include a set of columns that add up to the number of columns of it's parent. For example, two nested .span3 columns should be placed within a .span6.

    +
    +
    + Level 1 of column +
    +
    + Level 2 +
    +
    + Level 2 +
    +
    +
    +
    +
    +
    +
    +<div class="row">
    +  <div class="span6">
    +    Level 1 column
    +    <div class="row">
    +      <div class="span3">Level 2</div>
    +      <div class="span3">Level 2</div>
    +    </div>
    +  </div>
    +</div>
    +
    +
    +
    +
    + + + + +
    + + +

    Fluid columns

    +
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    +
    +
    4
    +
    4
    +
    4
    +
    +
    +
    4
    +
    8
    +
    +
    +
    6
    +
    6
    +
    +
    +
    12
    +
    + +
    +
    +

    Percents, not pixels

    +

    The fluid grid system uses percents for column widths instead of fixed pixels. It also has the same responsive variations as our fixed grid system, ensuring proper proportions for key screen resolutions and devices.

    +
    +
    +

    Fluid rows

    +

    Make any row fluid simply by changing .row to .row-fluid. The columns stay the exact same, making it super straightforward to flip between fixed and fluid layouts.

    +
    +
    +

    Markup

    +
    +<div class="row-fluid">
    +  <div class="span4">...</div>
    +  <div class="span8">...</div>
    +</div>
    +
    +
    +
    + +

    Fluid nesting

    +
    +
    +

    Nesting with fluid grids is a bit different: the number of nested columns doesn't need to match the parent. Instead, your columns are reset at each level because each row takes up 100% of the parent column.

    +
    +
    + Fluid 12 +
    +
    + Fluid 6 +
    +
    + Fluid 6 +
    +
    +
    +
    +
    +
    +
    +<div class="row-fluid">
    +  <div class="span12">
    +    Level 1 of column
    +    <div class="row-fluid">
    +      <div class="span6">Level 2</div>
    +      <div class="span6">Level 2</div>
    +    </div>
    +  </div>
    +</div>
    +
    +
    +
    + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    VariableDefault valueDescription
    @gridColumns12Number of columns
    @gridColumnWidth60pxWidth of each column
    @gridGutterWidth20pxNegative space between columns
    +
    +
    +

    Variables in LESS

    +

    Built into Bootstrap are a handful of variables for customizing the default 940px grid system, documented above. All variables for the grid are stored in variables.less.

    +
    +
    +

    How to customize

    +

    Modifying the grid means changing the three @grid* variables and recompiling Bootstrap. Change the grid variables in variables.less and use one of the four ways documented to recompile. If you're adding more columns, be sure to add the CSS for those in grid.less.

    +
    +
    +

    Staying responsive

    +

    Customization of the grid only works at the default level, the 940px grid. To maintain the responsive aspects of Bootstrap, you'll also have to customize the grids in responsive.less.

    +
    +
    + +
    + + + + +
    + + +
    +
    +

    Fixed layout

    +

    The default and simple 940px-wide, centered layout for just about any website or page provided by a single <div class="container">.

    +
    +
    +
    +
    +<body>
    +  <div class="container">
    +    ...
    +  </div>
    +</body>
    +
    +
    +
    +

    Fluid layout

    +

    <div class="container-fluid"> gives flexible page structure, min- and max-widths, and a left-hand sidebar. It's great for apps and docs.

    +
    +
    +
    +
    +
    +<div class="container-fluid">
    +  <div class="row-fluid">
    +    <div class="span2">
    +      <!--Sidebar content-->
    +    </div>
    +    <div class="span10">
    +      <!--Body content-->
    +    </div>
    +  </div>
    +</div>
    +
    +
    +
    +
    + + + + + +
    + + +
    +
    +

    Responsive devices

    +

    What they do

    +

    Media queries allow for custom CSS based on a number of conditions—ratios, widths, display type, etc—but usually focuses around min-width and max-width.

    +
      +
    • Modify the width of column in our grid
    • +
    • Stack elements instead of float wherever necessary
    • +
    • Resize headings and text to be more appropriate for devices
    • +
    +

    Use media queries responsibly and only as a start to your mobile audiences. For larger projects, do consider dedicated code bases and not layers of media queries.

    +
    +
    +

    Supported devices

    +

    Bootstrap supports a handful of media queries in a single file to help make your projects more appropriate on different devices and screen resolutions. Here's what's included:

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    LabelLayout widthColumn widthGutter width
    Smartphones480px and belowFluid columns, no fixed widths
    Smartphones to tablets767px and belowFluid columns, no fixed widths
    Portrait tablets768px and above42px20px
    Default980px and up60px20px
    Large display1200px and up70px30px
    + +

    Requires meta tag

    +

    To ensure devices display responsive pages properly, include the viewport meta tag.

    +
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    +
    +
    + +
    + + +

    Using the media queries

    +
    +
    +

    Bootstrap doesn't automatically include these media queries, but understanding and adding them is very easy and requires minimal setup. You have a few options for including the responsive features of Bootstrap:

    +
      +
    1. Use the compiled responsive version, bootstrap-responsive.css
    2. +
    3. Add @import "responsive.less" and recompile Bootstrap
    4. +
    5. Modify and recompile responsive.less as a separate file
    6. +
    +

    Why not just include it? Truth be told, not everything needs to be responsive. Instead of encouraging developers to remove this feature, we figure it best to enable it.

    +
    +
    +
    +  /* Landscape phones and down */
    +  @media (max-width: 480px) { ... }
    +
    +  /* Landscape phone to portrait tablet */
    +  @media (max-width: 767px) { ... }
    +
    +  /* Portrait tablet to landscape and desktop */
    +  @media (min-width: 768px) and (max-width: 979px) { ... }
    +
    +  /* Large desktop */
    +  @media (min-width: 1200px) { ... }
    +
    +
    +
    +
    + + +

    Responsive utility classes

    +
    +
    +

    What are they

    +

    For faster mobile-friendly development, use these basic utility classes for showing and hiding content by device.

    +

    When to use

    +

    Use on a limited basis and avoid creating entirely different versions of the same site. Instead, use them to complement each device's presentation.

    +

    For example, you might show a <select> element for nav on mobile layouts, but not on tablets or desktops.

    +
    +
    +

    Support classes

    +

    Shown here is a table of the classes we support and their effect on a given media query layout (labeled by device). They can be found in responsive.less.

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    ClassPhones 480px and belowTablets 767px and belowDesktops 768px and above
    .visible-phoneVisible
    .visible-tabletVisible
    .visible-desktopVisible
    .hidden-phoneVisibleVisible
    .hidden-tabletVisibleVisible
    .hidden-desktopVisibleVisible
    +

    Test case

    +

    Resize your browser or load on different devices to test the above classes.

    +

    Visible on...

    +

    Green checkmarks indicate that class is visible in your current viewport.

    +
      +
    • Phone✔ Phone
    • +
    • Tablet✔ Tablet
    • +
    • Desktop✔ Desktop
    • +
    +

    Hidden on...

    +

    Here, green checkmarks indicate that class is hidden in your current viewport.

    +
      +
    • Phone✔ Phone
    • +
    • Tablet✔ Tablet
    • +
    • Desktop✔ Desktop
    • +
    +
    +
    + + +
    +
    +
    +
    +
    + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/symposion_project/static/bootstrap/docs/templates/layout.mustache b/symposion_project/static/bootstrap/docs/templates/layout.mustache new file mode 100644 index 00000000..ae2ce4aa --- /dev/null +++ b/symposion_project/static/bootstrap/docs/templates/layout.mustache @@ -0,0 +1,146 @@ + + + + + {{title}} + + + + + + + + + + + + + + + + + + + + + {{#production}} + + {{/production}} + + + + + + + + +
    + +{{>body}} + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + {{#production}} + + + {{/production}} + + + diff --git a/symposion_project/static/bootstrap/docs/templates/pages/base-css.mustache b/symposion_project/static/bootstrap/docs/templates/pages/base-css.mustache new file mode 100644 index 00000000..1404a31f --- /dev/null +++ b/symposion_project/static/bootstrap/docs/templates/pages/base-css.mustache @@ -0,0 +1,1594 @@ + +
    +

    {{_i}}Base CSS{{/i}}

    +

    {{_i}}On top of the scaffolding, basic HTML elements are styled and enhanced with extensible classes to provide a fresh, consistent look and feel.{{/i}}

    + +
    + + + +
    + + +

    {{_i}}Headings & body copy{{/i}}

    + + +
    +
    +

    {{_i}}Typographic scale{{/i}}

    +

    {{_i}}The entire typographic grid is based on two Less variables in our variables.less file: @baseFontSize and @baseLineHeight. The first is the base font-size used throughout and the second is the base line-height.{{/i}}

    +

    {{_i}}We use those variables, and some math, to create the margins, paddings, and line-heights of all our type and more.{{/i}}

    +
    +
    +

    {{_i}}Example body text{{/i}}

    +

    Nullam quis risus eget urna mollis ornare vel eu leo. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nullam id dolor id nibh ultricies vehicula.

    +

    {{_i}}Lead body copy{{/i}}

    +

    {{_i}}Make a paragraph stand out by adding .lead.{{/i}}

    +

    Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. Duis mollis, est non commodo luctus.

    +
    +
    +
    +

    h1. {{_i}}Heading 1{{/i}}

    +

    h2. {{_i}}Heading 2{{/i}}

    +

    h3. {{_i}}Heading 3{{/i}}

    +

    h4. {{_i}}Heading 4{{/i}}

    +
    h5. {{_i}}Heading 5{{/i}}
    +
    h6. {{_i}}Heading 6{{/i}}
    +
    +
    +
    + + +

    {{_i}}Emphasis, address, and abbreviation{{/i}}

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    {{_i}}Element{{/i}}{{_i}}Usage{{/i}}{{_i}}Optional{{/i}}
    + <strong> + + {{_i}}For emphasizing a snippet of text with important{{/i}} + + {{_i}}None{{/i}} +
    + <em> + + {{_i}}For emphasizing a snippet of text with stress{{/i}} + + {{_i}}None{{/i}} +
    + <abbr> + + {{_i}}Wraps abbreviations and acronyms to show the expanded version on hover{{/i}} + +

    {{_i}}Include optional title attribute for expanded text{{/i}}

    + {{_i}}Use .initialism class for uppercase abbreviations.{{/i}} +
    + <address> + + {{_i}}For contact information for its nearest ancestor or the entire body of work{{/i}} + + {{_i}}Preserve formatting by ending all lines with <br>{{/i}} +
    + +
    +
    +

    {{_i}}Using emphasis{{/i}}

    +

    Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Maecenas faucibus mollis interdum. Nulla vitae elit libero, a pharetra augue.

    +

    {{_i}}Note: Feel free to use <b> and <i> in HTML5, but their usage has changed a bit. <b> is meant to highlight words or phrases without conveying additional importance while <i> is mostly for voice, technical terms, etc.{{/i}}

    +
    +
    +

    {{_i}}Example addresses{{/i}}

    +

    {{_i}}Here are two examples of how the <address> tag can be used:{{/i}}

    +
    + Twitter, Inc.
    + 795 Folsom Ave, Suite 600
    + San Francisco, CA 94107
    + P: (123) 456-7890 +
    +
    + {{_i}}Full Name{{/i}}
    + {{_i}}first.last@gmail.com{{/i}} +
    +
    +
    +

    {{_i}}Example abbreviations{{/i}}

    +

    {{_i}}Abbreviations with a title attribute have a light dotted bottom border and a help cursor on hover. This gives users extra indication something will be shown on hover.{{/i}}

    +

    {{_i}}Add the initialism class to an abbreviation to increase typographic harmony by giving it a slightly smaller text size.{{/i}}

    +

    {{_i}}HTML is the best thing since sliced bread.{{/i}}

    +

    {{_i}}An abbreviation of the word attribute is attr.{{/i}}

    +
    +
    + + + +

    {{_i}}Blockquotes{{/i}}

    + + + + + + + + + + + + + + + + + + + + +
    {{_i}}Element{{/i}}{{_i}}Usage{{/i}}{{_i}}Optional{{/i}}
    + <blockquote> + + {{_i}}Block-level element for quoting content from another source{{/i}} + +

    {{_i}}Add cite attribute for source URL{{/i}}

    + {{_i}}Use .pull-left and .pull-right classes for floated options{{/i}} +
    + <small> + + {{_i}}Optional element for adding a user-facing citation, typically an author with title of work{{/i}} + + {{_i}}Place the <cite> around the title or name of source{{/i}} +
    +
    +
    +

    {{_i}}To include a blockquote, wrap <blockquote> around any HTML as the quote. For straight quotes we recommend a <p>.{{/i}}

    +

    {{_i}}Include an optional <small> element to cite your source and you'll get an em dash &mdash; before it for styling purposes.{{/i}}

    +
    +
    +
    +<blockquote>
    +  <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer posuere erat a ante venenatis.</p>
    +  <small>{{_i}}Someone famous{{/i}}</small>
    +</blockquote>
    +
    +
    +
    + +

    {{_i}}Example blockquotes{{/i}}

    +
    +
    +

    {{_i}}Default blockquotes are styled as such:{{/i}}

    +
    +

    Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer posuere erat a ante venenatis.

    + {{_i}}Someone famous in Body of work{{/i}} +
    +
    +
    +

    {{_i}}To float your blockquote to the right, add class="pull-right":{{/i}}

    +
    +

    Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer posuere erat a ante venenatis.

    + {{_i}}Someone famous in Body of work{{/i}} +
    +
    +
    + + + +

    {{_i}}Lists{{/i}}

    +
    +
    +

    {{_i}}Unordered{{/i}}

    +

    <ul>

    +
      +
    • Lorem ipsum dolor sit amet
    • +
    • Consectetur adipiscing elit
    • +
    • Integer molestie lorem at massa
    • +
    • Facilisis in pretium nisl aliquet
    • +
    • Nulla volutpat aliquam velit +
        +
      • Phasellus iaculis neque
      • +
      • Purus sodales ultricies
      • +
      • Vestibulum laoreet porttitor sem
      • +
      • Ac tristique libero volutpat at
      • +
      +
    • +
    • Faucibus porta lacus fringilla vel
    • +
    • Aenean sit amet erat nunc
    • +
    • Eget porttitor lorem
    • +
    +
    +
    +

    {{_i}}Unstyled{{/i}}

    +

    <ul class="unstyled">

    +
      +
    • Lorem ipsum dolor sit amet
    • +
    • Consectetur adipiscing elit
    • +
    • Integer molestie lorem at massa
    • +
    • Facilisis in pretium nisl aliquet
    • +
    • Nulla volutpat aliquam velit +
        +
      • Phasellus iaculis neque
      • +
      • Purus sodales ultricies
      • +
      • Vestibulum laoreet porttitor sem
      • +
      • Ac tristique libero volutpat at
      • +
      +
    • +
    • Faucibus porta lacus fringilla vel
    • +
    • Aenean sit amet erat nunc
    • +
    • Eget porttitor lorem
    • +
    +
    +
    +

    {{_i}}Ordered{{/i}}

    +

    <ol>

    +
      +
    1. Lorem ipsum dolor sit amet
    2. +
    3. Consectetur adipiscing elit
    4. +
    5. Integer molestie lorem at massa
    6. +
    7. Facilisis in pretium nisl aliquet
    8. +
    9. Nulla volutpat aliquam velit
    10. +
    11. Faucibus porta lacus fringilla vel
    12. +
    13. Aenean sit amet erat nunc
    14. +
    15. Eget porttitor lorem
    16. +
    +
    +
    +
    +
    +
    +

    {{_i}}Description{{/i}}

    +

    <dl>

    +
    +
    {{_i}}Description lists{{/i}}
    +
    {{_i}}A description list is perfect for defining terms.{{/i}}
    +
    Euismod
    +
    Vestibulum id ligula porta felis euismod semper eget lacinia odio sem nec elit.
    +
    Donec id elit non mi porta gravida at eget metus.
    +
    Malesuada porta
    +
    Etiam porta sem malesuada magna mollis euismod.
    +
    +
    +
    +

    {{_i}}Horizontal description{{/i}}

    +

    <dl class="dl-horizontal">

    +
    +
    {{_i}}Description lists{{/i}}
    +
    {{_i}}A description list is perfect for defining terms.{{/i}}
    +
    Euismod
    +
    Vestibulum id ligula porta felis euismod semper eget lacinia odio sem nec elit.
    +
    Donec id elit non mi porta gravida at eget metus.
    +
    Malesuada porta
    +
    Etiam porta sem malesuada magna mollis euismod.
    +
    Felis euismod semper eget lacinia
    +
    Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus.
    +
    +
    +

    + {{_i}}Heads up!{{/i}} + {{_i}}Horizontal description lists will truncate terms that are too long to fit in the left column fix text-overflow. In narrower viewports, they will change to the default stacked layout.{{/i}} +

    +
    +
    +
    + + + + +
    + +
    +
    +

    Inline

    +

    Wrap inline snippets of code with <code>.

    +
    +{{_i}}For example, <code>section</code> should be wrapped as inline.{{/i}}
    +
    +
    +
    +

    Basic block

    +

    {{_i}}Use <pre> for multiple lines of code. Be sure to escape any angle brackets in the code for proper rendering.{{/i}}

    +
    +<p>{{_i}}Sample text here...{{/i}}</p>
    +
    +
    +<pre>
    +  &lt;p&gt;{{_i}}Sample text here...{{/i}}&lt;/p&gt;
    +</pre>
    +
    +

    {{_i}}Note: Be sure to keep code within <pre> tags as close to the left as possible; it will render all tabs.{{/i}}

    +

    {{_i}}You may optionally add the .pre-scrollable class which will set a max-height of 350px and provide a y-axis scrollbar.{{/i}}

    +
    +
    +

    Google Prettify

    +

    Take the same <pre> element and add two optional classes for enhanced rendering.

    +
    +<p>{{_i}}Sample text here...{{/i}}</p>
    +
    +
    +<pre class="prettyprint
    +     linenums">
    +  &lt;p&gt;{{_i}}Sample text here...{{/i}}&lt;/p&gt;
    +</pre>
    +
    +

    {{_i}}Download google-code-prettify and view the readme for how to use.{{/i}}

    +
    +
    +
    + + + + +
    + + +

    {{_i}}Table markup{{/i}}

    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    {{_i}}Tag{{/i}}{{_i}}Description{{/i}}
    + <table> + + {{_i}}Wrapping element for displaying data in a tabular format{{/i}} +
    + <thead> + + {{_i}}Container element for table header rows (<tr>) to label table columns{{/i}} +
    + <tbody> + + {{_i}}Container element for table rows (<tr>) in the body of the table{{/i}} +
    + <tr> + + {{_i}}Container element for a set of table cells (<td> or <th>) that appears on a single row{{/i}} +
    + <td> + + {{_i}}Default table cell{{/i}} +
    + <th> + + {{_i}}Special table cell for column (or row, depending on scope and placement) labels{{/i}}
    + {{_i}}Must be used within a <thead>{{/i}} +
    + <caption> + + {{_i}}Description or summary of what the table holds, especially useful for screen readers{{/i}} +
    +
    +
    +
    +<table>
    +  <thead>
    +    <tr>
    +      <th>…</th>
    +      <th>…</th>
    +    </tr>
    +  </thead>
    +  <tbody>
    +    <tr>
    +      <td>…</td>
    +      <td>…</td>
    +    </tr>
    +  </tbody>
    +</table>
    +
    +
    +
    + +

    {{_i}}Table options{{/i}}

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    {{_i}}Name{{/i}}{{_i}}Class{{/i}}{{_i}}Description{{/i}}
    {{_i}}Default{{/i}}{{_i}}None{{/i}}{{_i}}No styles, just columns and rows{{/i}}
    {{_i}}Basic{{/i}} + .table + {{_i}}Only horizontal lines between rows{{/i}}
    {{_i}}Bordered{{/i}} + .table-bordered + {{_i}}Rounds corners and adds outer border{{/i}}
    {{_i}}Zebra-stripe{{/i}} + .table-striped + {{_i}}Adds light gray background color to odd rows (1, 3, 5, etc){{/i}}
    {{_i}}Condensed{{/i}} + .table-condensed + {{_i}}Cuts vertical padding in half, from 8px to 4px, within all td and th elements{{/i}}
    + + +

    {{_i}}Example tables{{/i}}

    + +

    1. {{_i}}Default table styles{{/i}}

    +
    +
    +

    {{_i}}Tables are automatically styled with only a few borders to ensure readability and maintain structure. With 2.0, the .table class is required.{{/i}}

    +
    +<table class="table">
    +  …
    +</table>
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    #{{_i}}First Name{{/i}}{{_i}}Last Name{{/i}}{{_i}}Username{{/i}}
    1MarkOtto@mdo
    2JacobThornton@fat
    3Larrythe Bird@twitter
    +
    +
    + + +

    2. {{_i}}Striped table{{/i}}

    +
    +
    +

    {{_i}}Get a little fancy with your tables by adding zebra-striping—just add the .table-striped class.{{/i}}

    +

    {{_i}}Note: Striped tables use the :nth-child CSS selector and is not available in IE7-IE8.{{/i}}

    +
    +<table class="table table-striped">
    +  …
    +</table>
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    #{{_i}}First Name{{/i}}{{_i}}Last Name{{/i}}{{_i}}Username{{/i}}
    1MarkOtto@mdo
    2JacobThornton@fat
    3Larrythe Bird@twitter
    +
    +
    + + +

    3. {{_i}}Bordered table{{/i}}

    +
    +
    +

    {{_i}}Add borders around the entire table and rounded corners for aesthetic purposes.{{/i}}

    +
    +<table class="table table-bordered">
    +  …
    +</table>
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    #{{_i}}First Name{{/i}}{{_i}}Last Name{{/i}}{{_i}}Username{{/i}}
    1MarkOtto@mdo
    MarkOtto@TwBootstrap
    2JacobThornton@fat
    3Larry the Bird@twitter
    +
    +
    + + +

    4. {{_i}}Condensed table{{/i}}

    +
    +
    +

    {{_i}}Make your tables more compact by adding the .table-condensed class to cut table cell padding in half (from 8px to 4px).{{/i}}

    +
    +<table class="table table-condensed">
    +  …
    +</table>
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    #{{_i}}First Name{{/i}}{{_i}}Last Name{{/i}}{{_i}}Username{{/i}}
    1MarkOtto@mdo
    2JacobThornton@fat
    3Larry the Bird@twitter
    +
    +
    + + + +

    5. {{_i}}Combine them all!{{/i}}

    +
    +
    +

    {{_i}}Feel free to combine any of the table classes to achieve different looks by utilizing any of the available classes.{{/i}}

    +
    +<table class="table table-striped table-bordered table-condensed">
    +  ...
    +</table>
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    {{_i}}Full name{{/i}}
    #{{_i}}First Name{{/i}}{{_i}}Last Name{{/i}}{{_i}}Username{{/i}}
    1MarkOtto@mdo
    2JacobThornton@fat
    3Larry the Bird@twitter
    +
    +
    +
    + + + + +
    + +
    +
    +

    {{_i}}Flexible HTML and CSS{{/i}}

    +

    {{_i}}The best part about forms in Bootstrap is that all your inputs and controls look great no matter how you build them in your markup. No superfluous HTML is required, but we provide the patterns for those who require it.{{/i}}

    +

    {{_i}}More complicated layouts come with succinct and scalable classes for easy styling and event binding, so you're covered at every step.{{/i}}

    +
    +
    +

    {{_i}}Four layouts included{{/i}}

    +

    {{_i}}Bootstrap comes with support for four types of form layouts:{{/i}}

    +
      +
    • {{_i}}Vertical (default){{/i}}
    • +
    • {{_i}}Search{{/i}}
    • +
    • {{_i}}Inline{{/i}}
    • +
    • {{_i}}Horizontal{{/i}}
    • +
    +

    {{_i}}Different types of form layouts require some changes to markup, but the controls themselves remain and behave the same.{{/i}}

    +
    +
    +

    {{_i}}Control states and more{{/i}}

    +

    {{_i}}Bootstrap's forms include styles for all the base form controls like input, textarea, and select you'd expect. But it also comes with a number of custom components like appended and prepended inputs and support for lists of checkboxes.{{/i}}

    +

    {{_i}}States like error, warning, and success are included for each type of form control. Also included are styles for disabled controls.{{/i}}

    +
    +
    + +

    {{_i}}Four types of forms{{/i}}

    +

    {{_i}}Bootstrap provides simple markup and styles for four styles of common web forms.{{/i}}

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    {{_i}}Name{{/i}}{{_i}}Class{{/i}}{{_i}}Description{{/i}}
    {{_i}}Vertical (default){{/i}}.form-vertical ({{_i}}not required{{/i}}){{_i}}Stacked, left-aligned labels over controls{{/i}}
    {{_i}}Inline{{/i}}.form-inline{{_i}}Left-aligned label and inline-block controls for compact style{{/i}}
    {{_i}}Search{{/i}}.form-search{{_i}}Extra-rounded text input for a typical search aesthetic{{/i}}
    {{_i}}Horizontal{{/i}}.form-horizontal{{_i}}Float left, right-aligned labels on same line as controls{{/i}}
    + + +

    {{_i}}Example forms using just form controls, no extra markup{{/i}}

    +
    +
    +

    {{_i}}Basic form{{/i}}

    +

    {{_i}}Smart and lightweight defaults without extra markup.{{/i}}

    +
    + + +

    {{_i}}Example block-level help text here.{{/i}}

    + + +
    +
    +<form class="well">
    +  <label>{{_i}}Label name{{/i}}</label>
    +  <input type="text" class="span3" placeholder="{{_i}}Type something…{{/i}}">
    +  <span class="help-block">Example block-level help text here.</span>
    +  <label class="checkbox">
    +    <input type="checkbox"> {{_i}}Check me out{{/i}}
    +  </label>
    +  <button type="submit" class="btn">{{_i}}Submit{{/i}}</button>
    +</form>
    +
    +
    +
    +

    {{_i}}Search form{{/i}}

    +

    {{_i}}Add .form-search to the form and .search-query to the input.{{/i}}

    + +
    +<form class="well form-search">
    +  <input type="text" class="input-medium search-query">
    +  <button type="submit" class="btn">{{_i}}Search{{/i}}</button>
    +</form>
    +
    + +

    {{_i}}Inline form{{/i}}

    +

    {{_i}}Add .form-inline to finesse the vertical alignment and spacing of form controls.{{/i}}

    +
    + + + + +
    +
    +<form class="well form-inline">
    +  <input type="text" class="input-small" placeholder="{{_i}}Email{{/i}}">
    +  <input type="password" class="input-small" placeholder="{{_i}}Password{{/i}}">
    +  <label class="checkbox">
    +    <input type="checkbox"> {{_i}}Remember me{{/i}}
    +  </label>
    +  <button type="submit" class="btn">{{_i}}Sign in{{/i}}</button>
    +</form>
    +
    +
    +
    + +
    + +

    {{_i}}Horizontal forms{{/i}}

    +
    +
    +

    {{_i}}{{/i}}

    +

    {{_i}}Shown on the right are all the default form controls we support. Here's the bulleted list:{{/i}}

    +
      +
    • {{_i}}text inputs (text, password, email, etc){{/i}}
    • +
    • {{_i}}checkbox{{/i}}
    • +
    • {{_i}}radio{{/i}}
    • +
    • {{_i}}select{{/i}}
    • +
    • {{_i}}multiple select{{/i}}
    • +
    • {{_i}}file input{{/i}}
    • +
    • {{_i}}textarea{{/i}}
    • +
    +
    +
    +
    +
    +
    + +
    + +

    {{_i}}In addition to freeform text, any HTML5 text-based input appears like so.{{/i}}

    +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + + +
    +
    +
    +

    {{_i}}Example markup{{/i}}

    +

    {{_i}}Given the above example form layout, here's the markup associated with the first input and control group. The .control-group, .control-label, and .controls classes are all required for styling.{{/i}}

    +
    +<form class="form-horizontal">
    +  <fieldset>
    +    <legend>{{_i}}Legend text{{/i}}</legend>
    +    <div class="control-group">
    +      <label class="control-label" for="input01">{{_i}}Text input{{/i}}</label>
    +      <div class="controls">
    +        <input type="text" class="input-xlarge" id="input01">
    +        <p class="help-block">{{_i}}Supporting help text{{/i}}</p>
    +      </div>
    +    </div>
    +  </fieldset>
    +</form>
    +
    +
    +
    + +
    + +

    {{_i}}Form control states{{/i}}

    +
    +
    +

    {{_i}}Bootstrap features styles for browser-supported focused and disabled states. We remove the default Webkit outline and apply a box-shadow in its place for :focus.{{/i}}

    +
    +

    {{_i}}Form validation{{/i}}

    +

    {{_i}}It also includes validation styles for errors, warnings, and success. To use, add the error class to the surrounding .control-group.{{/i}}

    +
    +<fieldset
    +  class="control-group error">
    +  …
    +</fieldset>
    +
    +
    +
    +
    +
    +
    + +
    + +
    +
    +
    + +
    + Some value here +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + + {{_i}}Something may have gone wrong{{/i}} +
    +
    +
    + +
    + + {{_i}}Please correct the error{{/i}} +
    +
    +
    + +
    + + {{_i}}Woohoo!{{/i}} +
    +
    +
    + +
    + + {{_i}}Woohoo!{{/i}} +
    +
    +
    + + +
    +
    +
    +
    +
    + +
    + +

    {{_i}}Extending form controls{{/i}}

    +
    +
    +

    {{_i}}Prepend & append inputs{{/i}}

    +

    {{_i}}Input groups—with appended or prepended text—provide an easy way to give more context for your inputs. Great examples include the @ sign for Twitter usernames or $ for finances.{{/i}}

    +
    +

    {{_i}}Checkboxes and radios{{/i}}

    +

    {{_i}}Up to v1.4, Bootstrap required extra markup around checkboxes and radios to stack them. Now, it's a simple matter of repeating the <label class="checkbox"> that wraps the <input type="checkbox">.{{/i}}

    +

    {{_i}}Inline checkboxes and radios are also supported. Just add .inline to any .checkbox or .radio and you're done.{{/i}}

    +
    +

    {{_i}}Inline forms and append/prepend{{/i}}

    +

    {{_i}}To use prepend or append inputs in an inline form, be sure to place the .add-on and input on the same line, without spaces.{{/i}}

    +
    +

    {{_i}}Form help text{{/i}}

    +

    {{_i}}To add help text for your form inputs, include inline help text with <span class="help-inline"> or a help text block with <p class="help-block"> after the input element.{{/i}}

    +
    +
    +
    +
    +
    + +
    + + + + + + +

    {{_i}}Use the same .span* classes from the grid system for input sizes.{{/i}}

    +
    +
    +
    + +
    + + + +

    {{_i}}You may also use static classes that don't map to the grid, adapt to the responsive CSS styles, or account for varying types of controls (e.g., input vs. select).{{/i}}

    +
    +
    +
    + +
    +
    + @ +
    +

    {{_i}}Here's some help text{{/i}}

    +
    +
    +
    + +
    +
    + .00 +
    + {{_i}}Here's more help text{{/i}} +
    +
    +
    + +
    +
    + $.00 +
    +
    +
    +
    + +
    +
    + +
    +
    +
    +
    + +
    +
    + +
    +
    +
    +
    + +
    + + + +
    +
    +
    + +
    + + + +

    {{_i}}Note: Labels surround all the options for much larger click areas and a more usable form.{{/i}}

    +
    +
    +
    + +
    + + +
    +
    +
    + + +
    +
    +
    +
    +
    +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    {{_i}}Button{{/i}}{{_i}}class=""{{/i}}{{_i}}Description{{/i}}
    btn{{_i}}Standard gray button with gradient{{/i}}
    btn btn-primary{{_i}}Provides extra visual weight and identifies the primary action in a set of buttons{{/i}}
    btn btn-info{{_i}}Used as an alternative to the default styles{{/i}}
    btn btn-success{{_i}}Indicates a successful or positive action{{/i}}
    btn btn-warning{{_i}}Indicates caution should be taken with this action{{/i}}
    btn btn-danger{{_i}}Indicates a dangerous or potentially negative action{{/i}}
    btn btn-inverse{{_i}}Alternate dark gray button, not tied to a semantic action or use{{/i}}
    + +
    +
    +

    {{_i}}Buttons for actions{{/i}}

    +

    {{_i}}As a convention, buttons should only be used for actions while hyperlinks are to be used for objects. For instance, "Download" should be a button while "recent activity" should be a link.{{/i}}

    +

    {{_i}}Button styles can be applied to anything with the .btn class applied. However, typically you'll want to apply these to only <a> and <button> elements.{{/i}}

    +

    {{_i}}Cross browser compatibility{{/i}}

    +

    {{_i}}IE9 doesn't crop background gradients on rounded corners, so we remove it. Related, IE9 jankifies disabled button elements, rendering text gray with a nasty text-shadow that we cannot fix.{{/i}}

    +
    +
    +

    {{_i}}Multiple sizes{{/i}}

    +

    {{_i}}Fancy larger or smaller buttons? Add .btn-large, .btn-small, or .btn-mini for two additional sizes.{{/i}}

    +

    + + +

    +

    + + +

    +

    + + +

    +
    +

    {{_i}}Disabled state{{/i}}

    +

    {{_i}}For disabled buttons, add the .disabled class to links and the disabled attribute for <button> elements.{{/i}}

    +

    + {{_i}}Primary link{{/i}} + {{_i}}Link{{/i}} +

    +

    + + +

    +

    + {{_i}}Heads up!{{/i}} + {{_i}}We use .disabled as a utility class here, similar to the common .active class, so no prefix is required.{{/i}} +

    +
    +
    +

    {{_i}}One class, multiple tags{{/i}}

    +

    {{_i}}Use the .btn class on an <a>, <button>, or <input> element.{{/i}}

    +
    +{{_i}}Link{{/i}} + + + +
    +
    +<a class="btn" href="">{{_i}}Link{{/i}}</a>
    +<button class="btn" type="submit">
    +  {{_i}}Button{{/i}}
    +</button>
    +<input class="btn" type="button"
    +         value="{{_i}}Input{{/i}}">
    +<input class="btn" type="submit"
    +         value="{{_i}}Submit{{/i}}">
    +
    +

    {{_i}}As a best practice, try to match the element for you context to ensure matching cross-browser rendering. If you have an input, use an <input type="submit"> for your button.{{/i}}

    +
    +
    +
    + + + + +
    + +
    +
    +
      +
    • icon-glass
    • +
    • icon-music
    • +
    • icon-search
    • +
    • icon-envelope
    • +
    • icon-heart
    • +
    • icon-star
    • +
    • icon-star-empty
    • +
    • icon-user
    • +
    • icon-film
    • +
    • icon-th-large
    • +
    • icon-th
    • +
    • icon-th-list
    • +
    • icon-ok
    • +
    • icon-remove
    • +
    • icon-zoom-in
    • +
    • icon-zoom-out
    • +
    • icon-off
    • +
    • icon-signal
    • +
    • icon-cog
    • +
    • icon-trash
    • +
    • icon-home
    • +
    • icon-file
    • +
    • icon-time
    • +
    • icon-road
    • +
    • icon-download-alt
    • +
    • icon-download
    • +
    • icon-upload
    • +
    • icon-inbox
    • +
    • icon-play-circle
    • +
    • icon-repeat
    • +
    • icon-refresh
    • +
    • icon-list-alt
    • +
    • icon-lock
    • +
    • icon-flag
    • +
    • icon-headphones
    • +
    +
    +
    +
      +
    • icon-volume-off
    • +
    • icon-volume-down
    • +
    • icon-volume-up
    • +
    • icon-qrcode
    • +
    • icon-barcode
    • +
    • icon-tag
    • +
    • icon-tags
    • +
    • icon-book
    • +
    • icon-bookmark
    • +
    • icon-print
    • +
    • icon-camera
    • +
    • icon-font
    • +
    • icon-bold
    • +
    • icon-italic
    • +
    • icon-text-height
    • +
    • icon-text-width
    • +
    • icon-align-left
    • +
    • icon-align-center
    • +
    • icon-align-right
    • +
    • icon-align-justify
    • +
    • icon-list
    • +
    • icon-indent-left
    • +
    • icon-indent-right
    • +
    • icon-facetime-video
    • +
    • icon-picture
    • +
    • icon-pencil
    • +
    • icon-map-marker
    • +
    • icon-adjust
    • +
    • icon-tint
    • +
    • icon-edit
    • +
    • icon-share
    • +
    • icon-check
    • +
    • icon-move
    • +
    • icon-step-backward
    • +
    • icon-fast-backward
    • +
    +
    +
    +
      +
    • icon-backward
    • +
    • icon-play
    • +
    • icon-pause
    • +
    • icon-stop
    • +
    • icon-forward
    • +
    • icon-fast-forward
    • +
    • icon-step-forward
    • +
    • icon-eject
    • +
    • icon-chevron-left
    • +
    • icon-chevron-right
    • +
    • icon-plus-sign
    • +
    • icon-minus-sign
    • +
    • icon-remove-sign
    • +
    • icon-ok-sign
    • +
    • icon-question-sign
    • +
    • icon-info-sign
    • +
    • icon-screenshot
    • +
    • icon-remove-circle
    • +
    • icon-ok-circle
    • +
    • icon-ban-circle
    • +
    • icon-arrow-left
    • +
    • icon-arrow-right
    • +
    • icon-arrow-up
    • +
    • icon-arrow-down
    • +
    • icon-share-alt
    • +
    • icon-resize-full
    • +
    • icon-resize-small
    • +
    • icon-plus
    • +
    • icon-minus
    • +
    • icon-asterisk
    • +
    • icon-exclamation-sign
    • +
    • icon-gift
    • +
    • icon-leaf
    • +
    • icon-fire
    • +
    • icon-eye-open
    • +
    +
    +
    +
      +
    • icon-eye-close
    • +
    • icon-warning-sign
    • +
    • icon-plane
    • +
    • icon-calendar
    • +
    • icon-random
    • +
    • icon-comment
    • +
    • icon-magnet
    • +
    • icon-chevron-up
    • +
    • icon-chevron-down
    • +
    • icon-retweet
    • +
    • icon-shopping-cart
    • +
    • icon-folder-close
    • +
    • icon-folder-open
    • +
    • icon-resize-vertical
    • +
    • icon-resize-horizontal
    • +
    • icon-hdd
    • +
    • icon-bullhorn
    • +
    • icon-bell
    • +
    • icon-certificate
    • +
    • icon-thumbs-up
    • +
    • icon-thumbs-down
    • +
    • icon-hand-right
    • +
    • icon-hand-left
    • +
    • icon-hand-up
    • +
    • icon-hand-down
    • +
    • icon-circle-arrow-right
    • +
    • icon-circle-arrow-left
    • +
    • icon-circle-arrow-up
    • +
    • icon-circle-arrow-down
    • +
    • icon-globe
    • +
    • icon-wrench
    • +
    • icon-tasks
    • +
    • icon-filter
    • +
    • icon-briefcase
    • +
    • icon-fullscreen
    • +
    +
    +
    + +
    + +
    +
    +

    {{_i}}Built as a sprite{{/i}}

    +

    {{_i}}Instead of making every icon an extra request, we've compiled them into a sprite—a bunch of images in one file that uses CSS to position the images with background-position. This is the same method we use on Twitter.com and it has worked well for us.{{/i}}

    +

    {{_i}}All icons classes are prefixed with .icon- for proper namespacing and scoping, much like our other components. This will help avoid conflicts with other tools.{{/i}}

    +

    {{_i}}Glyphicons has granted us use of the Halflings set in our open-source toolkit so long as we provide a link and credit here in the docs. Please consider doing the same in your projects.{{/i}}

    +
    +
    +

    {{_i}}How to use{{/i}}

    +

    {{_i}}Bootstrap uses an <i> tag for all icons, but they have no case class—only a shared prefix. To use, place the following code just about anywhere:{{/i}}

    +
    +<i class="icon-search"></i>
    +
    +

    {{_i}}There are also styles available for inverted (white) icons, made ready with one extra class:{{/i}}

    +
    +<i class="icon-search icon-white"></i>
    +
    +

    {{_i}}There are 140 classes to choose from for your icons. Just add an <i> tag with the right classes and you're set. You can find the full list in sprites.less or right here in this document.{{/i}}

    +

    + {{_i}}Heads up!{{/i}} + {{_i}}When using beside strings of text, as in buttons or nav links, be sure to leave a space after the <i> tag for proper spacing.{{/i}} +

    +
    +
    +

    {{_i}}Use cases{{/i}}

    +

    {{_i}}Icons are great, but where would one use them? Here are a few ideas:{{/i}}

    +
      +
    • {{_i}}As visuals for your sidebar navigation{{/i}}
    • +
    • {{_i}}For a purely icon-driven navigation{{/i}}
    • +
    • {{_i}}For buttons to help convey the meaning of an action{{/i}}
    • +
    • {{_i}}With links to share context on a user's destination{{/i}}
    • +
    +

    {{_i}}Essentially, anywhere you can put an <i> tag, you can put an icon.{{/i}}

    +
    +
    + +

    {{_i}}Examples{{/i}}

    +

    {{_i}}Use them in buttons, button groups for a toolbar, navigation, or prepended form inputs.{{/i}}

    + +
    diff --git a/symposion_project/static/bootstrap/docs/templates/pages/components.mustache b/symposion_project/static/bootstrap/docs/templates/pages/components.mustache new file mode 100644 index 00000000..b1f85899 --- /dev/null +++ b/symposion_project/static/bootstrap/docs/templates/pages/components.mustache @@ -0,0 +1,1815 @@ + +
    +

    {{_i}}Components{{/i}}

    +

    {{_i}}Dozens of reusable components are built into Bootstrap to provide navigation, alerts, popovers, and much more.{{/i}}

    + +
    + + + + +
    + +
    +
    +

    {{_i}}Button groups{{/i}}

    +

    {{_i}}Use button groups to join multiple buttons together as one composite component. Build them with a series of <a> or <button> elements.{{/i}}

    +

    {{_i}}Best practices{{/i}}

    +

    {{_i}}We recommend the following guidelines for using button groups and toolbars:{{/i}}

    +
      +
    • {{_i}}Always use the same element in a single button group, <a> or <button>.{{/i}}
    • +
    • {{_i}}Don't mix buttons of different colors in the same button group.{{/i}}
    • +
    • {{_i}}Use icons in addition to or instead of text, but be sure include alt and title text where appropriate.{{/i}}
    • +
    +

    {{_i}}Related Button groups with dropdowns (see below) should be called out separately and always include a dropdown caret to indicate intended behavior.{{/i}}

    +
    +
    +

    {{_i}}Default example{{/i}}

    +

    {{_i}}Here's how the HTML looks for a standard button group built with anchor tag buttons:{{/i}}

    +
    +
    + + + +
    +
    +
    +<div class="btn-group">
    +  <button class="btn">1</button>
    +  <button class="btn">2</button>
    +  <button class="btn">3</button>
    +</div>
    +
    +

    {{_i}}Toolbar example{{/i}}

    +

    {{_i}}Combine sets of <div class="btn-group"> into a <div class="btn-toolbar"> for more complex components.{{/i}}

    +
    +
    + + + + +
    +
    + + + +
    +
    + +
    +
    +
    +<div class="btn-toolbar">
    +  <div class="btn-group">
    +    ...
    +  </div>
    +</div>
    +
    +
    +
    +

    {{_i}}Checkbox and radio flavors{{/i}}

    +

    {{_i}}Button groups can also function as radios, where only one button may be active, or checkboxes, where any number of buttons may be active. View the Javascript docs for that.{{/i}}

    +

    {{_i}}Get the javascript »{{/i}}

    +

    {{_i}}Dropdowns in button groups{{/i}}

    +

    {{_i}}Heads up!{{/i}} {{_i}}Buttons with dropdowns must be individually wrapped in their own .btn-group within a .btn-toolbar for proper rendering.{{/i}}

    +
    +
    +
    + + + + +
    + + +

    {{_i}}Button dropdowns{{/i}}

    +
    +
    +

    {{_i}}Overview and examples{{/i}}

    +

    {{_i}}Use any button to trigger a dropdown menu by placing it within a .btn-group and providing the proper menu markup.{{/i}}

    + + +
    +
    +

    {{_i}}Example markup{{/i}}

    +

    {{_i}}Similar to a button group, our markup uses regular button markup, but with a handful of additions to refine the style and support Bootstrap's dropdown jQuery plugin.{{/i}}

    +
    +<div class="btn-group">
    +  <a class="btn dropdown-toggle" data-toggle="dropdown" href="#">
    +    {{_i}}Action{{/i}}
    +    <span class="caret"></span>
    +  </a>
    +  <ul class="dropdown-menu">
    +    <!-- {{_i}}dropdown menu links{{/i}} -->
    +  </ul>
    +</div>
    +
    +
    +
    +
    +
    +

    {{_i}}Works with all button sizes{{/i}}

    +

    {{_i}}Button dropdowns work at any size. your button sizes to .btn-large, .btn-small, or .btn-mini.{{/i}}

    + +
    +
    +

    {{_i}}Requires javascript{{/i}}

    +

    {{_i}}Button dropdowns require the Bootstrap dropdown plugin to function.{{/i}}

    +

    {{_i}}In some cases—like mobile—dropdown menus will extend outside the viewport. You need to resolve the alignment manually or with custom javascript.{{/i}}

    +
    +
    +
    + +

    {{_i}}Split button dropdowns{{/i}}

    +
    +
    +

    {{_i}}Overview and examples{{/i}}

    +

    {{_i}}Building on the button group styles and markup, we can easily create a split button. Split buttons feature a standard action on the left and a dropdown toggle on the right with contextual links.{{/i}}

    + + + +

    {{_i}}Sizes{{/i}}

    +

    {{_i}}Utilize the extra button classes .btn-mini, .btn-small, or .btn-large for sizing.{{/i}}

    + + + +
    +<div class="btn-group">
    +  ...
    +  <ul class="dropdown-menu pull-right">
    +    <!-- {{_i}}dropdown menu links{{/i}} -->
    +  </ul>
    +</div>
    +
    +
    +
    +

    {{_i}}Example markup{{/i}}

    +

    {{_i}}We expand on the normal button dropdowns to provide a second button action that operates as a separate dropdown trigger.{{/i}}

    +
    +<div class="btn-group">
    +  <button class="btn">{{_i}}Action{{/i}}</button>
    +  <button class="btn dropdown-toggle" data-toggle="dropdown">
    +    <span class="caret"></span>
    +  </button>
    +  <ul class="dropdown-menu">
    +    <!-- {{_i}}dropdown menu links{{/i}} -->
    +  </ul>
    +</div>
    +
    +

    {{_i}}Dropup menus{{/i}}

    +

    {{_i}}Dropdown menus can also be toggled from the bottom up by adding a single class to the immediate parent of .dropdown-menu. It will flip the direction of the .caret and reposition the menu itself to move from the bottom up instead of top down.{{/i}}

    + +
    +<div class="btn-group dropup">
    +  <button class="btn">{{_i}}Dropup{{/i}}</button>
    +  <button class="btn dropdown-toggle" data-toggle="dropdown">
    +    <span class="caret"></span>
    +  </button>
    +  <ul class="dropdown-menu">
    +    <!-- {{_i}}dropdown menu links{{/i}} -->
    +  </ul>
    +</div>
    +
    + +
    +
    +
    + + + + + + + + + + + + + + + + + + + +
    + + +

    {{_i}}Multicon-page pagination{{/i}}

    +
    +
    +

    {{_i}}When to use{{/i}}

    +

    {{_i}}Ultra simplistic and minimally styled pagination inspired by Rdio, great for apps and search results. The large block is hard to miss, easily scalable, and provides large click areas.{{/i}}

    +

    {{_i}}Stateful page links{{/i}}

    +

    {{_i}}Links are customizable and work in a number of circumstances with the right class. .disabled for unclickable links and .active for current page.{{/i}}

    +

    {{_i}}Flexible alignment{{/i}}

    +

    {{_i}}Add either of two optional classes to change the alignment of pagination links: .pagination-centered and .pagination-right.{{/i}}

    +
    +
    +

    {{_i}}Examples{{/i}}

    +

    {{_i}}The default pagination component is flexible and works in a number of variations.{{/i}}

    + + + + +
    +
    +

    {{_i}}Markup{{/i}}

    +

    {{_i}}Wrapped in a <div>, pagination is just a <ul>.{{/i}}

    +
    +<div class="pagination">
    +  <ul>
    +    <li><a href="#">Prev</a></li>
    +    <li class="active">
    +      <a href="#">1</a>
    +    </li>
    +    <li><a href="#">2</a></li>
    +    <li><a href="#">3</a></li>
    +    <li><a href="#">4</a></li>
    +    <li><a href="#">Next</a></li>
    +  </ul>
    +</div>
    +
    +
    +
    + +

    {{_i}}Pager{{/i}} {{_i}}For quick previous and next links{{/i}}

    +
    +
    +

    {{_i}}About pager{{/i}}

    +

    {{_i}}The pager component is a set of links for simple pagination implementations with light markup and even lighter styles. It's great for simple sites like blogs or magazines.{{/i}}

    +

    {{_i}}Optional disabled state{{/i}}

    +

    {{_i}}Pager links also use the general .disabled class from the pagination.{{/i}}

    +
    +
    +

    {{_i}}Default example{{/i}}

    +

    {{_i}}By default, the pager centers links.{{/i}}

    + +
    +<ul class="pager">
    +  <li>
    +    <a href="#">{{_i}}Previous{{/i}}</a>
    +  </li>
    +  <li>
    +    <a href="#">{{_i}}Next{{/i}}</a>
    +  </li>
    +</ul>
    +
    +
    +
    +

    {{_i}}Aligned links{{/i}}

    +

    {{_i}}Alternatively, you can align each link to the sides:{{/i}}

    + +
    +<ul class="pager">
    +  <li class="previous">
    +    <a href="#">{{_i}}&larr; Older{{/i}}</a>
    +  </li>
    +  <li class="next">
    +    <a href="#">{{_i}}Newer &rarr;{{/i}}</a>
    +  </li>
    +</ul>
    +
    +
    +
    +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    {{_i}}Labels{{/i}}{{_i}}Markup{{/i}}
    + {{_i}}Default{{/i}} + + <span class="label">{{_i}}Default{{/i}}</span> +
    + {{_i}}Success{{/i}} + + <span class="label label-success">{{_i}}Success{{/i}}</span> +
    + {{_i}}Warning{{/i}} + + <span class="label label-warning">{{_i}}Warning{{/i}}</span> +
    + {{_i}}Important{{/i}} + + <span class="label label-important">{{_i}}Important{{/i}}</span> +
    + {{_i}}Info{{/i}} + + <span class="label label-info">{{_i}}Info{{/i}}</span> +
    + {{_i}}Inverse{{/i}} + + <span class="label label-inverse">{{_i}}Inverse{{/i}}</span> +
    +
    + + + + +
    + +
    +
    +

    About

    +

    {{_i}}Badges are small, simple components for displaying an indicator or count of some sort. They're commonly found in email clients like Mail.app or on mobile apps for push notifications.{{/i}}

    +
    +
    +

    Available classes

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    {{_i}}Name{{/i}}{{_i}}Example{{/i}}{{_i}}Markup{{/i}}
    + {{_i}}Default{{/i}} + + 1 + + <span class="badge">1</span> +
    + {{_i}}Success{{/i}} + + 2 + + <span class="badge badge-success">2</span> +
    + {{_i}}Warning{{/i}} + + 4 + + <span class="badge badge-warning">4</span> +
    + {{_i}}Important{{/i}} + + 6 + + <span class="badge badge-important">6</span> +
    + {{_i}}Info{{/i}} + + 8 + + <span class="badge badge-info">8</span> +
    + {{_i}}Inverse{{/i}} + + 10 + + <span class="badge badge-inverse">10</span> +
    +
    +
    +
    + + + + +
    + +

    {{_i}}Hero unit{{/i}}

    +
    +
    +

    {{_i}}Bootstrap provides a lightweight, flexible component called a hero unit to showcase content on your site. It works well on marketing and content-heavy sites.{{/i}}

    +

    {{_i}}Markup{{/i}}

    +

    {{_i}}Wrap your content in a div like so:{{/i}}

    +
    +<div class="hero-unit">
    +  <h1>{{_i}}Heading{{/i}}</h1>
    +  <p>{{_i}}Tagline{{/i}}</p>
    +  <p>
    +    <a class="btn btn-primary btn-large">
    +      {{_i}}Learn more{{/i}}
    +    </a>
    +  </p>
    +</div>
    +
    +
    +
    +
    +

    {{_i}}Hello, world!{{/i}}

    +

    {{_i}}This is a simple hero unit, a simple jumbotron-style component for calling extra attention to featured content or information.{{/i}}

    +

    {{_i}}Learn more{{/i}}

    +
    +
    +
    +

    {{_i}}Page header{{/i}}

    +
    +
    +

    {{_i}}A simple shell for an h1 to appropriately space out and segment sections of content on a page. It can utilize the h1's default small, element as well most other components (with additional styles).{{/i}}

    +
    +
    + +
    +<div class="page-header">
    +  <h1>{{_i}}Example page header{{/i}}</h1>
    +</div>
    +
    +
    +
    +
    + + + + +
    + + +
    +
    +

    {{_i}}Default thumbnails{{/i}}

    +

    {{_i}}By default, Bootstrap's thumbnails are designed to showcase linked images with minimal required markup.{{/i}}

    + +
    +
    +

    {{_i}}Highly customizable{{/i}}

    +

    {{_i}}With a bit of extra markup, it's possible to add any kind of HTML content like headings, paragraphs, or buttons into thumbnails.{{/i}}

    +
      +
    • +
      + +
      +
      {{_i}}Thumbnail label{{/i}}
      +

      Cras justo odio, dapibus ac facilisis in, egestas eget quam. Donec id elit non mi porta gravida at eget metus. Nullam id dolor id nibh ultricies vehicula ut id elit.

      +

      {{_i}}Action{{/i}} {{_i}}Action{{/i}}

      +
      +
      +
    • +
    • +
      + +
      +
      {{_i}}Thumbnail label{{/i}}
      +

      Cras justo odio, dapibus ac facilisis in, egestas eget quam. Donec id elit non mi porta gravida at eget metus. Nullam id dolor id nibh ultricies vehicula ut id elit.

      +

      {{_i}}Action{{/i}} {{_i}}Action{{/i}}

      +
      +
      +
    • +
    +
    +
    + +
    +
    +

    {{_i}}Why use thumbnails{{/i}}

    +

    {{_i}}Thumbnails (previously .media-grid up until v1.4) are great for grids of photos or videos, image search results, retail products, portfolios, and much more. They can be links or static content.{{/i}}

    +
    +
    +

    {{_i}}Simple, flexible markup{{/i}}

    +

    {{_i}}Thumbnail markup is simple—a ul with any number of li elements is all that is required. It's also super flexible, allowing for any type of content with just a bit more markup to wrap your contents.{{/i}}

    +
    +
    +

    {{_i}}Uses grid column sizes{{/i}}

    +

    {{_i}}Lastly, the thumbnails component uses existing grid system classes—like .span2 or .span3—for control of thumbnail dimensions.{{/i}}

    +
    +
    + +
    +
    +

    {{_i}}The markup{{/i}}

    +

    {{_i}}As mentioned previously, the required markup for thumbnails is light and straightforward. Here's a look at the default setup for linked images:{{/i}}

    +
    +<ul class="thumbnails">
    +  <li class="span3">
    +    <a href="#" class="thumbnail">
    +      <img src="http://placehold.it/260x180" alt="">
    +    </a>
    +  </li>
    +  ...
    +</ul>
    +
    +

    {{_i}}For custom HTML content in thumbnails, the markup changes slightly. To allow block level content anywhere, we swap the <a> for a <div> like so:{{/i}}

    +
    +<ul class="thumbnails">
    +  <li class="span3">
    +    <div class="thumbnail">
    +      <img src="http://placehold.it/260x180" alt="">
    +      <h5>{{_i}}Thumbnail label{{/i}}</h5>
    +      <p>{{_i}}Thumbnail caption right here...{{/i}}</p>
    +    </div>
    +  </li>
    +  ...
    +</ul>
    +
    +
    +
    +

    {{_i}}More examples{{/i}}

    +

    {{_i}}Explore all your options with the various grid classes available to you. You can also mix and match different sizes.{{/i}}

    + +
    +
    + +
    + + + + +
    + + +

    {{_i}}Lightweight defaults{{/i}}

    +
    +
    +

    {{_i}}Rewritten base class{{/i}}

    +

    {{_i}}With Bootstrap 2, we've simplified the base class: .alert instead of .alert-message. We've also reduced the minimum required markup—no <p> is required by default, just the outer <div>.{{/i}}

    +

    {{_i}}Single alert message{{/i}}

    +

    {{_i}}For a more durable component with less code, we've removed the differentiating look for block alerts, messages that come with more padding and typically more text. The class also has changed to .alert-block.{{/i}}

    +
    +

    {{_i}}Goes great with javascript{{/i}}

    +

    {{_i}}Bootstrap comes with a great jQuery plugin that supports alert messages, making dismissing them quick and easy.{{/i}}

    +

    {{_i}}Get the plugin »{{/i}}

    +
    +
    +

    {{_i}}Example alerts{{/i}}

    +

    {{_i}}Wrap your message and an optional close icon in a div with simple class.{{/i}}

    +
    + + {{_i}}Warning!{{/i}} {{_i}}Best check yo self, you're not looking too good.{{/i}} +
    +
    +<div class="alert">
    +  <button class="close" data-dismiss="alert">×</button>
    +  <strong>{{_i}}Warning!{{/i}}</strong> {{_i}}Best check yo self, you're not looking too good.{{/i}}
    +</div>
    +
    +

    {{_i}}Heads up!{{/i}} {{_i}}iOS devices require an href="#" for the dismissal of alerts. Be sure to include it and the data attribute for anchor close icons. Alternatively, you may use a <button> element with the data attribute, which we have opted to do for our docs. When using <button>, you must include type="button" or your forms may not submit.{{/i}}

    +

    {{_i}}Easily extend the standard alert message with two optional classes: .alert-block for more padding and text controls and .alert-heading for a matching heading.{{/i}}

    +
    + +

    {{_i}}Warning!{{/i}}

    +

    {{_i}}Best check yo self, you're not looking too good.{{/i}} Nulla vitae elit libero, a pharetra augue. Praesent commodo cursus magna, vel scelerisque nisl consectetur et.

    +
    +
    +<div class="alert alert-block">
    +  <a class="close" data-dismiss="alert" href="#">×</a>
    +  <h4 class="alert-heading">{{_i}}Warning!{{/i}}</h4>
    +  {{_i}}Best check yo self, you're not...{{/i}}
    +</div>
    +
    +
    +
    + +

    {{_i}}Contextual alternatives{{/i}} {{_i}}Add optional classes to change an alert's connotation{{/i}}

    +
    +
    +

    {{_i}}Error or danger{{/i}}

    +
    + + {{_i}}Oh snap!{{/i}} {{_i}}Change a few things up and try submitting again.{{/i}} +
    +
    +<div class="alert alert-error">
    +  ...
    +</div>
    +
    +
    +
    +

    {{_i}}Success{{/i}}

    +
    + + {{_i}}Well done!{{/i}} {{_i}}You successfully read this important alert message.{{/i}} +
    +
    +<div class="alert alert-success">
    +  ...
    +</div>
    +
    +
    +
    +

    {{_i}}Information{{/i}}

    +
    + + {{_i}}Heads up!{{/i}} {{_i}}This alert needs your attention, but it's not super important.{{/i}} +
    +
    +<div class="alert alert-info">
    +  ...
    +</div>
    +
    +
    +
    + +
    + + + + +
    + + +

    {{_i}}Examples and markup{{/i}}

    +
    +
    +

    {{_i}}Basic{{/i}}

    +

    {{_i}}Default progress bar with a vertical gradient.{{/i}}

    +
    +
    +
    +
    +<div class="progress">
    +  <div class="bar"
    +       style="width: 60%;"></div>
    +</div>
    +
    +
    +
    +

    {{_i}}Striped{{/i}}

    +

    {{_i}}Uses a gradient to create a striped effect (no IE).{{/i}}

    +
    +
    +
    +
    +<div class="progress progress-striped">
    +  <div class="bar"
    +       style="width: 20%;"></div>
    +</div>
    +
    +
    +
    +

    {{_i}}Animated{{/i}}

    +

    {{_i}}Takes the striped example and animates it (no IE).{{/i}}

    +
    +
    +
    +
    +<div class="progress progress-striped
    +     active">
    +  <div class="bar"
    +       style="width: 40%;"></div>
    +</div>
    +
    +
    +
    + +

    {{_i}}Options and browser support{{/i}}

    +
    +
    +

    {{_i}}Additional colors{{/i}}

    +

    {{_i}}Progress bars use some of the same button and alert classes for consistent styles.{{/i}}

    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +

    {{_i}}Striped bars{{/i}}

    +

    {{_i}}Similar to the solid colors, we have varied striped progress bars.{{/i}}

    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +

    {{_i}}Behavior{{/i}}

    +

    {{_i}}Progress bars use CSS3 transitions, so if you dynamically adjust the width via javascript, it will smoothly resize.{{/i}}

    +

    {{_i}}If you use the .active class, your .progress-striped progress bars will animate the stripes left to right.{{/i}}

    +
    +
    +

    {{_i}}Browser support{{/i}}

    +

    {{_i}}Progress bars use CSS3 gradients, transitions, and animations to achieve all their effects. These features are not supported in IE7-9 or older versions of Firefox.{{/i}}

    +

    {{_i}}Opera and IE do not support animations at this time.{{/i}}

    +
    +
    + +
    + + + + + + +
    + +
    +
    +

    {{_i}}Wells{{/i}}

    +

    {{_i}}Use the well as a simple effect on an element to give it an inset effect.{{/i}}

    +
    + {{_i}}Look, I'm in a well!{{/i}} +
    +
    +<div class="well">
    +  ...
    +</div>
    +
    +
    +
    +

    {{_i}}Close icon{{/i}}

    +

    {{_i}}Use the generic close icon for dismissing content like modals and alerts.{{/i}}

    +

    +
    <button class="close">&times;</button>
    +

    {{_i}}iOS devices require an href="#" for click events if you rather use an anchor.{{/i}}

    +
    <a class="close" href="#">&times;</a>
    +
    +
    +
    diff --git a/symposion_project/static/bootstrap/docs/templates/pages/download.mustache b/symposion_project/static/bootstrap/docs/templates/pages/download.mustache new file mode 100644 index 00000000..6efe7574 --- /dev/null +++ b/symposion_project/static/bootstrap/docs/templates/pages/download.mustache @@ -0,0 +1,338 @@ + +
    +

    {{_i}}Customize and download{{/i}}

    +

    {{_i}}Download the full repository or customize your entire Bootstrap build by selecting only the components, javascript plugins, and assets you need.{{/i}}

    + +
    + +
    + +
    +
    +

    {{_i}}Scaffolding{{/i}}

    + + + + +

    {{_i}}Base CSS{{/i}}

    + + + + + + + +
    +
    +

    {{_i}}Components{{/i}}

    + + + + + + + + + + +
    +
    +

    {{_i}}JS Components{{/i}}

    + + + + + + +
    +
    +

    {{_i}}Miscellaneous{{/i}}

    + + + + +

    {{_i}}Responsive{{/i}}

    + + + + + +
    +
    +
    + +
    + +
    +
    + + + + + + +
    +
    + + + + + + +
    +
    +

    {{_i}}Heads up!{{/i}}

    +

    {{_i}}All checked plugins will be compiled into a single file, bootstrap.js. All plugins require the latest version of jQuery to be included.{{/i}}

    +
    +
    +
    + + +
    + +
    +
    +

    {{_i}}Scaffolding{{/i}}

    + + + + + +

    {{_i}}Links{{/i}}

    + + + + +

    {{_i}}Colors{{/i}}

    + + + + + + + + + + + + + + + +

    {{_i}}Sprites{{/i}}

    + + + + + +
    +
    +

    {{_i}}Grid system{{/i}}

    + + + + + + +

    {{_i}}Fluid grid system{{/i}}

    + + + + + +

    {{_i}}Typography{{/i}}

    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +

    {{_i}}Tables{{/i}}

    + + + + + + + + + +

    {{_i}}Navbar{{/i}}

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    {{_i}}Dropdowns{{/i}}

    + + + + + + + + + + +
    +
    +

    {{_i}}Forms{{/i}}

    + + + + + + + + + + + + + + + + + + +

    {{_i}}Form states & alerts{{/i}}

    + + + + + + + + + + + + + + + + +
    +
    +
    + +
    + +
    + Customize and Download +

    {{_i}}What's included?{{/i}}

    +

    {{_i}}Downloads include compiled CSS, compiled and minified CSS, and compiled jQuery plugins, all nicely packed up into a zipball for your convenience.{{/i}}

    +
    +
    diff --git a/symposion_project/static/bootstrap/docs/templates/pages/examples.mustache b/symposion_project/static/bootstrap/docs/templates/pages/examples.mustache new file mode 100644 index 00000000..dee7d565 --- /dev/null +++ b/symposion_project/static/bootstrap/docs/templates/pages/examples.mustache @@ -0,0 +1,31 @@ + +
    +

    {{_i}}Bootstrap examples{{/i}}

    +

    {{_i}}We've included a few basic examples as starting points for your work with Bootstrap. We encourage folks to iterate on these examples and not simply use them as an end result.{{/i}}

    +
    + + +
      +
    • + + + +

      {{_i}}Basic marketing site{{/i}}

      +

      {{_i}}Featuring a hero unit for a primary message and three supporting elements.{{/i}}

      +
    • +
    • + + + +

      {{_i}}Fluid layout{{/i}}

      +

      {{_i}}Uses our new responsive, fluid grid system to create a seamless liquid layout.{{/i}}

      +
    • +
    • + + + +

      {{_i}}Starter template{{/i}}

      +

      {{_i}}A barebones HTML document with all the Bootstrap CSS and javascript included.{{/i}}

      +
    • +
    diff --git a/symposion_project/static/bootstrap/docs/templates/pages/index.mustache b/symposion_project/static/bootstrap/docs/templates/pages/index.mustache new file mode 100644 index 00000000..f14e95c1 --- /dev/null +++ b/symposion_project/static/bootstrap/docs/templates/pages/index.mustache @@ -0,0 +1,144 @@ + +
    +
    +

    {{_i}}Bootstrap, from Twitter{{/i}}

    +

    {{_i}}Simple and flexible HTML, CSS, and Javascript for popular user interface components and interactions.{{/i}}

    +

    + {{_i}}View project on GitHub{{/i}} + {{_i}}Download Bootstrap (v2.0.4){{/i}} +

    +
    + + +
    + +
    + +
    +

    {{_i}}Designed for everyone, everywhere.{{/i}}

    + +
    +
    + +

    {{_i}}Built for and by nerds{{/i}}

    +

    {{_i}}Like you, we love building awesome products on the web. We love it so much, we decided to help people just like us do it easier, better, and faster. Bootstrap is built for you.{{/i}}

    +
    +
    + +

    {{_i}}For all skill levels{{/i}}

    +

    {{_i}}Bootstrap is designed to help people of all skill levels—designer or developer, huge nerd or early beginner. Use it as a complete kit or use to start something more complex.{{/i}}

    +
    +
    + +

    {{_i}}Cross-everything{{/i}}

    +

    {{_i}}Originally built with only modern browsers in mind, Bootstrap has evolved to include support for all major browsers (even IE7!) and, with Bootstrap 2, tablets and smartphones, too.{{/i}}

    +
    +
    +
    +
    + +

    {{_i}}12-column grid{{/i}}

    +

    {{_i}}Grid systems aren't everything, but having a durable and flexible one at the core of your work can make development much simpler. Use our built-in grid classes or roll your own.{{/i}}

    +
    +
    + +

    {{_i}}Responsive design{{/i}}

    +

    {{_i}}With Bootstrap 2, we've gone fully responsive. Our components are scaled according to a range of resolutions and devices to provide a consistent experience, no matter what.{{/i}}

    +
    +
    + +

    {{_i}}Styleguide docs{{/i}}

    +

    {{_i}}Unlike other front-end toolkits, Bootstrap was designed first and foremost as a styleguide to document not only our features, but best practices and living, coded examples.{{/i}}

    +
    +
    +
    +
    + +

    {{_i}}Growing library{{/i}}

    +

    {{_i}}Despite being only 10kb (gzipped), Bootstrap is one of the most complete front-end toolkits out there with dozens of fully functional components ready to be put to use.{{/i}}

    +
    +
    + +

    {{_i}}Custom jQuery plugins{{/i}}

    +

    {{_i}}What good is an awesome design component without easy-to-use, proper, and extensible interactions? With Bootstrap, you get custom-built jQuery plugins to bring your projects to life.{{/i}}

    +
    +
    + +

    {{_i}}Built on LESS{{/i}}

    +

    {{_i}}Where vanilla CSS falters, LESS excels. Variables, nesting, operations, and mixins in LESS makes coding CSS faster and more efficient with minimal overhead.{{/i}}

    +
    +
    +
    +
    + +

    HTML5

    +

    {{_i}}Built to support new HTML5 elements and syntax.{{/i}}

    +
    +
    + +

    CSS3

    +

    {{_i}}Progressively enhanced components for ultimate style.{{/i}}

    +
    +
    + +

    {{_i}}Open-source{{/i}}

    +

    {{_i}}Built for and maintained by the community via GitHub.{{/i}}

    +
    +
    + +

    {{_i}}Made at Twitter{{/i}}

    +

    {{_i}}Brought to you by an experienced engineer and designer.{{/i}}

    +
    +
    + +
    + +

    {{_i}}Built with Bootstrap.{{/i}}

    + + + +
    \ No newline at end of file diff --git a/symposion_project/static/bootstrap/docs/templates/pages/javascript.mustache b/symposion_project/static/bootstrap/docs/templates/pages/javascript.mustache new file mode 100644 index 00000000..4ed30284 --- /dev/null +++ b/symposion_project/static/bootstrap/docs/templates/pages/javascript.mustache @@ -0,0 +1,1405 @@ + +
    +

    {{_i}}Javascript for Bootstrap{{/i}}

    +

    {{_i}}Bring Bootstrap's components to life—now with 12 custom jQuery plugins.{{/i}} +

    +
    + + + +
    + +
    +
    +

    {{_i}}Modals{{/i}}

    +

    {{_i}}A streamlined, but flexible, take on the traditional javascript modal plugin with only the minimum required functionality and smart defaults.{{/i}}

    +
    +
    +

    {{_i}}Dropdowns{{/i}}

    +

    {{_i}}Add dropdown menus to nearly anything in Bootstrap with this simple plugin. Bootstrap features full dropdown menu support on in the navbar, tabs, and pills.{{/i}}

    +
    +
    +

    {{_i}}Scrollspy{{/i}}

    +

    {{_i}}Use scrollspy to automatically update the links in your navbar to show the current active link based on scroll position.{{/i}}

    +
    +
    +

    {{_i}}Togglable tabs{{/i}}

    +

    {{_i}}Use this plugin to make tabs and pills more useful by allowing them to toggle through tabbable panes of local content.{{/i}}

    +
    +
    +
    +
    +

    {{_i}}Tooltips{{/i}}

    +

    {{_i}}A new take on the jQuery Tipsy plugin, Tooltips don't rely on images—they use CSS3 for animations and data-attributes for local title storage.{{/i}}

    +
    +
    +

    {{_i}}Popovers{{/i}} *

    +

    {{_i}}Add small overlays of content, like those on the iPad, to any element for housing secondary information.{{/i}}

    +

    * {{_i}}Requires Tooltips to be included{{/i}}

    +
    +
    +

    {{_i}}Alert messages{{/i}}

    +

    {{_i}}The alert plugin is a tiny class for adding close functionality to alerts.{{/i}}

    +
    +
    +

    {{_i}}Buttons{{/i}}

    +

    {{_i}}Do more with buttons. Control button states or create groups of buttons for more components like toolbars.{{/i}}

    +
    +
    +
    +
    +

    {{_i}}Collapse{{/i}}

    +

    {{_i}}Get base styles and flexible support for collapsible components like accordions and navigation.{{/i}}

    +
    +
    +

    {{_i}}Carousel{{/i}}

    +

    {{_i}}Create a merry-go-round of any content you wish to provide an interactive slideshow of content.{{/i}}

    +
    +
    +

    Typeahead

    +

    {{_i}}A basic, easily extended plugin for quickly creating elegant typeaheads with any form text input.{{/i}}

    +
    +
    +

    {{_i}}Transitions{{/i}} *

    +

    {{_i}}For simple transition effects, include bootstrap-transition.js once to slide in modals or fade out alerts.{{/i}}

    +

    * {{_i}}Required for animation in plugins{{/i}}

    +
    +
    +
    {{_i}}Heads up!{{/i}} {{_i}}All javascript plugins require the latest version of jQuery.{{/i}}
    +
    + + + + +
    + +
    +
    +

    {{_i}}About modals{{/i}}

    +

    {{_i}}A streamlined, but flexible, take on the traditional javascript modal plugin with only the minimum required functionality and smart defaults.{{/i}}

    + {{_i}}Download file{{/i}} +
    +
    +

    {{_i}}Static example{{/i}}

    +

    {{_i}}Below is a statically rendered modal.{{/i}}

    + + +

    {{_i}}Live demo{{/i}}

    +

    {{_i}}Toggle a modal via javascript by clicking the button below. It will slide down and fade in from the top of the page.{{/i}}

    + + + {{_i}}Launch demo modal{{/i}} + +
    + +

    {{_i}}Using bootstrap-modal{{/i}}

    +

    {{_i}}Call the modal via javascript:{{/i}}

    +
    $('#myModal').modal(options)
    +

    {{_i}}Options{{/i}}

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    {{_i}}Name{{/i}}{{_i}}type{{/i}}{{_i}}default{{/i}}{{_i}}description{{/i}}
    {{_i}}backdrop{{/i}}{{_i}}boolean{{/i}}{{_i}}true{{/i}}{{_i}}Includes a modal-backdrop element. Alternatively, specify static for a backdrop which doesn't close the modal on click.{{/i}}
    {{_i}}keyboard{{/i}}{{_i}}boolean{{/i}}{{_i}}true{{/i}}{{_i}}Closes the modal when escape key is pressed{{/i}}
    {{_i}}show{{/i}}{{_i}}boolean{{/i}}{{_i}}true{{/i}}{{_i}}Shows the modal when initialized.{{/i}}
    +

    {{_i}}Markup{{/i}}

    +

    {{_i}}You can activate modals on your page easily without having to write a single line of javascript. Just set data-toggle="modal" on a controller element with a data-target="#foo" or href="#foo" which corresponds to a modal element id, and when clicked, it will launch your modal.

    +

    Also, to add options to your modal instance, just include them as additional data attributes on either the control element or the modal markup itself.{{/i}}

    +
    +<a class="btn" data-toggle="modal" href="#myModal" >{{_i}}Launch Modal{{/i}}</a>
    +
    + +
    +<div class="modal hide" id="myModal">
    +  <div class="modal-header">
    +    <button type="button" class="close" data-dismiss="modal">×</button>
    +    <h3>Modal header</h3>
    +  </div>
    +  <div class="modal-body">
    +    <p>{{_i}}One fine body…{{/i}}</p>
    +  </div>
    +  <div class="modal-footer">
    +    <a href="#" class="btn" data-dismiss="modal">{{_i}}Close{{/i}}</a>
    +    <a href="#" class="btn btn-primary">{{_i}}Save changes{{/i}}</a>
    +  </div>
    +</div>
    +
    +
    + {{_i}}Heads up!{{/i}} {{_i}}If you want your modal to animate in and out, just add a .fade class to the .modal element (refer to the demo to see this in action) and include bootstrap-transition.js.{{/i}} +
    + Methods{{/i}} +

    .modal({{_i}}options{{/i}})

    +

    {{_i}}Activates your content as a modal. Accepts an optional options object.{{/i}}

    +
    +$('#myModal').modal({
    +  keyboard: false
    +})
    +

    .modal('toggle')

    +

    {{_i}}Manually toggles a modal.{{/i}}

    +
    $('#myModal').modal('toggle')
    +

    .modal('show')

    +

    {{_i}}Manually opens a modal.{{/i}}

    +
    $('#myModal').modal('show')
    +

    .modal('hide')

    +

    {{_i}}Manually hides a modal.{{/i}}

    +
    $('#myModal').modal('hide')
    +

    {{_i}}Events{{/i}}

    +

    {{_i}}Bootstrap's modal class exposes a few events for hooking into modal functionality.{{/i}}

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    {{_i}}Event{{/i}}{{_i}}Description{{/i}}
    {{_i}}show{{/i}}{{_i}}This event fires immediately when the show instance method is called.{{/i}}
    {{_i}}shown{{/i}}{{_i}}This event is fired when the modal has been made visible to the user (will wait for css transitions to complete).{{/i}}
    {{_i}}hide{{/i}}{{_i}}This event is fired immediately when the hide instance method has been called.{{/i}}
    {{_i}}hidden{{/i}}{{_i}}This event is fired when the modal has finished being hidden from the user (will wait for css transitions to complete).{{/i}}
    + +
    +$('#myModal').on('hidden', function () {
    +  // {{_i}}do something…{{/i}}
    +})
    +
    +
    +
    + + + + + + + + + +
    + +
    +
    +

    {{_i}}The ScrollSpy plugin is for automatically updating nav targets based on scroll position.{{/i}}

    + {{_i}}Download file{{/i}} +
    +
    +

    {{_i}}Example navbar with scrollspy{{/i}}

    +

    {{_i}}Scroll the area below and watch the navigation update. The dropdown sub items will be highlighted as well. Try it!{{/i}}

    + +
    +

    @fat

    +

    + Ad leggings keytar, brunch id art party dolor labore. Pitchfork yr enim lo-fi before they sold out qui. Tumblr farm-to-table bicycle rights whatever. Anim keffiyeh carles cardigan. Velit seitan mcsweeney's photo booth 3 wolf moon irure. Cosby sweater lomo jean shorts, williamsburg hoodie minim qui you probably haven't heard of them et cardigan trust fund culpa biodiesel wes anderson aesthetic. Nihil tattooed accusamus, cred irony biodiesel keffiyeh artisan ullamco consequat. +

    +

    @mdo

    +

    + Veniam marfa mustache skateboard, adipisicing fugiat velit pitchfork beard. Freegan beard aliqua cupidatat mcsweeney's vero. Cupidatat four loko nisi, ea helvetica nulla carles. Tattooed cosby sweater food truck, mcsweeney's quis non freegan vinyl. Lo-fi wes anderson +1 sartorial. Carles non aesthetic exercitation quis gentrify. Brooklyn adipisicing craft beer vice keytar deserunt. +

    +

    one

    +

    + Occaecat commodo aliqua delectus. Fap craft beer deserunt skateboard ea. Lomo bicycle rights adipisicing banh mi, velit ea sunt next level locavore single-origin coffee in magna veniam. High life id vinyl, echo park consequat quis aliquip banh mi pitchfork. Vero VHS est adipisicing. Consectetur nisi DIY minim messenger bag. Cred ex in, sustainable delectus consectetur fanny pack iphone. +

    +

    two

    +

    + In incididunt echo park, officia deserunt mcsweeney's proident master cleanse thundercats sapiente veniam. Excepteur VHS elit, proident shoreditch +1 biodiesel laborum craft beer. Single-origin coffee wayfarers irure four loko, cupidatat terry richardson master cleanse. Assumenda you probably haven't heard of them art party fanny pack, tattooed nulla cardigan tempor ad. Proident wolf nesciunt sartorial keffiyeh eu banh mi sustainable. Elit wolf voluptate, lo-fi ea portland before they sold out four loko. Locavore enim nostrud mlkshk brooklyn nesciunt. +

    +

    three

    +

    + Ad leggings keytar, brunch id art party dolor labore. Pitchfork yr enim lo-fi before they sold out qui. Tumblr farm-to-table bicycle rights whatever. Anim keffiyeh carles cardigan. Velit seitan mcsweeney's photo booth 3 wolf moon irure. Cosby sweater lomo jean shorts, williamsburg hoodie minim qui you probably haven't heard of them et cardigan trust fund culpa biodiesel wes anderson aesthetic. Nihil tattooed accusamus, cred irony biodiesel keffiyeh artisan ullamco consequat. +

    +

    Keytar twee blog, culpa messenger bag marfa whatever delectus food truck. Sapiente synth id assumenda. Locavore sed helvetica cliche irony, thundercats you probably haven't heard of them consequat hoodie gluten-free lo-fi fap aliquip. Labore elit placeat before they sold out, terry richardson proident brunch nesciunt quis cosby sweater pariatur keffiyeh ut helvetica artisan. Cardigan craft beer seitan readymade velit. VHS chambray laboris tempor veniam. Anim mollit minim commodo ullamco thundercats. +

    +
    +
    +

    {{_i}}Using bootstrap-scrollspy.js{{/i}}

    +

    {{_i}}Call the scrollspy via javascript:{{/i}}

    +
    $('#navbar').scrollspy()
    +

    {{_i}}Markup{{/i}}

    +

    {{_i}}To easily add scrollspy behavior to your topbar navigation, just add data-spy="scroll" to the element you want to spy on (most typically this would be the body).{{/i}}

    +
    <body data-spy="scroll" >...</body>
    +
    + {{_i}}Heads up!{{/i}} + {{_i}}Navbar links must have resolvable id targets. For example, a <a href="#home">home</a> must correspond to something in the dom like <div id="home"></div>.{{/i}} +
    +

    {{_i}}Methods{{/i}}

    +

    .scrollspy('refresh')

    +

    {{_i}}When using scrollspy in conjunction with adding or removing of elements from the DOM, you'll need to call the refresh method like so:{{/i}}

    +
    +$('[data-spy="scroll"]').each(function () {
    +  var $spy = $(this).scrollspy('refresh')
    +});
    +
    +

    {{_i}}Options{{/i}}

    + + + + + + + + + + + + + + + + + +
    {{_i}}Name{{/i}}{{_i}}type{{/i}}{{_i}}default{{/i}}{{_i}}description{{/i}}
    {{_i}}offset{{/i}}{{_i}}number{{/i}}{{_i}}10{{/i}}{{_i}}Pixels to offset from top when calculating position of scroll.{{/i}}
    +

    {{_i}}Events{{/i}}

    + + + + + + + + + + + + + +
    {{_i}}Event{{/i}}{{_i}}Description{{/i}}
    {{_i}}activate{{/i}}{{_i}}This event fires whenever a new item becomes activated by the scrollspy.{{/i}}
    +
    +
    +
    + + + + +
    + +
    +
    +

    {{_i}}This plugin adds quick, dynamic tab and pill functionality for transitioning through local content.{{/i}}

    + {{_i}}Download file{{/i}} +
    +
    +

    {{_i}}Example tabs{{/i}}

    +

    {{_i}}Click the tabs below to toggle between hidden panes, even via dropdown menus.{{/i}}

    + +
    +
    +

    Raw denim you probably haven't heard of them jean shorts Austin. Nesciunt tofu stumptown aliqua, retro synth master cleanse. Mustache cliche tempor, williamsburg carles vegan helvetica. Reprehenderit butcher retro keffiyeh dreamcatcher synth. Cosby sweater eu banh mi, qui irure terry richardson ex squid. Aliquip placeat salvia cillum iphone. Seitan aliquip quis cardigan american apparel, butcher voluptate nisi qui.

    +
    +
    +

    Food truck fixie locavore, accusamus mcsweeney's marfa nulla single-origin coffee squid. Exercitation +1 labore velit, blog sartorial PBR leggings next level wes anderson artisan four loko farm-to-table craft beer twee. Qui photo booth letterpress, commodo enim craft beer mlkshk aliquip jean shorts ullamco ad vinyl cillum PBR. Homo nostrud organic, assumenda labore aesthetic magna delectus mollit. Keytar helvetica VHS salvia yr, vero magna velit sapiente labore stumptown. Vegan fanny pack odio cillum wes anderson 8-bit, sustainable jean shorts beard ut DIY ethical culpa terry richardson biodiesel. Art party scenester stumptown, tumblr butcher vero sint qui sapiente accusamus tattooed echo park.

    +
    + + +
    +
    +

    {{_i}}Using bootstrap-tab.js{{/i}}

    +

    {{_i}}Enable tabbable tabs via javascript (each tab needs to be activated individually):{{/i}}

    +
    +$('#myTab a').click(function (e) {
    +  e.preventDefault();
    +  $(this).tab('show');
    +})
    +

    {{_i}}You can activate individual tabs in several ways:{{/i}}

    +
    +$('#myTab a[href="#profile"]').tab('show'); // Select tab by name
    +$('#myTab a:first').tab('show'); // Select first tab
    +$('#myTab a:last').tab('show'); // Select last tab
    +$('#myTab li:eq(2) a').tab('show'); // Select third tab (0-indexed)
    +
    +

    {{_i}}Markup{{/i}}

    +

    {{_i}}You can activate a tab or pill navigation without writing any javascript by simply specifying data-toggle="tab" or data-toggle="pill" on an element. Adding the nav and nav-tabs classes to the tab ul will apply the bootstrap tab styling.{{/i}}

    +
    +<ul class="nav nav-tabs">
    +  <li><a href="#home" data-toggle="tab">{{_i}}Home{{/i}}</a></li>
    +  <li><a href="#profile" data-toggle="tab">{{_i}}Profile{{/i}}</a></li>
    +  <li><a href="#messages" data-toggle="tab">{{_i}}Messages{{/i}}</a></li>
    +  <li><a href="#settings" data-toggle="tab">{{_i}}Settings{{/i}}</a></li>
    +</ul>
    +

    {{_i}}Methods{{/i}}

    +

    $().tab

    +

    + {{_i}}Activates a tab element and content container. Tab should have either a data-target or an href targeting a container node in the DOM.{{/i}} +

    +
    +<ul class="nav nav-tabs" id="myTab">
    +  <li class="active"><a href="#home">{{_i}}Home{{/i}}</a></li>
    +  <li><a href="#profile">{{_i}}Profile{{/i}}</a></li>
    +  <li><a href="#messages">{{_i}}Messages{{/i}}</a></li>
    +  <li><a href="#settings">{{_i}}Settings{{/i}}</a></li>
    +</ul>
    +
    +<div class="tab-content">
    +  <div class="tab-pane active" id="home">...</div>
    +  <div class="tab-pane" id="profile">...</div>
    +  <div class="tab-pane" id="messages">...</div>
    +  <div class="tab-pane" id="settings">...</div>
    +</div>
    +
    +<script>
    +  $(function () {
    +    $('#myTab a:last').tab('show');
    +  })
    +</script>
    +

    {{_i}}Events{{/i}}

    + + + + + + + + + + + + + + + + + +
    {{_i}}Event{{/i}}{{_i}}Description{{/i}}
    {{_i}}show{{/i}}{{_i}}This event fires on tab show, but before the new tab has been shown. Use event.target and event.relatedTarget to target the active tab and the previous active tab (if available) respectively.{{/i}}
    {{_i}}shown{{/i}}{{_i}}This event fires on tab show after a tab has been shown. Use event.target and event.relatedTarget to target the active tab and the previous active tab (if available) respectively.{{/i}}
    + +
    +$('a[data-toggle="tab"]').on('shown', function (e) {
    +  e.target // activated tab
    +  e.relatedTarget // previous tab
    +})
    +
    +
    +
    + + + +
    + +
    +
    +

    {{_i}}About Tooltips{{/i}}

    +

    {{_i}}Inspired by the excellent jQuery.tipsy plugin written by Jason Frame; Tooltips are an updated version, which don't rely on images, use css3 for animations, and data-attributes for local title storage.{{/i}}

    + {{_i}}Download file{{/i}} +
    +
    +

    {{_i}}Example use of Tooltips{{/i}}

    +

    {{_i}}Hover over the links below to see tooltips:{{/i}}

    +
    +

    {{_i}}Tight pants next level keffiyeh you probably haven't heard of them. Photo booth beard raw denim letterpress vegan messenger bag stumptown. Farm-to-table seitan, mcsweeney's fixie sustainable quinoa 8-bit american apparel have a terry richardson vinyl chambray. Beard stumptown, cardigans banh mi lomo thundercats. Tofu biodiesel williamsburg marfa, four loko mcsweeney's cleanse vegan chambray. A really ironic artisan whatever keytar, scenester farm-to-table banksy Austin twitter handle freegan cred raw denim single-origin coffee viral.{{/i}} +

    +
    +
    +

    {{_i}}Using{{/i}} bootstrap-tooltip.js

    +

    {{_i}}Trigger the tooltip via javascript:{{/i}}

    +
    $('#example').tooltip({{_i}}options{{/i}})
    +

    {{_i}}Options{{/i}}

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    {{_i}}Name{{/i}}{{_i}}type{{/i}}{{_i}}default{{/i}}{{_i}}description{{/i}}
    {{_i}}animation{{/i}}{{_i}}boolean{{/i}}true{{_i}}apply a css fade transition to the tooltip{{/i}}
    {{_i}}placement{{/i}}{{_i}}string|function{{/i}}'top'{{_i}}how to position the tooltip{{/i}} - top | bottom | left | right
    {{_i}}selector{{/i}}{{_i}}string{{/i}}false{{_i}}If a selector is provided, tooltip objects will be delegated to the specified targets.{{/i}}
    {{_i}}title{{/i}}{{_i}}string | function{{/i}}''{{_i}}default title value if `title` tag isn't present{{/i}}
    {{_i}}trigger{{/i}}{{_i}}string{{/i}}'hover'{{_i}}how tooltip is triggered{{/i}} - hover | focus | manual
    {{_i}}delay{{/i}}{{_i}}number | object{{/i}}0 +

    {{_i}}delay showing and hiding the tooltip (ms) - does not apply to manual trigger type{{/i}}

    +

    {{_i}}If a number is supplied, delay is applied to both hide/show{{/i}}

    +

    {{_i}}Object structure is: delay: { show: 500, hide: 100 }{{/i}}

    +
    +
    + {{_i}}Heads up!{{/i}} + {{_i}}Options for individual tooltips can alternatively be specified through the use of data attributes.{{/i}} +
    +

    {{_i}}Markup{{/i}}

    +

    {{_i}}For performance reasons, the Tooltip and Popover data-apis are opt in. If you would like to use them just specify a selector option.{{/i}}

    +
    +<a href="#" rel="tooltip" title="{{_i}}first tooltip{{/i}}">{{_i}}hover over me{{/i}}</a>
    +
    +

    {{_i}}Methods{{/i}}

    +

    $().tooltip({{_i}}options{{/i}})

    +

    {{_i}}Attaches a tooltip handler to an element collection.{{/i}}

    +

    .tooltip('show')

    +

    {{_i}}Reveals an element's tooltip.{{/i}}

    +
    $('#element').tooltip('show')
    +

    .tooltip('hide')

    +

    {{_i}}Hides an element's tooltip.{{/i}}

    +
    $('#element').tooltip('hide')
    +

    .tooltip('toggle')

    +

    {{_i}}Toggles an element's tooltip.{{/i}}

    +
    $('#element').tooltip('toggle')
    +
    +
    +
    + + + + +
    + +
    +
    +

    {{_i}}About popovers{{/i}}

    +

    {{_i}}Add small overlays of content, like those on the iPad, to any element for housing secondary information.{{/i}}

    +

    * {{_i}}Requires Tooltip to be included{{/i}}

    + {{_i}}Download file{{/i}} +
    +
    +

    {{_i}}Example hover popover{{/i}}

    +

    {{_i}}Hover over the button to trigger the popover.{{/i}}

    + +
    +

    {{_i}}Using bootstrap-popover.js{{/i}}

    +

    {{_i}}Enable popovers via javascript:{{/i}}

    +
    $('#example').popover({{_i}}options{{/i}})
    +

    {{_i}}Options{{/i}}

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    {{_i}}Name{{/i}}{{_i}}type{{/i}}{{_i}}default{{/i}}{{_i}}description{{/i}}
    {{_i}}animation{{/i}}{{_i}}boolean{{/i}}true{{_i}}apply a css fade transition to the tooltip{{/i}}
    {{_i}}placement{{/i}}{{_i}}string|function{{/i}}'right'{{_i}}how to position the popover{{/i}} - top | bottom | left | right
    {{_i}}selector{{/i}}{{_i}}string{{/i}}false{{_i}}if a selector is provided, tooltip objects will be delegated to the specified targets{{/i}}
    {{_i}}trigger{{/i}}{{_i}}string{{/i}}'hover'{{_i}}how tooltip is triggered{{/i}} - hover | focus | manual
    {{_i}}title{{/i}}{{_i}}string | function{{/i}}''{{_i}}default title value if `title` attribute isn't present{{/i}}
    {{_i}}content{{/i}}{{_i}}string | function{{/i}}''{{_i}}default content value if `data-content` attribute isn't present{{/i}}
    {{_i}}delay{{/i}}{{_i}}number | object{{/i}}0 +

    {{_i}}delay showing and hiding the popover (ms) - does not apply to manual trigger type{{/i}}

    +

    {{_i}}If a number is supplied, delay is applied to both hide/show{{/i}}

    +

    {{_i}}Object structure is: delay: { show: 500, hide: 100 }{{/i}}

    +
    +
    + {{_i}}Heads up!{{/i}} + {{_i}}Options for individual popovers can alternatively be specified through the use of data attributes.{{/i}} +
    +

    {{_i}}Markup{{/i}}

    +

    + {{_i}}For performance reasons, the Tooltip and Popover data-apis are opt in. If you would like to use them just specify a selector option.{{/i}} +

    +

    {{_i}}Methods{{/i}}

    +

    $().popover({{_i}}options{{/i}})

    +

    {{_i}}Initializes popovers for an element collection.{{/i}}

    +

    .popover('show')

    +

    {{_i}}Reveals an elements popover.{{/i}}

    +
    $('#element').popover('show')
    +

    .popover('hide')

    +

    {{_i}}Hides an elements popover.{{/i}}

    +
    $('#element').popover('hide')
    +

    .popover('toggle')

    +

    {{_i}}Toggles an elements popover.{{/i}}

    +
    $('#element').popover('toggle')
    +
    +
    +
    + + + + +
    + +
    +
    +

    {{_i}}About alerts{{/i}}

    +

    {{_i}}The alert plugin is a tiny class for adding close functionality to alerts.{{/i}}

    + {{_i}}Download{{/i}} +
    +
    +

    {{_i}}Example alerts{{/i}}

    +

    {{_i}}The alerts plugin works on regular alert messages, and block messages.{{/i}}

    +
    + + {{_i}}Holy guacamole!{{/i}} {{_i}}Best check yo self, you're not looking too good.{{/i}} +
    +
    + +

    {{_i}}Oh snap! You got an error!{{/i}}

    +

    {{_i}}Change this and that and try again. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Cras mattis consectetur purus sit amet fermentum.{{/i}}

    +

    + {{_i}}Take this action{{/i}} {{_i}}Or do this{{/i}} +

    +
    +
    +

    {{_i}}Using bootstrap-alert.js{{/i}}

    +

    {{_i}}Enable dismissal of an alert via javascript:{{/i}}

    +
    $(".alert").alert()
    +

    {{_i}}Markup{{/i}}

    +

    {{_i}}Just add data-dismiss="alert" to your close button to automatically give an alert close functionality.{{/i}}

    +
    <a class="close" data-dismiss="alert" href="#">&times;</a>
    +

    {{_i}}Methods{{/i}}

    +

    $().alert()

    +

    {{_i}}Wraps all alerts with close functionality. To have your alerts animate out when closed, make sure they have the .fade and .in class already applied to them.{{/i}}

    +

    .alert('close')

    +

    {{_i}}Closes an alert.{{/i}}

    +
    $(".alert").alert('close')
    +

    {{_i}}Events{{/i}}

    +

    {{_i}}Bootstrap's alert class exposes a few events for hooking into alert functionality.{{/i}}

    + + + + + + + + + + + + + + + + + +
    {{_i}}Event{{/i}}{{_i}}Description{{/i}}
    {{_i}}close{{/i}}{{_i}}This event fires immediately when the close instance method is called.{{/i}}
    {{_i}}closed{{/i}}{{_i}}This event is fired when the alert has been closed (will wait for css transitions to complete).{{/i}}
    +
    +$('#my-alert').bind('closed', function () {
    +  // {{_i}}do something…{{/i}}
    +})
    +
    +
    +
    + + + + +
    + +
    +
    +

    {{_i}}About{{/i}}

    +

    {{_i}}Do more with buttons. Control button states or create groups of buttons for more components like toolbars.{{/i}}

    + {{_i}}Download file{{/i}} +
    +
    +

    {{_i}}Example uses{{/i}}

    +

    {{_i}}Use the buttons plugin for states and toggles.{{/i}}

    + + + + + + + + + + + + + + + + + + + +
    {{_i}}Stateful{{/i}} + +
    {{_i}}Single toggle{{/i}} + +
    {{_i}}Checkbox{{/i}} +
    + + + +
    +
    {{_i}}Radio{{/i}} +
    + + + +
    +
    +
    +

    {{_i}}Using bootstrap-button.js{{/i}}

    +

    {{_i}}Enable buttons via javascript:{{/i}}

    +
    $('.nav-tabs').button()
    +

    {{_i}}Markup{{/i}}

    +

    {{_i}}Data attributes are integral to the button plugin. Check out the example code below for the various markup types.{{/i}}

    +
    +<!-- {{_i}}Add data-toggle="button" to activate toggling on a single button{{/i}} -->
    +<button class="btn" data-toggle="button">Single Toggle</button>
    +
    +<!-- {{_i}}Add data-toggle="buttons-checkbox" for checkbox style toggling on btn-group{{/i}} -->
    +<div class="btn-group" data-toggle="buttons-checkbox">
    +  <button class="btn">Left</button>
    +  <button class="btn">Middle</button>
    +  <button class="btn">Right</button>
    +</div>
    +
    +<!-- {{_i}}Add data-toggle="buttons-radio" for radio style toggling on btn-group{{/i}} -->
    +<div class="btn-group" data-toggle="buttons-radio">
    +  <button class="btn">Left</button>
    +  <button class="btn">Middle</button>
    +  <button class="btn">Right</button>
    +</div>
    +
    +

    {{_i}}Methods{{/i}}

    +

    $().button('toggle')

    +

    {{_i}}Toggles push state. Gives the button the appearance that it has been activated.{{/i}}

    +
    + {{_i}}Heads up!{{/i}} + {{_i}}You can enable auto toggling of a button by using the data-toggle attribute.{{/i}} +
    +
    <button class="btn" data-toggle="button" >…</button>
    +

    $().button('loading')

    +

    {{_i}}Sets button state to loading - disables button and swaps text to loading text. Loading text should be defined on the button element using the data attribute data-loading-text.{{/i}} +

    +
    <button class="btn" data-loading-text="loading stuff..." >...</button>
    +
    + {{_i}}Heads up!{{/i}} + {{_i}}Firefox persists the disabled state across page loads. A workaround for this is to use autocomplete="off".{{/i}} +
    +

    $().button('reset')

    +

    {{_i}}Resets button state - swaps text to original text.{{/i}}

    +

    $().button(string)

    +

    {{_i}}Resets button state - swaps text to any data defined text state.{{/i}}

    +
    <button class="btn" data-complete-text="finished!" >...</button>
    +<script>
    +  $('.btn').button('complete')
    +</script>
    +
    +
    +
    + + + + +
    + +
    +
    +

    {{_i}}About{{/i}}

    +

    {{_i}}Get base styles and flexible support for collapsible components like accordions and navigation.{{/i}}

    + {{_i}}Download file{{/i}} +

    * {{_i}}Requires the Transitions plugin to be included.{{/i}}

    +
    +
    +

    {{_i}}Example accordion{{/i}}

    +

    {{_i}}Using the collapse plugin, we built a simple accordion style widget:{{/i}}

    + +
    +
    + +
    +
    + Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS. +
    +
    +
    +
    + +
    +
    + Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS. +
    +
    +
    +
    + +
    +
    + Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS. +
    +
    +
    +
    + + +
    +

    {{_i}}Using bootstrap-collapse.js{{/i}}

    +

    {{_i}}Enable via javascript:{{/i}}

    +
    $(".collapse").collapse()
    +

    {{_i}}Options{{/i}}

    + + + + + + + + + + + + + + + + + + + + + + + +
    {{_i}}Name{{/i}}{{_i}}type{{/i}}{{_i}}default{{/i}}{{_i}}description{{/i}}
    {{_i}}parent{{/i}}{{_i}}selector{{/i}}false{{_i}}If selector then all collapsible elements under the specified parent will be closed when this collapsible item is shown. (similar to traditional accordion behavior){{/i}}
    {{_i}}toggle{{/i}}{{_i}}boolean{{/i}}true{{_i}}Toggles the collapsible element on invocation{{/i}}
    +

    {{_i}}Markup{{/i}}

    +

    {{_i}}Just add data-toggle="collapse" and a data-target to element to automatically assign control of a collapsible element. The data-target attribute accepts a css selector to apply the collapse to. Be sure to add the class collapse to the collapsible element. If you'd like it to default open, add the additional class in.{{/i}}

    +
    +<button class="btn btn-danger" data-toggle="collapse" data-target="#demo">
    +  {{_i}}simple collapsible{{/i}}
    +</button>
    +
    +<div id="demo" class="collapse in"> … </div>
    +
    + {{_i}}Heads up!{{/i}} + {{_i}}To add accordion-like group management to a collapsible control, add the data attribute data-parent="#selector". Refer to the demo to see this in action.{{/i}} +
    +

    {{_i}}Methods{{/i}}

    +

    .collapse({{_i}}options{{/i}})

    +

    {{_i}}Activates your content as a collapsible element. Accepts an optional options object.{{/i}} +

    +$('#myCollapsible').collapse({
    +  toggle: false
    +})
    +

    .collapse('toggle')

    +

    {{_i}}Toggles a collapsible element to shown or hidden.{{/i}}

    +

    .collapse('show')

    +

    {{_i}}Shows a collapsible element.{{/i}}

    +

    .collapse('hide')

    +

    {{_i}}Hides a collapsible element.{{/i}}

    +

    {{_i}}Events{{/i}}

    +

    + {{_i}}Bootstrap's collapse class exposes a few events for hooking into collapse functionality.{{/i}} +

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    {{_i}}Event{{/i}}{{_i}}Description{{/i}}
    {{_i}}show{{/i}}{{_i}}This event fires immediately when the show instance method is called.{{/i}}
    {{_i}}shown{{/i}}{{_i}}This event is fired when a collapse element has been made visible to the user (will wait for css transitions to complete).{{/i}}
    {{_i}}hide{{/i}} + {{_i}}This event is fired immediately when the hide method has been called.{{/i}} +
    {{_i}}hidden{{/i}}{{_i}}This event is fired when a collapse element has been hidden from the user (will wait for css transitions to complete).{{/i}}
    + +
    +$('#myCollapsible').on('hidden', function () {
    +  // {{_i}}do something…{{/i}}
    +})
    +
    +
    +
    + + + + + + + + + +
    + +
    +
    +

    {{_i}}About{{/i}}

    +

    {{_i}}A basic, easily extended plugin for quickly creating elegant typeaheads with any form text input.{{/i}}

    + {{_i}}Download file{{/i}} +
    +
    +

    {{_i}}Example{{/i}}

    +

    {{_i}}Start typing in the field below to show the typeahead results.{{/i}}

    +
    + +
    +
    +

    {{_i}}Using bootstrap-typeahead.js{{/i}}

    +

    {{_i}}Call the typeahead via javascript:{{/i}}

    +
    $('.typeahead').typeahead()
    +

    {{_i}}Options{{/i}}

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    {{_i}}Name{{/i}}{{_i}}type{{/i}}{{_i}}default{{/i}}{{_i}}description{{/i}}
    {{_i}}source{{/i}}{{_i}}array{{/i}}[ ]{{_i}}The data source to query against.{{/i}}
    {{_i}}items{{/i}}{{_i}}number{{/i}}8{{_i}}The max number of items to display in the dropdown.{{/i}}
    {{_i}}matcher{{/i}}{{_i}}function{{/i}}{{_i}}case insensitive{{/i}}{{_i}}The method used to determine if a query matches an item. Accepts a single argument, the item against which to test the query. Access the current query with this.query. Return a boolean true if query is a match.{{/i}}
    {{_i}}sorter{{/i}}{{_i}}function{{/i}}{{_i}}exact match,
    case sensitive,
    case insensitive{{/i}}
    {{_i}}Method used to sort autocomplete results. Accepts a single argument items and has the scope of the typeahead instance. Reference the current query with this.query.{{/i}}
    {{_i}}highlighter{{/i}}{{_i}}function{{/i}}{{_i}}highlights all default matches{{/i}}{{_i}}Method used to highlight autocomplete results. Accepts a single argument item and has the scope of the typeahead instance. Should return html.{{/i}}
    + +

    {{_i}}Markup{{/i}}

    +

    {{_i}}Add data attributes to register an element with typeahead functionality.{{/i}}

    +
    +<input type="text" data-provide="typeahead">
    +
    +

    {{_i}}Methods{{/i}}

    +

    .typeahead({{_i}}options{{/i}})

    +

    {{_i}}Initializes an input with a typeahead.{{/i}}

    +
    +
    +
    \ No newline at end of file diff --git a/symposion_project/static/bootstrap/docs/templates/pages/less.mustache b/symposion_project/static/bootstrap/docs/templates/pages/less.mustache new file mode 100644 index 00000000..7bdbe761 --- /dev/null +++ b/symposion_project/static/bootstrap/docs/templates/pages/less.mustache @@ -0,0 +1,944 @@ + +
    +

    {{_i}}Using LESS with Bootstrap{{/i}}

    +

    {{_i}}Customize and extend Bootstrap with LESS, a CSS preprocessor, to take advantage of the variables, mixins, and more used to build Bootstrap's CSS.{{/i}}

    + +
    + + + + +
    + +
    +
    +

    {{_i}}Why LESS?{{/i}}

    +

    {{_i}}Bootstrap is made with LESS at its core, a dynamic stylesheet language created by our good friend, Alexis Sellier. It makes developing systems-based CSS faster, easier, and more fun.{{/i}}

    +
    +
    +

    {{_i}}What's included?{{/i}}

    +

    {{_i}}As an extension of CSS, LESS includes variables, mixins for reusable snippets of code, operations for simple math, nesting, and even color functions.{{/i}}

    +
    +
    +

    {{_i}}Learn more{{/i}}

    + LESS CSS +

    {{_i}}Visit the official website at http://lesscss.org to learn more.{{/i}}

    +
    +
    +
    +
    +

    {{_i}}Variables{{/i}}

    +

    {{_i}}Managing colors and pixel values in CSS can be a bit of a pain, usually full of copy and paste. Not with LESS though—assign colors or pixel values as variables and change them once.{{/i}}

    +
    +
    +

    {{_i}}Mixins{{/i}}

    +

    {{_i}}Those three border-radius declarations you need to make in regular ol' CSS? Now they're down to one line with the help of mixins, snippets of code you can reuse anywhere.{{/i}}

    +
    +
    +

    {{_i}}Operations{{/i}}

    +

    {{_i}}Make your grid, leading, and more super flexible by doing the math on the fly with operations. Multiply, divide, add, and subtract your way to CSS sanity.{{/i}}

    +
    +
    +
    + + + + +
    + + +

    {{_i}}Scaffolding and links{{/i}}

    + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    @bodyBackground@white{{_i}}Page background color{{/i}}
    @textColor@grayDark{{_i}}Default text color for entire body, headings, and more{{/i}}
    @linkColor#08c{{_i}}Default link text color{{/i}}
    @linkColorHoverdarken(@linkColor, 15%){{_i}}Default link text hover color{{/i}}
    +

    {{_i}}Grid system{{/i}}

    + + + + + + + + + + + + + + + + + + + + + + + +
    @gridColumns12
    @gridColumnWidth60px
    @gridGutterWidth20px
    @fluidGridColumnWidth6.382978723%
    @fluidGridGutterWidth2.127659574%
    +

    {{_i}}Typography{{/i}}

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    @sansFontFamily"Helvetica Neue", Helvetica, Arial, sans-serif
    @serifFontFamilyGeorgia, "Times New Roman", Times, serif
    @monoFontFamilyMenlo, Monaco, "Courier New", monospace
    @baseFontSize13pxMust be pixels
    @baseFontFamily@sansFontFamily
    @baseLineHeight18pxMust be pixels
    @altFontFamily@serifFontFamily
    @headingsFontFamilyinherit
    @headingsFontWeightbold
    @headingsColorinherit
    +

    {{_i}}Tables{{/i}}

    + + + + + + + + + + + + + + + + + + + +
    @tableBackgroundtransparent
    @tableBackgroundAccent#f9f9f9
    @tableBackgroundHover#f5f5f5
    @tableBorderddd
    + +

    {{_i}}Grayscale colors{{/i}}

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    @black#000
    @grayDarker#222
    @grayDark#333
    @gray#555
    @grayLight#999
    @grayLighter#eee
    @white#fff
    +

    {{_i}}Accent colors{{/i}}

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    @blue#049cdb
    @green#46a546
    @red#9d261d
    @yellow#ffc40d
    @orange#f89406
    @pink#c3325f
    @purple#7a43b6
    + + +

    {{_i}}Components{{/i}}

    + +

    {{_i}}Buttons{{/i}}

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    @btnBackground@white
    @btnBackgroundHighlightdarken(@white, 10%)
    @btnBorderdarken(@white, 20%)
    @btnPrimaryBackground@linkColor
    @btnPrimaryBackgroundHighlightspin(@btnPrimaryBackground, 15%)
    @btnInfoBackground#5bc0de
    @btnInfoBackgroundHighlight#2f96b4
    @btnSuccessBackground#62c462
    @btnSuccessBackgroundHighlight51a351
    @btnWarningBackgroundlighten(@orange, 15%)
    @btnWarningBackgroundHighlight@orange
    @btnDangerBackground#ee5f5b
    @btnDangerBackgroundHighlight#bd362f
    @btnInverseBackground@gray
    @btnInverseBackgroundHighlight@grayDarker
    +

    {{_i}}Forms{{/i}}

    + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    @placeholderText@grayLight
    @inputBackground@white
    @inputBorder#ccc
    @inputBorderRadius3px
    @inputDisabledBackground@grayLighter
    @formActionsBackground#f5f5f5
    +

    {{_i}}Form states and alerts{{/i}}

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    @warningText#c09853
    @warningBackground#f3edd2
    @errorText#b94a48
    @errorBackground#f2dede
    @successText#468847
    @successBackground#dff0d8
    @infoText#3a87ad
    @infoBackground#d9edf7
    + +

    {{_i}}Navbar{{/i}}

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    @navbarHeight40px
    @navbarBackground@grayDarker
    @navbarBackgroundHighlight@grayDark
    @navbarText@grayLight
    @navbarLinkColor@grayLight
    @navbarLinkColorHover@white
    @navbarLinkColorActive@navbarLinkColorHover
    @navbarLinkBackgroundHovertransparent
    @navbarLinkBackgroundActive@navbarBackground
    @navbarSearchBackgroundlighten(@navbarBackground, 25%)
    @navbarSearchBackgroundFocus@white
    @navbarSearchBorderdarken(@navbarSearchBackground, 30%)
    @navbarSearchPlaceholderColor#ccc
    @navbarBrandColor@navbarLinkColor
    +

    {{_i}}Dropdowns{{/i}}

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    @dropdownBackground@white
    @dropdownBorderrgba(0,0,0,.2)
    @dropdownLinkColor@grayDark
    @dropdownLinkColorHover@white
    @dropdownLinkBackgroundHover@linkColor
    @@dropdownDividerTop#e5e5e5
    @@dropdownDividerBottom@white
    +

    {{_i}}Hero unit{{/i}}

    + + + + + + + + + + + + + + + + + + +
    @heroUnitBackground@grayLighter
    @heroUnitHeadingColorinherit
    @heroUnitLeadColorinhereit
    + + +
    + + + + +
    + +

    {{_i}}About mixins{{/i}}

    +
    +
    +

    {{_i}}Basic mixins{{/i}}

    +

    {{_i}}A basic mixin is essentially an include or a partial for a snippet of CSS. They're written just like a CSS class and can be called anywhere.{{/i}}

    +
    +.element {
    +  .clearfix();
    +}
    +
    +
    +
    +

    {{_i}}Parametric mixins{{/i}}

    +

    {{_i}}A parametric mixin is just like a basic mixin, but it also accepts parameters (hence the name) with optional default values.{{/i}}

    +
    +.element {
    +  .border-radius(4px);
    +}
    +
    +
    +
    +

    {{_i}}Easily add your own{{/i}}

    +

    {{_i}}Nearly all of Bootstrap's mixins are stored in mixins.less, a wonderful utility .less file that enables you to use a mixin in any of the .less files in the toolkit.{{/i}}

    +

    {{_i}}So, go ahead and use the existing ones or feel free to add your own as you need.{{/i}}

    +
    +
    +

    {{_i}}Included mixins{{/i}}

    +

    {{_i}}Utilities{{/i}}

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    {{_i}}Mixin{{/i}}{{_i}}Parameters{{/i}}{{_i}}Usage{{/i}}
    .clearfix()none{{_i}}Add to any parent to clear floats within{{/i}}
    .tab-focus()none{{_i}}Apply the Webkit focus style and round Firefox outline{{/i}}
    .center-block()none{{_i}}Auto center a block-level element using margin: auto{{/i}}
    .ie7-inline-block()none{{_i}}Use in addition to regular display: inline-block to get IE7 support{{/i}}
    .size()@height @width{{_i}}Quickly set the height and width on one line{{/i}}
    .square()@size{{_i}}Builds on .size() to set the width and height as same value{{/i}}
    .opacity()@opacity{{_i}}Set, in whole numbers, the opacity percentage (e.g., "50" or "75"){{/i}}
    +

    Forms

    + + + + + + + + + + + + + + + +
    {{_i}}Mixin{{/i}}{{_i}}Parameters{{/i}}{{_i}}Usage{{/i}}
    .placeholder()@color: @placeholderText{{_i}}Set the placeholder text color for inputs{{/i}}
    +

    Typography

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    {{_i}}Mixin{{/i}}{{_i}}Parameters{{/i}}{{_i}}Usage{{/i}}
    #font > #family > .serif()none{{_i}}Make an element use a serif font stack{{/i}}
    #font > #family > .sans-serif()none{{_i}}Make an element use a sans-serif font stack{{/i}}
    #font > #family > .monospace()none{{_i}}Make an element use a monospace font stack{{/i}}
    #font > .shorthand()@size: @baseFontSize, @weight: normal, @lineHeight: @baseLineHeight{{_i}}Easily set font size, weight, and leading{{/i}}
    #font > .serif()@size: @baseFontSize, @weight: normal, @lineHeight: @baseLineHeight{{_i}}Set font family to serif, and control size, weight, and leading{{/i}}
    #font > .sans-serif()@size: @baseFontSize, @weight: normal, @lineHeight: @baseLineHeight{{_i}}Set font family to sans-serif, and control size, weight, and leading{{/i}}
    #font > .monospace()@size: @baseFontSize, @weight: normal, @lineHeight: @baseLineHeight{{_i}}Set font family to monospace, and control size, weight, and leading{{/i}}
    +

    Grid system

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    {{_i}}Mixin{{/i}}{{_i}}Parameters{{/i}}{{_i}}Usage{{/i}}
    .container-fixed()none{{_i}}Create a horizontally centered container for holding your content{{/i}}
    #grid > .core()@gridColumnWidth, @gridGutterWidth{{_i}}Generate a pixel grid system (container, row, and columns) with n columns and x pixel wide gutter{{/i}}
    #grid > .fluid()@fluidGridColumnWidth, @fluidGridGutterWidth{{_i}}Generate a percent grid system with n columns and x % wide gutter{{/i}}
    #grid > .input()@gridColumnWidth, @gridGutterWidth{{_i}}Generate the pixel grid system for input elements, accounting for padding and borders{{/i}}
    .makeColumn@columns: 1, @offset: 0{{_i}}Turn any div into a grid column without the .span* classes{{/i}}
    +

    {{_i}}CSS3 properties{{/i}}

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    {{_i}}Mixin{{/i}}{{_i}}Parameters{{/i}}{{_i}}Usage{{/i}}
    .border-radius()@radius{{_i}}Round the corners of an element. Can be a single value or four space-separated values{{/i}}
    .box-shadow()@shadow{{_i}}Add a drop shadow to an element{{/i}}
    .transition()@transition{{_i}}Add CSS3 transition effect (e.g., all .2s linear){{/i}}
    .rotate()@degrees{{_i}}Rotate an element n degrees{{/i}}
    .scale()@ratio{{_i}}Scale an element to n times its original size{{/i}}
    .translate()@x, @y{{_i}}Move an element on the x and y planes{{/i}}
    .background-clip()@clip{{_i}}Crop the background of an element (useful for border-radius){{/i}}
    .background-size()@size{{_i}}Control the size of background images via CSS3{{/i}}
    .box-sizing()@boxmodel{{_i}}Change the box model for an element (e.g., border-box for a full-width input){{/i}}
    .user-select()@select{{_i}}Control cursor selection of text on a page{{/i}}
    .backface-visibility()@visibility: visible{{_i}}Prevent flickering of content when using CSS 3D transforms{{/i}}
    .resizable()@direction: both{{_i}}Make any element resizable on the right and bottom{{/i}}
    .content-columns()@columnCount, @columnGap: @gridGutterWidth{{_i}}Make the content of any element use CSS3 columns{{/i}}
    .hyphens()@mode: auto{{_i}}CSS3 hyphenation when you want it (includes word-wrap: break-word){{/i}}
    +

    {{_i}}Backgrounds and gradients{{/i}}

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    {{_i}}Mixin{{/i}}{{_i}}Parameters{{/i}}{{_i}}Usage{{/i}}
    #translucent > .background()@color: @white, @alpha: 1{{_i}}Give an element a translucent background color{{/i}}
    #translucent > .border()@color: @white, @alpha: 1{{_i}}Give an element a translucent border color{{/i}}
    #gradient > .vertical()@startColor, @endColor{{_i}}Create a cross-browser vertical background gradient{{/i}}
    #gradient > .horizontal()@startColor, @endColor{{_i}}Create a cross-browser horizontal background gradient{{/i}}
    #gradient > .directional()@startColor, @endColor, @deg{{_i}}Create a cross-browser directional background gradient{{/i}}
    #gradient > .vertical-three-colors()@startColor, @midColor, @colorStop, @endColor{{_i}}Create a cross-browser three-color background gradient{{/i}}
    #gradient > .radial()@innerColor, @outerColor{{_i}}Create a cross-browser radial background gradient{{/i}}
    #gradient > .striped()@color, @angle{{_i}}Create a cross-browser striped background gradient{{/i}}
    #gradientBar()@primaryColor, @secondaryColor{{_i}}Used for buttons to assign a gradient and slightly darker border{{/i}}
    +
    + + + + +
    + +
    + {{_i}}Note: If you're submitting a pull request to GitHub with modified CSS, you must recompile the CSS via any of these methods.{{/i}} +
    +

    {{_i}}Tools for compiling{{/i}}

    +
    +
    +

    {{_i}}Node with makefile{{/i}}

    +

    {{_i}}Install the LESS command line compiler, JSHint, Recess, and uglify-js globally with npm by running the following command:{{/i}}

    +
    $ npm install -g less jshint recess uglify-js
    +

    {{_i}}Once installed just run make from the root of your bootstrap directory and you're all set.{{/i}}

    +

    {{_i}}Additionally, if you have watchr installed, you may run make watch to have bootstrap automatically rebuilt every time you edit a file in the bootstrap lib (this isn't required, just a convenience method).{{/i}}

    +
    +
    +

    {{_i}}Command line{{/i}}

    +

    {{_i}}Install the LESS command line tool via Node and run the following command:{{/i}}

    +
    $ lessc ./less/bootstrap.less > bootstrap.css
    +

    {{_i}}Be sure to include --compress in that command if you're trying to save some bytes!{{/i}}

    +
    +
    +

    {{_i}}Javascript{{/i}}

    +

    {{_i}}Download the latest Less.js and include the path to it (and Bootstrap) in the <head>.{{/i}}

    +
    +<link rel="stylesheet/less" href="/path/to/bootstrap.less">
    +<script src="/path/to/less.js"></script>
    +
    +

    {{_i}}To recompile the .less files, just save them and reload your page. Less.js compiles them and stores them in local storage.{{/i}}

    +
    +
    +
    +
    +

    {{_i}}Unofficial Mac app{{/i}}

    +

    {{_i}}The unofficial Mac app watches directories of .less files and compiles the code to local files after every save of a watched .less file.{{/i}}

    +

    {{_i}}If you like, you can toggle preferences in the app for automatic minifying and which directory the compiled files end up in.{{/i}}

    +
    +
    +

    {{_i}}More Mac apps{{/i}}

    +

    Crunch

    +

    {{_i}}Crunch is a great looking LESS editor and compiler built on Adobe Air.{{/i}}

    +

    CodeKit

    +

    {{_i}}Created by the same guy as the unofficial Mac app, CodeKit is a Mac app that compiles LESS, SASS, Stylus, and CoffeeScript.{{/i}}

    +

    Simpless

    +

    {{_i}}Mac, Linux, and PC app for drag and drop compiling of LESS files. Plus, the source code is on GitHub.{{/i}}

    +
    +
    +
    diff --git a/symposion_project/static/bootstrap/docs/templates/pages/scaffolding.mustache b/symposion_project/static/bootstrap/docs/templates/pages/scaffolding.mustache new file mode 100644 index 00000000..1088cc77 --- /dev/null +++ b/symposion_project/static/bootstrap/docs/templates/pages/scaffolding.mustache @@ -0,0 +1,555 @@ + +
    +

    {{_i}}Scaffolding{{/i}}

    +

    {{_i}}Bootstrap is built on a responsive 12-column grid. We've also included fixed- and fluid-width layouts based on that system.{{/i}}

    + +
    + + + + + +
    + +
    +
    +

    {{_i}}Requires HTML5 doctype{{/i}}

    +

    {{_i}}Bootstrap makes use of HTML elements and CSS properties that require the use of the HTML5 doctype. Be sure to include it at the beginning of every Bootstrapped page in your project.{{/i}}

    +
    +<!DOCTYPE html>
    +<html lang="en">
    +  ...
    +</html>
    +
    +
    +
    +

    {{_i}}Typography and links{{/i}}

    +

    {{_i}}Within the scaffolding.less file, we set basic global display, typography, and link styles. Specifically, we:{{/i}}

    +
      +
    • {{_i}}Remove margin on the body{{/i}}
    • +
    • {{_i}}Set background-color: white; on the body{{/i}}
    • +
    • {{_i}}Use the @baseFontFamily, @baseFontSize, and @baseLineHeight attributes as our typographyic base{{/i}}
    • +
    • {{_i}}Set the global link color via @linkColor and apply link underlines only on :hover{{/i}}
    • +
    +
    +
    +

    {{_i}}Reset via Normalize{{/i}}

    +

    {{_i}}As of Bootstrap 2, the traditional CSS reset has evolved to make use of elements from Normalize.css, a project by Nicolas Gallagher that also powers the HTML5 Boilerplate.{{/i}}

    +

    {{_i}}The new reset can still be found in reset.less, but with many elements removed for brevity and accuracy.{{/i}}

    +
    +
    +
    + + + + + +
    + + +
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    +
    +
    4
    +
    4
    +
    4
    +
    +
    +
    4
    +
    8
    +
    +
    +
    6
    +
    6
    +
    +
    +
    12
    +
    +
    +
    +

    {{_i}}The default grid system provided in Bootstrap utilizes 12 columns that render out at widths of 724px, 940px (default without responsive CSS included), and 1170px. Below 767px viewports, the columns become fluid and stack vertically. {{/i}}

    +
    +
    +
    +<div class="row">
    +  <div class="span4">...</div>
    +  <div class="span8">...</div>
    +</div>
    +
    +
    +
    +

    {{_i}}As shown here, a basic layout can be created with two "columns", each spanning a number of the 12 foundational columns we defined as part of our grid system.{{/i}}

    +
    +
    + +
    + +

    {{_i}}Offsetting columns{{/i}}

    +
    +
    4
    +
    4 offset 4
    +
    +
    +
    3 offset 3
    +
    3 offset 3
    +
    +
    +
    8 offset 4
    +
    +
    +<div class="row">
    +  <div class="span4">...</div>
    +  <div class="span4 offset4">...</div>
    +</div>
    +
    + +
    + +

    {{_i}}Nesting columns{{/i}}

    +
    +
    +

    {{_i}}With the static (non-fluid) grid system in Bootstrap, nesting is easy. To nest your content, just add a new .row and set of .span* columns within an existing .span* column.{{/i}}

    +

    {{_i}}Example{{/i}}

    +

    {{_i}}Nested rows should include a set of columns that add up to the number of columns of it's parent. For example, two nested .span3 columns should be placed within a .span6.{{/i}}

    +
    +
    + {{_i}}Level 1 of column{{/i}} +
    +
    + {{_i}}Level 2{{/i}} +
    +
    + {{_i}}Level 2{{/i}} +
    +
    +
    +
    +
    +
    +
    +<div class="row">
    +  <div class="span6">
    +    {{_i}}Level 1 column{{/i}}
    +    <div class="row">
    +      <div class="span3">{{_i}}Level 2{{/i}}</div>
    +      <div class="span3">{{_i}}Level 2{{/i}}</div>
    +    </div>
    +  </div>
    +</div>
    +
    +
    +
    +
    + + + + +
    + + +

    {{_i}}Fluid columns{{/i}}

    +
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    1
    +
    +
    +
    4
    +
    4
    +
    4
    +
    +
    +
    4
    +
    8
    +
    +
    +
    6
    +
    6
    +
    +
    +
    12
    +
    + +
    +
    +

    {{_i}}Percents, not pixels{{/i}}

    +

    {{_i}}The fluid grid system uses percents for column widths instead of fixed pixels. It also has the same responsive variations as our fixed grid system, ensuring proper proportions for key screen resolutions and devices.{{/i}}

    +
    +
    +

    {{_i}}Fluid rows{{/i}}

    +

    {{_i}}Make any row fluid simply by changing .row to .row-fluid. The columns stay the exact same, making it super straightforward to flip between fixed and fluid layouts.{{/i}}

    +
    +
    +

    {{_i}}Markup{{/i}}

    +
    +<div class="row-fluid">
    +  <div class="span4">...</div>
    +  <div class="span8">...</div>
    +</div>
    +
    +
    +
    + +

    {{_i}}Fluid nesting{{/i}}

    +
    +
    +

    {{_i}}Nesting with fluid grids is a bit different: the number of nested columns doesn't need to match the parent. Instead, your columns are reset at each level because each row takes up 100% of the parent column.{{/i}}

    +
    +
    + {{_i}}Fluid 12{{/i}} +
    +
    + {{_i}}Fluid 6{{/i}} +
    +
    + {{_i}}Fluid 6{{/i}} +
    +
    +
    +
    +
    +
    +
    +<div class="row-fluid">
    +  <div class="span12">
    +    {{_i}}Level 1 of column{{/i}}
    +    <div class="row-fluid">
    +      <div class="span6">{{_i}}Level 2{{/i}}</div>
    +      <div class="span6">{{_i}}Level 2{{/i}}</div>
    +    </div>
    +  </div>
    +</div>
    +
    +
    +
    + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    {{_i}}Variable{{/i}}{{_i}}Default value{{/i}}{{_i}}Description{{/i}}
    @gridColumns12{{_i}}Number of columns{{/i}}
    @gridColumnWidth60px{{_i}}Width of each column{{/i}}
    @gridGutterWidth20px{{_i}}Negative space between columns{{/i}}
    +
    +
    +

    {{_i}}Variables in LESS{{/i}}

    +

    {{_i}}Built into Bootstrap are a handful of variables for customizing the default 940px grid system, documented above. All variables for the grid are stored in variables.less.{{/i}}

    +
    +
    +

    {{_i}}How to customize{{/i}}

    +

    {{_i}}Modifying the grid means changing the three @grid* variables and recompiling Bootstrap. Change the grid variables in variables.less and use one of the four ways documented to recompile. If you're adding more columns, be sure to add the CSS for those in grid.less.{{/i}}

    +
    +
    +

    {{_i}}Staying responsive{{/i}}

    +

    {{_i}}Customization of the grid only works at the default level, the 940px grid. To maintain the responsive aspects of Bootstrap, you'll also have to customize the grids in responsive.less.{{/i}}

    +
    +
    + +
    + + + + +
    + + +
    +
    +

    {{_i}}Fixed layout{{/i}}

    +

    {{_i}}The default and simple 940px-wide, centered layout for just about any website or page provided by a single <div class="container">.{{/i}}

    +
    +
    +
    +
    +<body>
    +  <div class="container">
    +    ...
    +  </div>
    +</body>
    +
    +
    +
    +

    {{_i}}Fluid layout{{/i}}

    +

    {{_i}}<div class="container-fluid"> gives flexible page structure, min- and max-widths, and a left-hand sidebar. It's great for apps and docs.{{/i}}

    +
    +
    +
    +
    +
    +<div class="container-fluid">
    +  <div class="row-fluid">
    +    <div class="span2">
    +      <!--{{_i}}Sidebar content{{/i}}-->
    +    </div>
    +    <div class="span10">
    +      <!--{{_i}}Body content{{/i}}-->
    +    </div>
    +  </div>
    +</div>
    +
    +
    +
    +
    + + + + + +
    + + +
    +
    +

    Responsive devices

    +

    {{_i}}What they do{{/i}}

    +

    {{_i}}Media queries allow for custom CSS based on a number of conditions—ratios, widths, display type, etc—but usually focuses around min-width and max-width.{{/i}}

    +
      +
    • {{_i}}Modify the width of column in our grid{{/i}}
    • +
    • {{_i}}Stack elements instead of float wherever necessary{{/i}}
    • +
    • {{_i}}Resize headings and text to be more appropriate for devices{{/i}}
    • +
    +

    {{_i}}Use media queries responsibly and only as a start to your mobile audiences. For larger projects, do consider dedicated code bases and not layers of media queries.{{/i}}

    +
    +
    +

    {{_i}}Supported devices{{/i}}

    +

    {{_i}}Bootstrap supports a handful of media queries in a single file to help make your projects more appropriate on different devices and screen resolutions. Here's what's included:{{/i}}

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    {{_i}}Label{{/i}}{{_i}}Layout width{{/i}}{{_i}}Column width{{/i}}{{_i}}Gutter width{{/i}}
    {{_i}}Smartphones{{/i}}480px and below{{_i}}Fluid columns, no fixed widths{{/i}}
    {{_i}}Smartphones to tablets{{/i}}767px and below{{_i}}Fluid columns, no fixed widths{{/i}}
    {{_i}}Portrait tablets{{/i}}768px and above42px20px
    {{_i}}Default{{/i}}980px and up60px20px
    {{_i}}Large display{{/i}}1200px and up70px30px
    + +

    {{_i}}Requires meta tag{{/i}}

    +

    {{_i}}To ensure devices display responsive pages properly, include the viewport meta tag.{{/i}}

    +
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    +
    +
    + +
    + + +

    {{_i}}Using the media queries{{/i}}

    +
    +
    +

    {{_i}}Bootstrap doesn't automatically include these media queries, but understanding and adding them is very easy and requires minimal setup. You have a few options for including the responsive features of Bootstrap:{{/i}}

    +
      +
    1. {{_i}}Use the compiled responsive version, bootstrap-responsive.css{{/i}}
    2. +
    3. {{_i}}Add @import "responsive.less" and recompile Bootstrap{{/i}}
    4. +
    5. {{_i}}Modify and recompile responsive.less as a separate file{{/i}}
    6. +
    +

    {{_i}}Why not just include it? Truth be told, not everything needs to be responsive. Instead of encouraging developers to remove this feature, we figure it best to enable it.{{/i}}

    +
    +
    +
    +  /* {{_i}}Landscape phones and down{{/i}} */
    +  @media (max-width: 480px) { ... }
    +
    +  /* {{_i}}Landscape phone to portrait tablet{{/i}} */
    +  @media (max-width: 767px) { ... }
    +
    +  /* {{_i}}Portrait tablet to landscape and desktop{{/i}} */
    +  @media (min-width: 768px) and (max-width: 979px) { ... }
    +
    +  /* {{_i}}Large desktop{{/i}} */
    +  @media (min-width: 1200px) { ... }
    +
    +
    +
    +
    + + +

    {{_i}}Responsive utility classes{{/i}}

    +
    +
    +

    {{_i}}What are they{{/i}}

    +

    {{_i}}For faster mobile-friendly development, use these basic utility classes for showing and hiding content by device.{{/i}}

    +

    {{_i}}When to use{{/i}}

    +

    {{_i}}Use on a limited basis and avoid creating entirely different versions of the same site. Instead, use them to complement each device's presentation.{{/i}}

    +

    {{_i}}For example, you might show a <select> element for nav on mobile layouts, but not on tablets or desktops.{{/i}}

    +
    +
    +

    {{_i}}Support classes{{/i}}

    +

    {{_i}}Shown here is a table of the classes we support and their effect on a given media query layout (labeled by device). They can be found in responsive.less.{{/i}}

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    {{_i}}Class{{/i}}{{_i}}Phones 480px and below{{/i}}{{_i}}Tablets 767px and below{{/i}}{{_i}}Desktops 768px and above{{/i}}
    .visible-phone{{_i}}Visible{{/i}}
    .visible-tablet{{_i}}Visible{{/i}}
    .visible-desktop{{_i}}Visible{{/i}}
    .hidden-phone{{_i}}Visible{{/i}}{{_i}}Visible{{/i}}
    .hidden-tablet{{_i}}Visible{{/i}}{{_i}}Visible{{/i}}
    .hidden-desktop{{_i}}Visible{{/i}}{{_i}}Visible{{/i}}
    +

    {{_i}}Test case{{/i}}

    +

    {{_i}}Resize your browser or load on different devices to test the above classes.{{/i}}

    +

    {{_i}}Visible on...{{/i}}

    +

    {{_i}}Green checkmarks indicate that class is visible in your current viewport.{{/i}}

    +
      +
    • {{_i}}Phone{{/i}}✔ {{_i}}Phone{{/i}}
    • +
    • {{_i}}Tablet{{/i}}✔ {{_i}}Tablet{{/i}}
    • +
    • {{_i}}Desktop{{/i}}✔ {{_i}}Desktop{{/i}}
    • +
    +

    {{_i}}Hidden on...{{/i}}

    +

    {{_i}}Here, green checkmarks indicate that class is hidden in your current viewport.{{/i}}

    +
      +
    • {{_i}}Phone{{/i}}✔ {{_i}}Phone{{/i}}
    • +
    • {{_i}}Tablet{{/i}}✔ {{_i}}Tablet{{/i}}
    • +
    • {{_i}}Desktop{{/i}}✔ {{_i}}Desktop{{/i}}
    • +
    +
    +
    + + +
    +
    +
    +
    +
    diff --git a/symposion_project/static/bootstrap/docs/templates/pages/upgrading.mustache b/symposion_project/static/bootstrap/docs/templates/pages/upgrading.mustache new file mode 100644 index 00000000..5a82e2dc --- /dev/null +++ b/symposion_project/static/bootstrap/docs/templates/pages/upgrading.mustache @@ -0,0 +1,194 @@ + +
    +

    {{_i}}Upgrading to Bootstrap 2{{/i}}

    +

    {{_i}}Learn about significant changes and additions since v1.4 with this handy guide.{{/i}}

    +
    + + + + +
    + +
      +
    • {{_i}}Docs: major updates across the board to general structure, examples, and code snippets. Also made responsive with new media queries.{{/i}}
    • +
    • {{_i}}Docs: all docs pages are now powered by Mustache templates and strings are wrapped in i18n tags for translation by the Twitter Translation Center. All changes to documentation must be done here and then compiled (similar to our CSS and LESS).{{/i}}
    • +
    • {{_i}}Repo directory structure: removed the compiled CSS from the root in favor of a large direct download link on the docs homepage. Compiled CSS is in /docs/assets/css/.{{/i}}
    • +
    • {{_i}}Docs and repo: one makefile, just type make in the Terminal and get updated docs and CSS.{{/i}}
    • +
    +
    + + + + +
    + +

    {{_i}}Grid system{{/i}}

    +
      +
    • {{_i}}Updated grid system, now only 12 columns instead of 16{{/i}} +
    • {{_i}}Responsive approach means your projects virtually work out of the box on smartphones, tablets, and more{{/i}}
    • +
    • {{_i}}Removed unused (by default) grid columns support for 17-24 columns{{/i}}
    • +
    +

    {{_i}}Responsive (media queries){{/i}}

    +
      +
    • {{_i}}Media queries added for basic support across mobile and tablet devices{{/i}} +
    • {{_i}}Responsive CSS is compiled separately, as bootstrap-responsive.css{{/i}}
    • +
    +
    + + + + +
    + +

    {{_i}}Typography{{/i}}

    +
      +
    • {{_i}}h4 elements were dropped from 16px to 14px with a default line-height of 18px{{/i}}
    • +
    • {{_i}}h5 elements were dropped from 14px to 12px{{/i}}
    • +
    • {{_i}}h6 elements were dropped from 13px to 11px{{/i}}
    • +
    • {{_i}}Right-aligned option for blockquotes if float: right;{{/i}}
    • +
    +

    {{_i}}Code{{/i}}

    +
      +
    • {{_i}}New graphical style for <code>{{/i}}
    • +
    • {{_i}}Google Code Prettify styles updated (based on GitHub's gists){{/i}}
    • +
    +

    {{_i}}Tables{{/i}}

    +
      +
    • {{_i}}Improved support for colspan and rowspan{{/i}}
    • +
    • {{_i}}Styles now restricted to new base class, .table{{/i}}
    • +
    • {{_i}}Table classes standardized with .table- required as a prefix{{/i}}
    • +
    • {{_i}}Removed unused table color options (too much code for such little impact){{/i}}
    • +
    • {{_i}}Dropped support for TableSorter{{/i}}
    • +
    +

    {{_i}}Buttons{{/i}}

    +
      +
    • {{_i}}New classes for colors and sizes, all prefixed with .btn-{{/i}}
    • +
    • {{_i}}IE9: removed gradients and added rounded corners{{/i}}
    • +
    • {{_i}}Updated active state to make styling clearer in button groups (new) and look better with custom transition{{/i}}
    • +
    • {{_i}}New mixin, .buttonBackground, to set button gradients{{/i}}
    • +
    • {{_i}}The .secondary class was removed from modal examples in our docs as it never had associated styles.{{/i}}
    • +
    +

    {{_i}}Forms{{/i}}

    +
      +
    • {{_i}}Default form style is now vertical (stacked) to use less CSS and add greater flexibility{{/i}}
    • +
    • {{_i}}Form classes standardized with .form- required as a prefix{{/i}}
    • +
    • {{_i}}New built-in form defaults for search, inline, and horizontal forms{{/i}}
    • +
    • {{_i}}For horizontal forms, previous classes .clearfix and .input are equivalent to the new .control-group and .controls.{{/i}}
    • +
    • {{_i}}More flexible horizontal form markup with classes for all styling, including new optional class for the label{{/i}}
    • +
    • {{_i}}Form states: colors updated and customizable via new LESS variables{{/i}}
    • +
    +

    {{_i}}Icons, by Glyphicons{{/i}}

    +
      +
    • {{_i}}New Glyphicons Halflings icon set added in sprite form, in black and white{{/i}}
    • +
    • {{_i}}Simple markup required for an icon in tons of contexts: <i class="icon-cog"></>{{/i}}
    • +
    • {{_i}}Add another class, .icon-white, for white variation of the same icon{{/i}}
    • +
    +
    + + + + +
    + +

    {{_i}}Button groups and dropdowns{{/i}}

    +
      +
    • {{_i}}Two brand new components in 2.0: button groups and button dropdowns{{/i}}
    • +
    • {{_i}}Dependency: button dropdowns are built on button groups, and therefore require all their styles{{/i}}
    • +
    • {{_i}}Button groups, .btn-group, can be grouped one level higher with a button toolbar, .btn-toolbar{{/i}}
    • +
    +

    {{_i}}Navigation{{/i}}

    +
      +
    • {{_i}}Tabs and pills now require the use of a new base class, .nav, on their <ul> and the class names are now .nav-pills and .nav-tabs.{{/i}}
    • +
    • {{_i}}New nav list variation added that uses the same base class, .nav{{/i}}
    • +
    • {{_i}}Vertical tabs and pills have been added—just add .nav-stacked to the <ul>{{/i}}
    • +
    • {{_i}}Pills were restyled to be less rounded by default{{/i}}
    • +
    • {{_i}}Pills now have dropdown menu support (they share the same markup and styles as tabs){{/i}}
    • +
    +

    {{_i}}Navbar (formerly topbar){{/i}}

    +
      +
    • {{_i}}Base class changed from .topbar to .navbar{{/i}}
    • +
    • {{_i}}Now supports static position (default behavior, not fixed) and fixed to the top of viewport via .navbar-fixed-top (previously only supported fixed){{/i}}
    • +
    • {{_i}}Added vertical dividers to top-level nav{{/i}}
    • +
    • {{_i}}Improved support for inline forms in the navbar, which now require .navbar-form to properly scope styles to only the intended forms.{{/i}}
    • +
    • {{_i}}Navbar search form now requires use of the .navbar-search class and its input the use of .search-query. To position the search form, you must use .pull-left or .pull-right.{{/i}}
    • +
    • {{_i}}Added optional responsive markup for collapsing navbar contents for smaller resolutions and devices. See navbar docs for how to utilize.{{/i}}
    • +
    +

    {{_i}}Dropdown menus{{/i}}

    +
      +
    • {{_i}}Updated the .dropdown-menu to tighten up spacing{{/i}}
    • +
    • {{_i}}Now requires you to add a <span class="caret"></span> to show the dropdown arrow{{/i}}
    • +
    • {{_i}}Now requires you to add a data-toggle="dropdown" attribute to obtain toggling behavior{{/i}}
    • +
    • {{_i}}The navbar (fixed topbar) has brand new dropdowns. Gone are the dark versions and in their place are the standard white ones with an additional caret at their tops for clarity of position.{{/i}}
    • +
    +

    {{_i}}Labels{{/i}}

    +
      +
    • {{_i}}Label colors updated to match form state colors{{/i}}
    • +
    • {{_i}}Not only do they match graphically, but they are powered by the same new variables{{/i}}
    • +
    +

    {{_i}}Thumbnails{{/i}}

    +
      +
    • {{_i}}Formerly .media-grid, now just .thumbnails, we've thoroughly extended this component for more uses while maintaining overall simplicity out of the box.{{/i}}
    • +
    • {{_i}}Individual thumbnails now require .thumbnail class{{/i}}
    • +
    +

    {{_i}}Alerts{{/i}}

    +
      +
    • {{_i}}New base class: .alert instead of .alert-message{{/i}}
    • +
    • {{_i}}Class names standardized for other options, now all starting with .alert-{{/i}}
    • +
    • {{_i}}Redesigned base alert styles to combine the default alerts and block-level alerts into one{{/i}}
    • +
    • {{_i}}Block level alert class changed: .alert-block instead of .block-message{{/i}}
    • +
    +

    {{_i}}Progress bars{{/i}}

    +
      +
    • {{_i}}New in 2.0{{/i}}
    • +
    • {{_i}}Features multiple styles via classes, including striped and animated variations via CSS3{{/i}}
    • +
    +

    {{_i}}Miscellaneous components{{/i}}

    +
      +
    • {{_i}}Added documentation for the well component and the close icon (used in modals and alerts){{/i}}
    • +
    +
    + + + + +
    + +
    + {{_i}}Heads up!{{/i}} {{_i}}We've rewritten just about everything for our plugins, so head on over to the Javascript page to learn more.{{/i}} +
    +

    {{_i}}Tooltips{{/i}}

    +
      +
    • {{_i}}The plugin method has been renamed from twipsy() to tooltip(), and the class name changed from twipsy to tooltip.{{/i}}
    • +
    • {{_i}}The placement option value that was below is now bottom, and above is now top.{{/i}}
    • +
    • {{_i}}The animate option was renamed to animation.{{/i}}
    • +
    • {{_i}}The html option was removed, as the tooltips default to allowing HTML now.{{/i}}
    • +
    +

    {{_i}}Popovers{{/i}}

    +
      +
    • {{_i}}Child elements now properly namespaced: .title to .popover-title, .inner to .popover-inner, and .content to .popover-content.{{/i}}
    • +
    • {{_i}}The placement option value that was below is now bottom, and above is now top.{{/i}}
    • +
    +

    {{_i}}New plugins{{/i}}

    + +
    + diff --git a/symposion_project/static/bootstrap/docs/upgrading.html b/symposion_project/static/bootstrap/docs/upgrading.html new file mode 100644 index 00000000..2a2c3981 --- /dev/null +++ b/symposion_project/static/bootstrap/docs/upgrading.html @@ -0,0 +1,310 @@ + + + + + Upgrading · Twitter Bootstrap + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +
    +

    Upgrading to Bootstrap 2

    +

    Learn about significant changes and additions since v1.4 with this handy guide.

    +
    + + + + +
    + +
      +
    • Docs: major updates across the board to general structure, examples, and code snippets. Also made responsive with new media queries.
    • +
    • Docs: all docs pages are now powered by Mustache templates and strings are wrapped in i18n tags for translation by the Twitter Translation Center. All changes to documentation must be done here and then compiled (similar to our CSS and LESS).
    • +
    • Repo directory structure: removed the compiled CSS from the root in favor of a large direct download link on the docs homepage. Compiled CSS is in /docs/assets/css/.
    • +
    • Docs and repo: one makefile, just type make in the Terminal and get updated docs and CSS.
    • +
    +
    + + + + +
    + +

    Grid system

    +
      +
    • Updated grid system, now only 12 columns instead of 16 +
    • Responsive approach means your projects virtually work out of the box on smartphones, tablets, and more
    • +
    • Removed unused (by default) grid columns support for 17-24 columns
    • +
    +

    Responsive (media queries)

    +
      +
    • Media queries added for basic support across mobile and tablet devices +
    • Responsive CSS is compiled separately, as bootstrap-responsive.css
    • +
    +
    + + + + +
    + +

    Typography

    +
      +
    • h4 elements were dropped from 16px to 14px with a default line-height of 18px
    • +
    • h5 elements were dropped from 14px to 12px
    • +
    • h6 elements were dropped from 13px to 11px
    • +
    • Right-aligned option for blockquotes if float: right;
    • +
    +

    Code

    +
      +
    • New graphical style for <code>
    • +
    • Google Code Prettify styles updated (based on GitHub's gists)
    • +
    +

    Tables

    +
      +
    • Improved support for colspan and rowspan
    • +
    • Styles now restricted to new base class, .table
    • +
    • Table classes standardized with .table- required as a prefix
    • +
    • Removed unused table color options (too much code for such little impact)
    • +
    • Dropped support for TableSorter
    • +
    +

    Buttons

    +
      +
    • New classes for colors and sizes, all prefixed with .btn-
    • +
    • IE9: removed gradients and added rounded corners
    • +
    • Updated active state to make styling clearer in button groups (new) and look better with custom transition
    • +
    • New mixin, .buttonBackground, to set button gradients
    • +
    • The .secondary class was removed from modal examples in our docs as it never had associated styles.
    • +
    +

    Forms

    +
      +
    • Default form style is now vertical (stacked) to use less CSS and add greater flexibility
    • +
    • Form classes standardized with .form- required as a prefix
    • +
    • New built-in form defaults for search, inline, and horizontal forms
    • +
    • For horizontal forms, previous classes .clearfix and .input are equivalent to the new .control-group and .controls.
    • +
    • More flexible horizontal form markup with classes for all styling, including new optional class for the label
    • +
    • Form states: colors updated and customizable via new LESS variables
    • +
    +

    Icons, by Glyphicons

    +
      +
    • New Glyphicons Halflings icon set added in sprite form, in black and white
    • +
    • Simple markup required for an icon in tons of contexts: <i class="icon-cog"></>
    • +
    • Add another class, .icon-white, for white variation of the same icon
    • +
    +
    + + + + +
    + +

    Button groups and dropdowns

    +
      +
    • Two brand new components in 2.0: button groups and button dropdowns
    • +
    • Dependency: button dropdowns are built on button groups, and therefore require all their styles
    • +
    • Button groups, .btn-group, can be grouped one level higher with a button toolbar, .btn-toolbar
    • +
    +

    Navigation

    +
      +
    • Tabs and pills now require the use of a new base class, .nav, on their <ul> and the class names are now .nav-pills and .nav-tabs.
    • +
    • New nav list variation added that uses the same base class, .nav
    • +
    • Vertical tabs and pills have been added—just add .nav-stacked to the <ul>
    • +
    • Pills were restyled to be less rounded by default
    • +
    • Pills now have dropdown menu support (they share the same markup and styles as tabs)
    • +
    +

    Navbar (formerly topbar)

    +
      +
    • Base class changed from .topbar to .navbar
    • +
    • Now supports static position (default behavior, not fixed) and fixed to the top of viewport via .navbar-fixed-top (previously only supported fixed)
    • +
    • Added vertical dividers to top-level nav
    • +
    • Improved support for inline forms in the navbar, which now require .navbar-form to properly scope styles to only the intended forms.
    • +
    • Navbar search form now requires use of the .navbar-search class and its input the use of .search-query. To position the search form, you must use .pull-left or .pull-right.
    • +
    • Added optional responsive markup for collapsing navbar contents for smaller resolutions and devices. See navbar docs for how to utilize.
    • +
    +

    Dropdown menus

    +
      +
    • Updated the .dropdown-menu to tighten up spacing
    • +
    • Now requires you to add a <span class="caret"></span> to show the dropdown arrow
    • +
    • Now requires you to add a data-toggle="dropdown" attribute to obtain toggling behavior
    • +
    • The navbar (fixed topbar) has brand new dropdowns. Gone are the dark versions and in their place are the standard white ones with an additional caret at their tops for clarity of position.
    • +
    +

    Labels

    +
      +
    • Label colors updated to match form state colors
    • +
    • Not only do they match graphically, but they are powered by the same new variables
    • +
    +

    Thumbnails

    +
      +
    • Formerly .media-grid, now just .thumbnails, we've thoroughly extended this component for more uses while maintaining overall simplicity out of the box.
    • +
    • Individual thumbnails now require .thumbnail class
    • +
    +

    Alerts

    +
      +
    • New base class: .alert instead of .alert-message
    • +
    • Class names standardized for other options, now all starting with .alert-
    • +
    • Redesigned base alert styles to combine the default alerts and block-level alerts into one
    • +
    • Block level alert class changed: .alert-block instead of .block-message
    • +
    +

    Progress bars

    +
      +
    • New in 2.0
    • +
    • Features multiple styles via classes, including striped and animated variations via CSS3
    • +
    +

    Miscellaneous components

    +
      +
    • Added documentation for the well component and the close icon (used in modals and alerts)
    • +
    +
    + + + + +
    + +
    + Heads up! We've rewritten just about everything for our plugins, so head on over to the Javascript page to learn more. +
    +

    Tooltips

    +
      +
    • The plugin method has been renamed from twipsy() to tooltip(), and the class name changed from twipsy to tooltip.
    • +
    • The placement option value that was below is now bottom, and above is now top.
    • +
    • The animate option was renamed to animation.
    • +
    • The html option was removed, as the tooltips default to allowing HTML now.
    • +
    +

    Popovers

    +
      +
    • Child elements now properly namespaced: .title to .popover-title, .inner to .popover-inner, and .content to .popover-content.
    • +
    • The placement option value that was below is now bottom, and above is now top.
    • +
    +

    New plugins

    + +
    + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/symposion_project/static/bootstrap/img/glyphicons-halflings-white.png b/symposion_project/static/bootstrap/img/glyphicons-halflings-white.png new file mode 100644 index 0000000000000000000000000000000000000000..3bf6484a29d8da269f9bc874b25493a45fae3bae GIT binary patch literal 8777 zcmZvC1yGz#v+m*$LXcp=A$ZWB0fL7wNbp_U*$~{_gL`my3oP#L!5tQYy99Ta`+g_q zKlj|KJ2f@c)ARJx{q*bbkhN_!|Wn*Vos8{TEhUT@5e;_WJsIMMcG5%>DiS&dv_N`4@J0cnAQ-#>RjZ z00W5t&tJ^l-QC*ST1-p~00u^9XJ=AUl7oW-;2a+x2k__T=grN{+1c4XK0ZL~^z^i$ zp&>vEhr@4fZWb380S18T&!0cQ3IKpHF)?v=b_NIm0Q>vwY7D0baZ)n z31Fa5sELUQARIVaU0nqf0XzT+fB_63aA;@<$l~wse|mcA;^G1TmX?-)e)jkGPfkuA z92@|!<>h5S_4f8QP-JRq>d&7)^Yin8l7K8gED$&_FaV?gY+wLjpoW%~7NDe=nHfMG z5DO3j{R9kv5GbssrUpO)OyvVrlx>u0UKD0i;Dpm5S5dY16(DL5l{ixz|mhJU@&-OWCTb7_%}8-fE(P~+XIRO zJU|wp1|S>|J3KrLcz^+v1f&BDpd>&MAaibR4#5A_4(MucZwG9E1h4@u0P@C8;oo+g zIVj7kfJi{oV~E(NZ*h(@^-(Q(C`Psb3KZ{N;^GB(a8NE*Vwc715!9 zr-H4Ao|T_c6+VT_JH9H+P3>iXSt!a$F`>s`jn`w9GZ_~B!{0soaiV|O_c^R2aWa%}O3jUE)WO=pa zs~_Wz08z|ieY5A%$@FcBF9^!1a}m5ks@7gjn;67N>}S~Hrm`4sM5Hh`q7&5-N{|31 z6x1{ol7BnskoViZ0GqbLa#kW`Z)VCjt1MysKg|rT zi!?s##Ck>8c zpi|>$lGlw#@yMNi&V4`6OBGJ(H&7lqLlcTQ&1zWriG_fL>BnFcr~?;E93{M-xIozQ zO=EHQ#+?<}%@wbWWv23#!V70h9MOuUVaU>3kpTvYfc|LBw?&b*89~Gc9i&8tlT#kF ztpbZoAzkdB+UTy=tx%L3Z4)I{zY(Kb)eg{InobSJmNwPZt$14aS-uc4eKuY8h$dtfyxu^a%zA)>fYI&)@ZXky?^{5>xSC?;w4r&td6vBdi%vHm4=XJH!3yL3?Ep+T5aU_>i;yr_XGq zxZfCzUU@GvnoIk+_Nd`aky>S&H!b*{A%L>?*XPAgWL(Vf(k7qUS}>Zn=U(ZfcOc{B z3*tOHH@t5Ub5D~#N7!Fxx}P2)sy{vE_l(R7$aW&CX>c|&HY+7};vUIietK%}!phrCuh+;C@1usp;XLU<8Gq8P!rEI3ieg#W$!= zQcZr{hp>8sF?k&Yl0?B84OneiQxef-4TEFrq3O~JAZR}yEJHA|Xkqd49tR&8oq{zP zY@>J^HBV*(gJvJZc_0VFN7Sx?H7#75E3#?N8Z!C+_f53YU}pyggxx1?wQi5Yb-_`I`_V*SMx5+*P^b=ec5RON-k1cIlsBLk}(HiaJyab0`CI zo0{=1_LO$~oE2%Tl_}KURuX<`+mQN_sTdM&* zkFf!Xtl^e^gTy6ON=&gTn6)$JHQq2)33R@_!#9?BLNq-Wi{U|rVX7Vny$l6#+SZ@KvQt@VYb%<9JfapI^b9j=wa+Tqb4ei;8c5 z&1>Uz@lVFv6T4Z*YU$r4G`g=91lSeA<=GRZ!*KTWKDPR}NPUW%peCUj`Ix_LDq!8| zMH-V`Pv!a~QkTL||L@cqiTz)*G-0=ytr1KqTuFPan9y4gYD5>PleK`NZB$ev@W%t= zkp)_=lBUTLZJpAtZg;pjI;7r2y|26-N7&a(hX|`1YNM9N8{>8JAuv}hp1v`3JHT-=5lbXpbMq7X~2J5Kl zh7tyU`_AusMFZ{ej9D;Uyy;SQ!4nwgSnngsYBwdS&EO3NS*o04)*juAYl;57c2Ly0(DEZ8IY?zSph-kyxu+D`tt@oU{32J#I{vmy=#0ySPK zA+i(A3yl)qmTz*$dZi#y9FS;$;h%bY+;StNx{_R56Otq+?pGe^T^{5d7Gs&?`_r`8 zD&dzOA|j8@3A&FR5U3*eQNBf<4^4W_iS_()*8b4aaUzfk2 zzIcMWSEjm;EPZPk{j{1>oXd}pXAj!NaRm8{Sjz!D=~q3WJ@vmt6ND_?HI~|wUS1j5 z9!S1MKr7%nxoJ3k`GB^7yV~*{n~O~n6($~x5Bu{7s|JyXbAyKI4+tO(zZYMslK;Zc zzeHGVl{`iP@jfSKq>R;{+djJ9n%$%EL()Uw+sykjNQdflkJZSjqV_QDWivbZS~S{K zkE@T^Jcv)Dfm93!mf$XYnCT--_A$zo9MOkPB6&diM8MwOfV?+ApNv`moV@nqn>&lv zYbN1-M|jc~sG|yLN^1R2=`+1ih3jCshg`iP&mY$GMTcY^W^T`WOCX!{-KHmZ#GiRH zYl{|+KLn5!PCLtBy~9i}`#d^gCDDx$+GQb~uc;V#K3OgbbOG0j5{BRG-si%Bo{@lB zGIt+Ain8^C`!*S0d0OSWVO+Z89}}O8aFTZ>p&k}2gGCV zh#<$gswePFxWGT$4DC^8@84_e*^KT74?7n8!$8cg=sL$OlKr&HMh@Rr5%*Wr!xoOl zo7jItnj-xYgVTX)H1=A2bD(tleEH57#V{xAeW_ezISg5OC zg=k>hOLA^urTH_e6*vSYRqCm$J{xo}-x3@HH;bsHD1Z`Pzvsn}%cvfw%Q(}h`Dgtb z0_J^niUmoCM5$*f)6}}qi(u;cPgxfyeVaaVmOsG<)5`6tzU4wyhF;k|~|x>7-2hXpVBpc5k{L4M`Wbe6Q?tr^*B z`Y*>6*&R#~%JlBIitlZ^qGe3s21~h3U|&k%%jeMM;6!~UH|+0+<5V-_zDqZQN79?n?!Aj!Nj`YMO9?j>uqI9-Tex+nJD z%e0#Yca6(zqGUR|KITa?9x-#C0!JKJHO(+fy@1!B$%ZwJwncQW7vGYv?~!^`#L~Um zOL++>4qmqW`0Chc0T23G8|vO)tK=Z2`gvS4*qpqhIJCEv9i&&$09VO8YOz|oZ+ubd zNXVdLc&p=KsSgtmIPLN69P7xYkYQ1vJ?u1g)T!6Ru`k2wkdj*wDC)VryGu2=yb0?F z>q~~e>KZ0d_#7f3UgV%9MY1}vMgF{B8yfE{HL*pMyhYF)WDZ^^3vS8F zGlOhs%g_~pS3=WQ#494@jAXwOtr^Y|TnQ5zki>qRG)(oPY*f}U_=ip_{qB0!%w7~G zWE!P4p3khyW-JJnE>eECuYfI?^d366Shq!Wm#x&jAo>=HdCllE$>DPO0N;y#4G)D2y#B@5=N=+F%Xo2n{gKcPcK2!hP*^WSXl+ut; zyLvVoY>VL{H%Kd9^i~lsb8j4>$EllrparEOJNT?Ym>vJa$(P^tOG)5aVb_5w^*&M0 zYOJ`I`}9}UoSnYg#E(&yyK(tqr^@n}qU2H2DhkK-`2He% zgXr_4kpXoQHxAO9S`wEdmqGU4j=1JdG!OixdqB4PPP6RXA}>GM zumruUUH|ZG2$bBj)Qluj&uB=dRb)?^qomw?Z$X%#D+Q*O97eHrgVB2*mR$bFBU`*} zIem?dM)i}raTFDn@5^caxE^XFXVhBePmH9fqcTi`TLaXiueH=@06sl}>F%}h9H_e9 z>^O?LxM1EjX}NVppaO@NNQr=AtHcH-BU{yBT_vejJ#J)l^cl69Z7$sk`82Zyw7Wxt z=~J?hZm{f@W}|96FUJfy65Gk8?^{^yjhOahUMCNNpt5DJw}ZKH7b!bGiFY9y6OY&T z_N)?Jj(MuLTN36ZCJ6I5Xy7uVlrb$o*Z%=-)kPo9s?<^Yqz~!Z* z_mP8(unFq65XSi!$@YtieSQ!<7IEOaA9VkKI?lA`*(nURvfKL8cX}-+~uw9|_5)uC2`ZHcaeX7L8aG6Ghleg@F9aG%X$#g6^yP5apnB>YTz&EfS{q z9UVfSyEIczebC)qlVu5cOoMzS_jrC|)rQlAzK7sfiW0`M8mVIohazPE9Jzn*qPt%6 zZL8RELY@L09B83@Be;x5V-IHnn$}{RAT#<2JA%ttlk#^(%u}CGze|1JY5MPhbfnYG zIw%$XfBmA-<_pKLpGKwbRF$#P;@_)ech#>vj25sv25VM$ouo)?BXdRcO{)*OwTw)G zv43W~T6ekBMtUD%5Bm>`^Ltv!w4~65N!Ut5twl!Agrzyq4O2Fi3pUMtCU~>9gt_=h-f% z;1&OuSu?A_sJvIvQ+dZNo3?m1%b1+s&UAx?8sUHEe_sB7zkm4R%6)<@oYB_i5>3Ip zIA+?jVdX|zL{)?TGpx+=Ta>G80}0}Ax+722$XFNJsC1gcH56{8B)*)eU#r~HrC&}` z|EWW92&;6y;3}!L5zXa385@?-D%>dSvyK;?jqU2t_R3wvBW;$!j45uQ7tyEIQva;Db}r&bR3kqNSh)Q_$MJ#Uj3Gj1F;)sO|%6z#@<+ zi{pbYsYS#u`X$Nf($OS+lhw>xgjos1OnF^$-I$u;qhJswhH~p|ab*nO>zBrtb0ndn zxV0uh!LN`&xckTP+JW}gznSpU492)u+`f{9Yr)js`NmfYH#Wdtradc0TnKNz@Su!e zu$9}G_=ku;%4xk}eXl>)KgpuT>_<`Ud(A^a++K&pm3LbN;gI}ku@YVrA%FJBZ5$;m zobR8}OLtW4-i+qPPLS-(7<>M{)rhiPoi@?&vDeVq5%fmZk=mDdRV>Pb-l7pP1y6|J z8I>sF+TypKV=_^NwBU^>4JJq<*14GLfM2*XQzYdlqqjnE)gZsPW^E@mp&ww* zW9i>XL=uwLVZ9pO*8K>t>vdL~Ek_NUL$?LQi5sc#1Q-f6-ywKcIT8Kw?C(_3pbR`e|)%9S-({if|E+hR2W!&qfQ&UiF^I!|M#xhdWsenv^wpKCBiuxXbnp85`{i|;BM?Ba`lqTA zyRm=UWJl&E{8JzYDHFu>*Z10-?#A8D|5jW9Ho0*CAs0fAy~MqbwYuOq9jjt9*nuHI zbDwKvh)5Ir$r!fS5|;?Dt>V+@F*v8=TJJF)TdnC#Mk>+tGDGCw;A~^PC`gUt*<(|i zB{{g{`uFehu`$fm4)&k7`u{xIV)yvA(%5SxX9MS80p2EKnLtCZ>tlX>*Z6nd&6-Mv$5rHD*db;&IBK3KH&M<+ArlGXDRdX1VVO4)&R$f4NxXI>GBh zSv|h>5GDAI(4E`@F?EnW zS>#c&Gw6~_XL`qQG4bK`W*>hek4LX*efn6|_MY+rXkNyAuu?NxS%L7~9tD3cn7&p( zCtfqe6sjB&Q-Vs7BP5+%;#Gk};4xtwU!KY0XXbmkUy$kR9)!~?*v)qw00!+Yg^#H> zc#8*z6zZo>+(bud?K<*!QO4ehiTCK&PD4G&n)Tr9X_3r-we z?fI+}-G~Yn93gI6F{}Dw_SC*FLZ)5(85zp4%uubtD)J)UELLkvGk4#tw&Tussa)mTD$R2&O~{ zCI3>fr-!-b@EGRI%g0L8UU%%u_<;e9439JNV;4KSxd|78v+I+8^rmMf3f40Jb}wEszROD?xBZu>Ll3;sUIoNxDK3|j3*sam2tC@@e$ z^!;+AK>efeBJB%ALsQ{uFui)oDoq()2USi?n=6C3#eetz?wPswc={I<8x=(8lE4EIsUfyGNZ{|KYn1IR|=E==f z(;!A5(-2y^2xRFCSPqzHAZn5RCN_bp22T(KEtjA(rFZ%>a4@STrHZflxKoqe9Z4@^ zM*scx_y73?Q{vt6?~WEl?2q*;@8 z3M*&@%l)SQmXkcUm)d@GT2#JdzhfSAP9|n#C;$E8X|pwD!r#X?0P>0ZisQ~TNqupW z*lUY~+ikD`vQb?@SAWX#r*Y+;=_|oacL$2CL$^(mV}aKO77pg}O+-=T1oLBT5sL2i z42Qth2+0@C`c+*D0*5!qy26sis<9a7>LN2{z%Qj49t z=L@x`4$ALHb*3COHoT?5S_c(Hs}g!V>W^=6Q0}zaubkDn)(lTax0+!+%B}9Vqw6{H zvL|BRM`O<@;eVi1DzM!tXtBrA20Ce@^Jz|>%X-t`vi-%WweXCh_LhI#bUg2*pcP~R z*RuTUzBKLXO~~uMd&o$v3@d0shHfUjC6c539PE6rF&;Ufa(Rw@K1*m7?f5)t`MjH0 z)_V(cajV5Am>f!kWcI@5rE8t6$S>5M=k=aRZROH6fA^jJp~2NlR4;Q2>L$7F#RT#9 z>4@1RhWG`Khy>P2j1Yx^BBL{S`niMaxlSWV-JBU0-T9zZ%>7mR3l$~QV$({o0;jTI ze5=cN^!Bc2bT|BcojXp~K#2cM>OTe*cM{Kg-j*CkiW)EGQot^}s;cy8_1_@JA0Whq zlrNr+R;Efa+`6N)s5rH*|E)nYZ3uqkk2C(E7@A|3YI`ozP~9Lexx#*1(r8luq+YPk z{J}c$s` zPM35Fx(YWB3Z5IYnN+L_4|jaR(5iWJi2~l&xy}aU7kW?o-V*6Av2wyZTG!E2KSW2* zGRLQkQU;Oz##ie-Z4fI)WSRxn$(ZcD;TL+;^r=a4(G~H3ZhK$lSXZj?cvyY8%d9JM zzc3#pD^W_QnWy#rx#;c&N@sqHhrnHRmj#i;s%zLm6SE(n&BWpd&f7>XnjV}OlZntI70fq%8~9<7 zMYaw`E-rp49-oC1N_uZTo)Cu%RR2QWdHpzQIcNsoDp`3xfP+`gI?tVQZ4X={qU?(n zV>0ASES^Xuc;9JBji{)RnFL(Lez;8XbB1uWaMp@p?7xhXk6V#!6B@aP4Rz7-K%a>i z?fvf}va_DGUXlI#4--`A3qK7J?-HwnG7O~H2;zR~RLW)_^#La!=}+>KW#anZ{|^D3 B7G?kd literal 0 HcmV?d00001 diff --git a/symposion_project/static/bootstrap/img/glyphicons-halflings.png b/symposion_project/static/bootstrap/img/glyphicons-halflings.png new file mode 100644 index 0000000000000000000000000000000000000000..79bc568c21395d5a5ceab62fadb04457094b2ac7 GIT binary patch literal 13826 zcma)jby!@B+o%-915yyF0YFyB4?Ne(CRg z-#O<#&wb84`D17H-t*49Gi$BAvS#fBDJx22pcA4aAt7PN%1EdpAw8RXk~3bSJRMO{ zLOPzl2q2PL5H+wV#M#IJgd}PLHU^Q&+8CLER6#~2F82K(0VJg7mlo<;5G{o-d_b@b zi_u>l7MP9Q6B-FgKp19c1hfJ{$c#Z|7Pf*EM~$r%WELiZ6q=k0YzlVbAae^DR|k-q ztD-v4)e6XKLLn?fCII7mGGGIO7?HtjtZg0nV1g9?*yVeY|6XRLAp1uJVkJoNAEdMt zl*z=w4j?j47B*%e8y7nn*Jl>?&uqM(d6~#Qv9YtUvVUS_<7Q@Os%DRy=VF;OnbPZB&l+~Sg=;$olKxc@r)Yv8{FpRTZ&JYl7zK5_7had2=;im|h^ zOS1E@^NNabNpOiuiHY)jW|#UmR@T-LVq^;h{dM{mYw=&$PyZv9Puu}y1OYp!gTdDS z?kdXWUuEt5GU<9?B8*-aqzJHUs!SW&!V4sCD=ZRit}=F za#FB9kud@CK`bEFpnvsHQESM*Bx{Smy@b!&$kyyB9n2;mQzNJ~ghI&7+QrV?0tmKs zG<38vvbHufF>%IThd>Rse#s3_OPbdF5nnAWt zL)hVIta5&^8bd;2&ytl8Rfo+Tcz~_-Bx?#ZE2<3oUBe})+zpAGX&=O$_aCJBN!CBt zv~LUxtg{dH^uI`jCU#YZa*6x&AyIg@k@bxImc$%rVne48BslqY$+TLFj(v37h7yfx z$^jmG#g_Rs?ETA?`?LMJ^OpUDIY(RQdGlgR?XG$OKf8PyqRZyid2g!3%@a^C1igpD z2NKzV@|1wiF}EtKQRH|$CJJ9)q3e}#g7m#Zl(d`W;iCBregW~kz}j^J z#1PLChA^$dal^V@@cK(w}dv%n2!w4^wV*y35J)-xE{$fXwc@pa}RzJm5M)#tr)iJZA7 zBA<^jjwJWvLx1>RPDIS^k*z$pgpiQZ-O2S}m#&N|A4@|nID3F1~ z+{<)-J1C8b8ezW2FI#gotv2}C#wQERQ(Bd4_} zR$QREVi8_9nE3}6@Vks1@*cVLJrSLt#`lb0$M?!xg%%C;C!jFg2$sX)U0bprNA043 zt1cd;7oNIanP3?<(O0mgAc`)87;35OB;`nL3-yw7Fq`<#Hqz;v+Mj? z%y|w07f93V#m`17f@xa3g&Kss@<20hE22A#Ba2fDjWQe?u<#pkgd4DKg$db>BIa`q zqEeb}1&O#H`nWg^GT=P^c&c$+@UcRMn~k-y&+aN^ic}0j)s9vGd$m}}SL4iw!tr4e z74SRhmFujYvTL$e!;=bil=GRdGp3UA1~R?@@XL?>oK21E-g3xj0Gu;SC|l|8wmd~d zG@8i53Tu3s9ldBp@%(!A6E=rZOl&LAvv1Nkj=ysQ(9(~g-8X6}A>#Y#1a(KQ1TAh( z`*b|k%zN|vOG$C7_4PTiy8Lhr&rZ~I!*iV zG+W%bI&HR#n{T~n|CLrV#?k5#Et)n4f;XdM7~@Er-K9uS8vPNM>uZUibWxth=wqXp zt{0wO*|bZs%9J3Y;Tj4)?d>OBZ>YUb@tFh)1KiKdOeB10_CBOTMml4P#hsP|NnH`$ zn8C$aG#8|gqT#i}vYTeH^aF(r1JFKcz$K3~!6}2FX0@^RHCL+33v-FhYXz#e!VN4~ z3pAY$kL`HvPAaz%ZKvX4N680T6G=`cF|!UT=iU?gUR}#z>rLnIjH4UiW&X!Z2Ih$B z#MDHe_%!Yd4!bTFMGeNcO(+vEfWe=Y&#$#Dh_vk`s>hf<^Bj2jofdTiH?Cvh55o&b zE2N(49<70oDa2DrZnfjbhn{Jl;CT6QCOL517jsNXxh ztk>S%Nl!1kKE!_Y1E%82zuk(#fmi4VMZZ|C9XG#t=_a%pE(?AS@K%j{n=lj?kEKY< zW|3b0>CWE2bkN^RapDK@3*dIhwI~%Mb87ZxnF|-bX;tNwFf}3s_Ti{S8}(TUA=c4( zY2Z!UZS&H=Pk;r%irg?jcz?{s!|V*#QA4{2Fzp37$r+}Z-K{*#DE7B^Inz!%Q9nU} zU%!E(b~61SJ_R5KSY88G!*+2Crm?Vp1DUFviD)lB1c&Atk+dP7K7{oK1?N#HTx(Jx zis^|e#sUW_TPZE3IGu1R+xV`&BV&1NNkrD4j;(NEKdkpSdz8YLZ}ya474taW7yY@8 zsA-+N{3&saE60RSnI802s?NYn0KiULv+`y9hNB!6%B_qCFHMhVOa;O!ge!LzPKbk( zbOnDN{s12ui~i)C55qt9+S4F%_rqna@M}~Kvh3z-^-K67%2T=8H8g<_=LYj#`6IF< z&#}t=5w#4@^{y}B4J8rm?|c7nu!l2bJZ`U-W4@aT)V{Bm!c%#8HewtNPwZ4>dYBdQ z$`?MJMLJt7`j`p7Y7C@WWmQu(B(vQ&FMa>ZZpX>;(|`+m?2Yl|fhX43DejM5BMl`? zr(v=9l4R8Y3}+Abj6x1X^T?$#`1;s>I24lFFFn~&HRgQK%%Ey(mn=20z;U>um1z~Q zJG*-wAw;tG!?{U#JnA5M5rX*u%NF+}y;0xPbTQppWv;^8{aGUxG$gD!0YAlLo;KuE zkFzemm@vHoQYYv<_b|t(esPHC%z-nLF5Q9^?&hl?0?g0d9hVSdDc=X~B?dQzaRfp; z+2*{_ss{}_cv+!%k7WX20;r5{GER*rd{={D1l}-^Se~*W+_M}?z+w9HX;SR@AB6by zI0}UM&nJY!1O!_&a8xRuf`=Drhp4bwFD4GN;7|wXEpdq}@{E+u#{VT}-UEwtWPkxKl^Wa8Qi?#AQLxY4w+?_Y4 zd1glMwHFc0bglfOS-7V_h zjsOP>)fG0TPo!`fIkeDn-b_WlxJH)NqQqX{Cjt1+PPI$%JFTSWT#$Mj_6O?PY#fK3 zMy2&j?Y~|hc!Xla$G$#xZ0%AyTx!yYt=5!)nk&0@J-$=t?&(X;8%~rQYD<{9lr1z zs@8X~WZq3R1+cmT>`KWeE&^_UF>|q&Ay^}*sN63yo7B9nz}D!eQt$6m26sKn>O$P zmvsnQ7b9nJQ46`zs$s*Wtto!ux2}?)U%;Z5%hb7!$w!&8C`>TRG+*DdD0JLss5Xff zBThm&kGp*Qxmrsc3GjV@6TVB6)l|r!wyRJP)U%eM@Of-k4FDYmUY)1+7EUyRGbs_` zleaIf78kfz<{vx`Ls^b4Ogd8_rSR#I2AH%NK)|Vfh#}z~2k0bJcEvc$3He?p;bGVK zyam;#Nl5X&J8j^k<~QS18sq4NPR$kE>m%=`^Ki#+ieKpZYF?TTM#Jv80{<7eYn$&q2aN=p)lq6fG9}Dv2}g_RSVx*Iv-0C}kEWsUw>e$24l?hUH3zqG z2Sa%=_ql^t*`t3yW7`PZ(-yol6mNfiUV1c7e)%BgzOh%HQQd^uq9gC3O*vPSi&V!$ zuJ-gy-6_@)r?@+~#wK_V|QHgllM9B^dZanlnPLZqhL-@Wql1PDLO_j>7Nz?o z+_&sbFV42Gr7019rPl3IUH2}h2Wl+=p46k?>x70Pnt9Gn_CduyDht`=S4b}9&F^387k|mAZg2^t9(aD+I+W{ z#iMaSJ%Slg$*$}d;|(Q|7`BKm3z9) zh-*c!-WX<4{kD>(FE8TvP+#HUL}QrAKt*0vVL7!~ovM)?Ur`?N{))Ew;yk>PkfjG- z*)^I$qo~mV?U!~Gwi(1*M)0+vT9Jy~`kGC^1<}kh2R4PgR^?53j%>|Ns{2kn=ewGn zvPvguwaHo(xrDKI-r{x~q$onf~4u$MK|{q*`g)sDyNO(})q!R?7xZH;c=m6iWiHEU8Q0KT-e zKaAgECVApd!3(FjK2!e|a^g^-5f7L7jB^GFCrwQ_*B`o?=jeoDN_*x+cXrv8gf$36NQ*!QC!Kwg5~wLak^RyUvu(CifB7CA>(1lu6}+@1^DvB!>VYXX?9Ys*9wd&0abG}7TGJ`WsH;FX_s&}n4v(1m|Q)++R8J>#?XO`$8g+3q` zwN~X&6{@){!8Q1(2!in4P8(_gYuOhhFGZ;=C-6kTb%~vBQQ*b-=z*J+>E;6ujm;wX zvb?kY(oC=+ca4)i4a#h@{dTzWSLS3ag^66Gpkn{ke!AC9A{1jMRP%OcQ)<<@nxJH} zZIr?|jBinPoiR)snBOcecjcb@Wuh3my1iVRzl-u;gB}~Rjhub`?Cfu)nPL3L+b$kL zO32z2XK-0_shy`%ZT9<2V<1qI5Rel|E7W{`Hg#M|m&O0`Ua-&p;v}tapS>wTE*On` z756q!EO*AN?oxlV&@ybUeVWd1q~Tg`kpqG}F@V;VsN#&)R^`V00X5}(4*PmNqShEg zQih?Ga1nmgvx@-!Wngeg;A+L{F-(i zf_X7=?WU?j|23>ePpP8OODXHU69Lw_MmSudzHtic8)MWn1BPdI_Ae4ykPB0u9il*G zJ?$Q@);~I`)dd=AQuaxcTe2HSse|E|ii5U_*5>3~bz~#PL%91W(Nyd|=|ZA6*w`c7 z$R1sRD@XhF^&4gJ#exDQRqq3%$Y|oPc!wXV-=n37^UJ=Olj%RP#gEAol|$!AAbjxW zXq&hxEZQyPL4JOa6I*343W#)9&u%!GDhw_3B>yJ7)O`Ae76GRZenb(|eWOMZU_spF zuD{--T)B0<*4E?|ri0F<=p!twyj!hH;HlUN0Htt?hj8zO#!~F83W|K9Lvq z3{RaoPbjaDFu@z{^qW3cjj7kS$GR|;9I%R~LZ@6(ENvrteZFbkkow-9p%qZBx>J+M zq8}TEyApxpU@n((iw0bRrJvc6Cd$y8wbf4?-w4%S5$Slysc^DTKW~+Y`!?zI;_DZL zV9KO0`~P=A@%O2`KlPzF{xwsO>z5=mqo0Z23o-D!NekrdbEa^%TfV56v|FDM?4cKX z@rrk@JJ?1_5irzO66hc^C*{*Ke&o=Ijw!R*ZAgtQC0ezeL17SocQu_m!6VUsNTcVG zpwRaCZCIJ=OR~@li`X(c8LO9k&wjr&0Gd_GRou<{3Hu`Css}PU72iy4PZtFd(l9VK zR)fk*&dPTy&yMX{o8@~bPnX0_Q@UX-RN+o|sC$;fpA|xTEugMj7@)yJ{4@bO3x^+O zH0OTqp82(iEah+>0QWS z$@9x&MNFG_ayE3OJxi@l$%9i2{OAD1go7t5}Sv8p*L*?_XV-Inr zpe~mOfBekpsM*iZA4B0U-_aDDuQGQ>$du+c-pHfXyBaLv@T`?*-je(+>E!q1bXa1q z14-*PWvM+oFg(z{YlRS2em5Pw1U1&De`{t$Pg={frAk6|^cDRB$0e*ut zvJ=N0<2rG{&|2ECVoU=~V0R9rfUWk0Z${R3(A&#kkMCPoz`s?k7N+_8!1v32J*zyO zR9Lv8#NK_E; zsf^8eBN5l`rT5}^m`=Z(Oaw_(G`KLa6xX%V@W0keWi;An4+N4QThS_k{n&Vyk{0!?N_d)(8r)?>J|F`-ZusfRTzNO)+h%L=-)$92e&Ck?1oAE(~~ z$-n~o0g*n;RB*mqiaAn=Wlm0w2D6Yu&4fY#;MU1bvU(~NK6m1FUoPk+w;|b?nzGkO z_PUIl=pfDRhrLvm<;sb9>BFB~Sc4oJ;hS&xb#O~;Q7(2b8< zQ9Hg8isf_ddK#6OY$>r#Kxz@D+gtkY>hy|#o8Z-=^bH`o)WbuhhdK98@PHbw2Zt=7 zV$-oYeC$U<;|pnaU4187;%~hxdnq*JOnEGam?8hex6Iy=ZlWGzZv-4 zoJ{KX4x(J5=P>qor+5;Qvhp3GFBpXJ9fO3crB!vqua&Y$iFJdsGsQL15;##Wtx)a! zYY)JHGBW`d%x6ZI`{f6_r^+OdBbZk{<-B0y4iS|--^SLDWVMu&VT?M2Z|8*E=pfeq z);Kt;$?dDKuIJvdZG|d_=QWvbk?X!+UMjWng_S4uk_M}7f`V03>h!f-=Qxpm9ReU7 za!V9@Dytw&Y;Dn_tG@+O7`;DiSse1^ilx|o^~@+CRqBxKgXtuFTdkV9s}V3?Sy6{S z*XctI(Eyb3h^4g}R#0C=Al$1x3GX$~3fA}}eX>>DF+LFj4zJ()a-xd1d6P?W{`m*D z*x%43iLpP6D8xOj1Z<^h)%1C*{`|uBM zAKe~zJa>JT4Tqn|wxn>-+P9_i;yHBP@*ap6jMJgu7>d2GIq{>J`g;o%tKlmpM-RrSw{_pAKK; zSq)!`7M=VE#*z4?xSugikUTPD}y7GXhB{U`6@}s8z0d@C`F9EQ3#s|A3?{zk{KOin$?&5UgsTdnL zO1i!hQhbL?LiIIX*RA*iV$~) zB>zWXKyBeJC4}W_3SGU)PQseJzO;g~99>U&xx8@V2Qp$StzgO_?GxT!9UmQV2vt-^ zkab;==s?$tI#Akh4J+G|pAPYZQ5vA(8|@a9T2-p=)uPN{@6f@tmW11S)1s z!h%|zyG6Dc);F%IdWaK*t#r*khD51^8Ay)ixzUtt=#AX2VmjE zOFg-|2AdD>SmMSf?bo9uRB)zYaT{m9I%7Vs)$dLGX>bj<#I2?S8OUQRh(mJrJhADZ zT_^gL-3m0*JIokIbOUyiA83%98nW2{Wp2BW5akVi?klylc_3UwSpIlPTwb zEIG-t+EJ;a3(OZ-sGt+R_j^Z;x|qvjBr|7-{wn4kOG&^GRt$u`kMx zzV;Zy-UA7<xMJg(rd2`sKuS9&FoYuUoug>t*^~eJTjg>pWcBUABu-7%@{xM zICt)A_$aq9KQ1!{${`~7GXd+8ZDmu`rjx$oiC@GP<}zwn_dR8&M)WQdC&iw3E)YGG z>3e7ZNZUGzmYhW2?kKOPphuHB2q3zn7e!n3V8t*?@hpE5fc7snCI0l&iE)SiOs(W%=b1^y8b;aHjB&KaO|McF*t%v`zlW*&h5@1@_C^ zu@=`+#rV2TS56EeCh=>uP<-lPc^}fc208qOOb9~TKo;7L zA~1!rYZOt)&{UFvJI5a$VIW+Rn=eIQsZ^sU)8hNGK};PpknpE84hIhht07)(ER+4_ zxLhMx$;116i@tQodN*XTcFS{`!fPjk0n} z1udu3=k`@uaQK?j)YF!Z2n=fc zY`~>$*#BZX+mGk=DFM0Z|L3%DK(H(w+__!4UF`kf9Jf(YzE zR+p>6%a^g;g${|zdmK6-Gj(({7pl{TV*3&Z!Tg4cKvV0j;*Hb(Z#qmw#wdm`wZ8ts zjIUMJ`h#Vh4=S1zDw~a^H)q+6{ z#Hz!oYPE7ZFi~~AG7n#q$;s}pANs@VyV5vhU2&d`=@Es*pQh}pgHHCW`KB+GEa9ck zW`9DlW`Wvi6+8Jp#bM-ebD50CjykM&Y5Nb{=n_#L!>gatGhc`j`D$a>B*m5@1=_tY z1!7V55YfU?hSlU@@flw?^BFXCnLzGQ5nOAvVvjQP>otW|mQj7Pc1evAEdaVt_O7si zLf)Opv3>@Ky-^Y?)9yR;H}8pcbX&{bu?-8JE^rhUOvU2ko_d9PU&9pXO^>cRZ#zZo zCkq39jb4}nCKp>1oQXcr)#BC}eH;uS!al|lo`b0S;{)B1C!B9NGJ7sRRf8u~;@IH-gDB{~GwmgyVn+go-vI%&pi z&YpjGP!eesJV1P}>w0bDVqj#o(Td$rcY=Dy(vmsW4Lu7vblFZ1AkwFt&8yEeH+$MF z-`f?Kpo$}2=fdkh7scLN3X|LFczR*OC>3vQN$>T`HJ{7Et7(nPTo6piDNA7Mqp=3RT0d>DNW?+-b;wgbWc@xKrOgn@*hcG0Bl300~zM z1cqJaF;{x*c%r%A4-dBquj5*G&bu!gKwoO_nS;LQT^1W`?RvhSP_8$3==>+aY-PTt z>bq-vSj!54>+X4cy9uFc7n4e89$B@NcVD5A-ZJOxHgc`}0Xekmrnv zFXt>J(de%xG=HqM%#sdc`1MGQF^WDoQiWxMaI(4dHmX&4!LlBo`(Of>F#wiHG2!fZ zvB{2Q#2#f}GF24rrVMQV1q+OtDek8cd8z74b#rGk91~90FBtkjwVnDn53id&|26Z`rO1<>1bMNki zIionO>*HS1J4(aUYgwsF#kSB3LoKM6=_L4awnOEIti-PdFWHKvSHkYopzzkmO{#f! zBCp*D{8xF0vlect8R3v&sfl^TuDXSf&P%wC74{#9?N5X!pC24A7h4?)2V-9N|c{C;w5wl|z8<2X0es$`*M5j(oF{0r&32 z`U~-Q8qfbA;nM54%Pd-|nK@0LdSA=5KyqV*g)A>?W!gQiNj|kKfej`z+TWeH!`Hpg z4x)z(>^8nLqTC<9RW5iJvCjWHv7}1afGXDDjvlcDu^s2txL;E`C?VN3k?3wy4?Rg4 znmrvze0;v4z1-miFC~klv>fjZbDDi1Sb3^nk~4(v>AQ0kEgcS!BT@@JFn156+M2%+9d~_aj?sf*d7G$H=KZ+;~_5OXv~HkLZB`D1C0=ySHh6%$1n_d9W{Z z&m>oGu#UW7!b=#@N;S*cUt1_&zh6G6Pp&1MS&qW^nP8>f9Vydi7A|Q=nJs1UqHe~% zo8!0@d07eTQ)zRgq2lRbPX=U9X)}<}K~;F^6$@(xJg{M=ogF(BJK$Va())Mp;3$9P zb1zLrct_$*_$9%}3(n0%gfU}7>#&k71PXy}!LO#cR3p!xc`NR8zFQw{A$DKq6Oeuw z;ZC#iv;VMss-vmXR&ElJ5dxInx1l|}uEaG5i80LcV~4TkD%!RUD@5+~l+kiSOpS0( zJ-iwpm}JCR@Sy?BW$_tvO%K-fQUFm-UCi;NK$-MsQoWnQXO+(qUd!{zFS!JepUfxD zmmoFLB>{OkHam{gP2#GXZaq&=xio1Kop4j#`v}Qz6U1D0dc!ks4ikn{Y6ti#ZeqYgF+ z0jQIIQUvnReW)_53Z+>u>)Lw((~vxa6AFrr%d}nI!o7{spwl@ir`qH9j7o=6JXYD| zsp>X-yI}#VHc1S{c}{E|acAh>zF%*}R`4 zM+xtI9F&>Xs(IJooneFYo;l{cU*-2DT~2TUm;QwTC9RXwFSwqHS82mcZmDj8xVn(+ zhjg5e>~E9?3K-*RvJ)uCq0UIdRl~D85$B^#Nph2%)6FN1>6!u6+%oE;F=J5B=`W{` zL<6;Qu8Pq|0+tS%yP10nmIgUV^r%Hyjyo|#W0hIVR`qiw@r)O7`K*l4Ma$$u=XQc$ z^#q3KLI6#VtuIxX4b;#_lx#bieZGmNS8?8jxHeTsE52O+t4ih5iw}=p7@DZs*!jev z{i#&SO#GsN^zjC{G<~Nu|2>~?q2Z@)UnNDB&2?wHQCn?p9v7YpNRPW1 zWM9#550th&<~(gv_Sok5g3e8tnTzkV2|gxe#kE{nUT{aP8n5=}qg4mCp!JuEcz=Ht z&y3I7&uxdKU%P7D+5NV%Ok}hj@mimhKlv+R1bd8?zb|20JJD?Q?=vElsc#c2!VJmq z&W&vW+CaWx`FG1VfMsEf)`p}0TTes}|I{%_X{vj;}wDxh!zb$|D=4e756H z7dp8?Ul~60@eSwbY!+Crzr*mLMSqj6ofW&@mJB8fIGm%=B28`wnbx8F8YnigN|~sB z)ie@y57LaLin3|;u`JzFDsS0JCrG!Z4g+Nd*=-JadG7AesG5y*rMun?dHJhkCMW_% zCal ztKYWr0+ECjETkqk!9jw#hv?D8BB>sVztP<9s&fY3kg7O(65kdl!pnzWhNl>mkKBOP z9wGNuspXb&`T7gZLu#Y670KyIg|D$foZ^6CxK^NurqGjTAORgOb-D`MnNNRW8Xw=g z8)`pHz^^@&DlTfcLBTlT7>c#c{d1Rs^_EM?6rpWz{8ZrZ3&E3&F=tOC;zGnc>6#NjY1JQMZ!+8#j*!95<*U{5CE&b@6WIV= z`L8w`z0>!&Y?@c9IUIXc)WVTOpF}^_=xxWoJZGv|AT41`N;g@MZhWeGa@pxlgGji8 zR3?G5Rb3_fNj8zy!w)Nl>leQXO0(UI&kdY+N-i0G7Z%q|`!Oo^N%yZLWCBLMop?7) z`#d}b79JtI-AG(Fx@TIi!6u-D3-^!Dlae;43Yp1%MZ9XATQ^#ln*F21RntEEXZFkB z`SV+qf>QWy^~x~X!#q&<(a*gW8Npq#5?J;o^D1<$rOl;PQ2b4cBvE-R>e$@3lbK}qIv=--S zEeI|aC9>S#V3jN>JO#=lUV`ja4_n@N34a(b9DsX~5L~fhJpe=AgZbr~VX+0ZQY{x^ z(k)K(A0~mNkFt zA8e)|)*K0!nFmOg^$p@)RlWA0%f_jul)Ga}wOT-A_SHF)3v!5Ywj5XdkuSTR2s1b> z60lzNZMkjx`b~_wapzIo-Eku>H`NV#XFRgb*F@gDM&yDMiwX=D%B zmzw)_!+aX+zV8mY9at~%ev^rb^(0rwKSp(3};ZpMvxEwD2OjDaVA6Ry$0&8rtZV3pHxzf$? zzAjYXA~;b|XCc95MUR%dTT@Z>0}uY+8y=;wW1vky{pKP;cOV}6&6tV$I;>`FK z906wPfPrz9t=;&M?(Wwdm z0?&;KzLQk84srC-9#ap*I_9GregSZjm<$6oiZ>h3ACEnS7A^faq{fPmD!rT69qQG% zRVF#+RDZ(-Ue?g!$?;NT#p=8F8SV%EZ5ry{-5J)UN6Jj~-klPlw7o4w&aUp0pn@@) zM(jp3}a6rP@=sC1ZvM zV)jL-HO|elZ@x|hHXkrmGu9uS2%=Jqa zgIqpCmA+s{=XewW1!LqE)3%%mIO z(8jQbk;xApH`iS0;h7M96j^_3N=#|-xP-=*>3=obmL(W)Au>jdy3E<UjD;R zOI^Va(lW(qH`MjF&}RqCOifgKKA39SANA9=Qv4z+3Qey|4BJBzex_v%9=l5D-xJaG`?IF#?EKul!io4R+`>v>t_65&VXqROwiMr@*>SD)gNHL4^Ml5(vgCqodJjd$~XNSPzt@GziL=mgy;Y+qBZh&1qKxwm{>$kMCyH2rN?F2%^-bX#z9QBC| zNx?aIaFXEMqAKsMWDfWB@Pt3@$5LZ%DVDT70icB1BXM`F_#4rYqTkpk%wf tVgFekgZM{XhA!KlmFcR^%iaf4$rSfz)nO-hfB%&wE2$_^D)!aq{{YOB6}SKZ literal 0 HcmV?d00001 diff --git a/symposion_project/static/bootstrap/js/.jshintrc b/symposion_project/static/bootstrap/js/.jshintrc new file mode 100644 index 00000000..bbac349e --- /dev/null +++ b/symposion_project/static/bootstrap/js/.jshintrc @@ -0,0 +1,10 @@ +{ + "validthis": true, + "laxcomma" : true, + "laxbreak" : true, + "browser" : true, + "debug" : true, + "boss" : true, + "expr" : true, + "asi" : true +} \ No newline at end of file diff --git a/symposion_project/static/bootstrap/js/README.md b/symposion_project/static/bootstrap/js/README.md new file mode 100644 index 00000000..b7927ba6 --- /dev/null +++ b/symposion_project/static/bootstrap/js/README.md @@ -0,0 +1,112 @@ +## 2.0 BOOTSTRAP JS PHILOSOPHY +These are the high-level design rules which guide the development of Bootstrap's plugin apis. + +--- + +### DATA-ATTRIBUTE API + +We believe you should be able to use all plugins provided by Bootstrap purely through the markup API without writing a single line of JavaScript. This is Bootstrap's first class API. + +We acknowledge that this isn't always the most performant and it may sometimes be desirable to turn this functionality off altogether. Therefore, as of 2.0 we provide the ability to disable the data attribute API by unbinding all events on the body namespaced with `'data-api'`. This looks like this: + + $('body').off('.data-api') + +To target a specific plugin, just include the plugins name as a namespace along with the data-api namespace like this: + + $('body').off('.alert.data-api') + +--- + +### PROGRAMATIC API + +We also believe you should be able to use all plugins provided by Bootstrap purely through the JavaScript API. + +All public APIs should be single, chainable methods, and return the collection acted upon. + + $(".btn.danger").button("toggle").addClass("fat") + +All methods should accept an optional options object, a string which targets a particular method, or null which initiates the default behavior: + + $("#myModal").modal() // initialized with defaults + $("#myModal").modal({ keyboard: false }) // initialized with no keyboard + $("#myModal").modal('show') // initializes and invokes show immediately + +--- + +### OPTIONS + +Options should be sparse and add universal value. We should pick the right defaults. + +All plugins should have a default object which can be modified to affect all instances' default options. The defaults object should be available via `$.fn.plugin.defaults`. + + $.fn.modal.defaults = { … } + +An options definition should take the following form: + + *noun*: *adjective* - describes or modifies a quality of an instance + +Examples: + + backdrop: true + keyboard: false + placement: 'top' + +--- + +### EVENTS + +All events should have an infinitive and past participle form. The infinitive is fired just before an action takes place, the past participle on completion of the action. + + show | shown + hide | hidden + +All infinitive events should provide preventDefault functionality. This provides the abililty to stop the execution of an action. + + $('#myModal').on('show', function (e) { + if (!data) return e.preventDefault() // stops modal from being shown + }) + +--- + +### CONSTRUCTORS + +Each plugin should expose its raw constructor on a `Constructor` property -- accessed in the following way: + + + $.fn.popover.Constructor + +--- + +### DATA ACCESSOR + +Each plugin stores a copy of the invoked class on an object. This class instance can be accessed directly through jQuery's data API like this: + + $('[rel=popover]').data('popover') instanceof $.fn.popover.Constructor + +--- + +### DATA ATTRIBUTES + +Data attributes should take the following form: + +- data-{{verb}}={{plugin}} - defines main interaction +- data-target || href^=# - defined on "control" element (if element controls an element other than self) +- data-{{noun}} - defines class instance options + +Examples: + + // control other targets + data-toggle="modal" data-target="#foo" + data-toggle="collapse" data-target="#foo" data-parent="#bar" + + // defined on element they control + data-spy="scroll" + + data-dismiss="modal" + data-dismiss="alert" + + data-toggle="dropdown" + + data-toggle="button" + data-toggle="buttons-checkbox" + data-toggle="buttons-radio" \ No newline at end of file diff --git a/symposion_project/static/bootstrap/js/bootstrap-alert.js b/symposion_project/static/bootstrap/js/bootstrap-alert.js new file mode 100644 index 00000000..57890a9a --- /dev/null +++ b/symposion_project/static/bootstrap/js/bootstrap-alert.js @@ -0,0 +1,90 @@ +/* ========================================================== + * bootstrap-alert.js v2.0.4 + * http://twitter.github.com/bootstrap/javascript.html#alerts + * ========================================================== + * Copyright 2012 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ========================================================== */ + + +!function ($) { + + "use strict"; // jshint ;_; + + + /* ALERT CLASS DEFINITION + * ====================== */ + + var dismiss = '[data-dismiss="alert"]' + , Alert = function (el) { + $(el).on('click', dismiss, this.close) + } + + Alert.prototype.close = function (e) { + var $this = $(this) + , selector = $this.attr('data-target') + , $parent + + if (!selector) { + selector = $this.attr('href') + selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7 + } + + $parent = $(selector) + + e && e.preventDefault() + + $parent.length || ($parent = $this.hasClass('alert') ? $this : $this.parent()) + + $parent.trigger(e = $.Event('close')) + + if (e.isDefaultPrevented()) return + + $parent.removeClass('in') + + function removeElement() { + $parent + .trigger('closed') + .remove() + } + + $.support.transition && $parent.hasClass('fade') ? + $parent.on($.support.transition.end, removeElement) : + removeElement() + } + + + /* ALERT PLUGIN DEFINITION + * ======================= */ + + $.fn.alert = function (option) { + return this.each(function () { + var $this = $(this) + , data = $this.data('alert') + if (!data) $this.data('alert', (data = new Alert(this))) + if (typeof option == 'string') data[option].call($this) + }) + } + + $.fn.alert.Constructor = Alert + + + /* ALERT DATA-API + * ============== */ + + $(function () { + $('body').on('click.alert.data-api', dismiss, Alert.prototype.close) + }) + +}(window.jQuery); \ No newline at end of file diff --git a/symposion_project/static/bootstrap/js/bootstrap-button.js b/symposion_project/static/bootstrap/js/bootstrap-button.js new file mode 100644 index 00000000..7f187be6 --- /dev/null +++ b/symposion_project/static/bootstrap/js/bootstrap-button.js @@ -0,0 +1,96 @@ +/* ============================================================ + * bootstrap-button.js v2.0.4 + * http://twitter.github.com/bootstrap/javascript.html#buttons + * ============================================================ + * Copyright 2012 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============================================================ */ + + +!function ($) { + + "use strict"; // jshint ;_; + + + /* BUTTON PUBLIC CLASS DEFINITION + * ============================== */ + + var Button = function (element, options) { + this.$element = $(element) + this.options = $.extend({}, $.fn.button.defaults, options) + } + + Button.prototype.setState = function (state) { + var d = 'disabled' + , $el = this.$element + , data = $el.data() + , val = $el.is('input') ? 'val' : 'html' + + state = state + 'Text' + data.resetText || $el.data('resetText', $el[val]()) + + $el[val](data[state] || this.options[state]) + + // push to event loop to allow forms to submit + setTimeout(function () { + state == 'loadingText' ? + $el.addClass(d).attr(d, d) : + $el.removeClass(d).removeAttr(d) + }, 0) + } + + Button.prototype.toggle = function () { + var $parent = this.$element.parent('[data-toggle="buttons-radio"]') + + $parent && $parent + .find('.active') + .removeClass('active') + + this.$element.toggleClass('active') + } + + + /* BUTTON PLUGIN DEFINITION + * ======================== */ + + $.fn.button = function (option) { + return this.each(function () { + var $this = $(this) + , data = $this.data('button') + , options = typeof option == 'object' && option + if (!data) $this.data('button', (data = new Button(this, options))) + if (option == 'toggle') data.toggle() + else if (option) data.setState(option) + }) + } + + $.fn.button.defaults = { + loadingText: 'loading...' + } + + $.fn.button.Constructor = Button + + + /* BUTTON DATA-API + * =============== */ + + $(function () { + $('body').on('click.button.data-api', '[data-toggle^=button]', function ( e ) { + var $btn = $(e.target) + if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn') + $btn.button('toggle') + }) + }) + +}(window.jQuery); \ No newline at end of file diff --git a/symposion_project/static/bootstrap/js/bootstrap-carousel.js b/symposion_project/static/bootstrap/js/bootstrap-carousel.js new file mode 100644 index 00000000..551de589 --- /dev/null +++ b/symposion_project/static/bootstrap/js/bootstrap-carousel.js @@ -0,0 +1,169 @@ +/* ========================================================== + * bootstrap-carousel.js v2.0.4 + * http://twitter.github.com/bootstrap/javascript.html#carousel + * ========================================================== + * Copyright 2012 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ========================================================== */ + + +!function ($) { + + "use strict"; // jshint ;_; + + + /* CAROUSEL CLASS DEFINITION + * ========================= */ + + var Carousel = function (element, options) { + this.$element = $(element) + this.options = options + this.options.slide && this.slide(this.options.slide) + this.options.pause == 'hover' && this.$element + .on('mouseenter', $.proxy(this.pause, this)) + .on('mouseleave', $.proxy(this.cycle, this)) + } + + Carousel.prototype = { + + cycle: function (e) { + if (!e) this.paused = false + this.options.interval + && !this.paused + && (this.interval = setInterval($.proxy(this.next, this), this.options.interval)) + return this + } + + , to: function (pos) { + var $active = this.$element.find('.active') + , children = $active.parent().children() + , activePos = children.index($active) + , that = this + + if (pos > (children.length - 1) || pos < 0) return + + if (this.sliding) { + return this.$element.one('slid', function () { + that.to(pos) + }) + } + + if (activePos == pos) { + return this.pause().cycle() + } + + return this.slide(pos > activePos ? 'next' : 'prev', $(children[pos])) + } + + , pause: function (e) { + if (!e) this.paused = true + clearInterval(this.interval) + this.interval = null + return this + } + + , next: function () { + if (this.sliding) return + return this.slide('next') + } + + , prev: function () { + if (this.sliding) return + return this.slide('prev') + } + + , slide: function (type, next) { + var $active = this.$element.find('.active') + , $next = next || $active[type]() + , isCycling = this.interval + , direction = type == 'next' ? 'left' : 'right' + , fallback = type == 'next' ? 'first' : 'last' + , that = this + , e = $.Event('slide') + + this.sliding = true + + isCycling && this.pause() + + $next = $next.length ? $next : this.$element.find('.item')[fallback]() + + if ($next.hasClass('active')) return + + if ($.support.transition && this.$element.hasClass('slide')) { + this.$element.trigger(e) + if (e.isDefaultPrevented()) return + $next.addClass(type) + $next[0].offsetWidth // force reflow + $active.addClass(direction) + $next.addClass(direction) + this.$element.one($.support.transition.end, function () { + $next.removeClass([type, direction].join(' ')).addClass('active') + $active.removeClass(['active', direction].join(' ')) + that.sliding = false + setTimeout(function () { that.$element.trigger('slid') }, 0) + }) + } else { + this.$element.trigger(e) + if (e.isDefaultPrevented()) return + $active.removeClass('active') + $next.addClass('active') + this.sliding = false + this.$element.trigger('slid') + } + + isCycling && this.cycle() + + return this + } + + } + + + /* CAROUSEL PLUGIN DEFINITION + * ========================== */ + + $.fn.carousel = function (option) { + return this.each(function () { + var $this = $(this) + , data = $this.data('carousel') + , options = $.extend({}, $.fn.carousel.defaults, typeof option == 'object' && option) + if (!data) $this.data('carousel', (data = new Carousel(this, options))) + if (typeof option == 'number') data.to(option) + else if (typeof option == 'string' || (option = options.slide)) data[option]() + else if (options.interval) data.cycle() + }) + } + + $.fn.carousel.defaults = { + interval: 5000 + , pause: 'hover' + } + + $.fn.carousel.Constructor = Carousel + + + /* CAROUSEL DATA-API + * ================= */ + + $(function () { + $('body').on('click.carousel.data-api', '[data-slide]', function ( e ) { + var $this = $(this), href + , $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7 + , options = !$target.data('modal') && $.extend({}, $target.data(), $this.data()) + $target.carousel(options) + e.preventDefault() + }) + }) + +}(window.jQuery); \ No newline at end of file diff --git a/symposion_project/static/bootstrap/js/bootstrap-collapse.js b/symposion_project/static/bootstrap/js/bootstrap-collapse.js new file mode 100644 index 00000000..fbc915b9 --- /dev/null +++ b/symposion_project/static/bootstrap/js/bootstrap-collapse.js @@ -0,0 +1,157 @@ +/* ============================================================= + * bootstrap-collapse.js v2.0.4 + * http://twitter.github.com/bootstrap/javascript.html#collapse + * ============================================================= + * Copyright 2012 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============================================================ */ + + +!function ($) { + + "use strict"; // jshint ;_; + + + /* COLLAPSE PUBLIC CLASS DEFINITION + * ================================ */ + + var Collapse = function (element, options) { + this.$element = $(element) + this.options = $.extend({}, $.fn.collapse.defaults, options) + + if (this.options.parent) { + this.$parent = $(this.options.parent) + } + + this.options.toggle && this.toggle() + } + + Collapse.prototype = { + + constructor: Collapse + + , dimension: function () { + var hasWidth = this.$element.hasClass('width') + return hasWidth ? 'width' : 'height' + } + + , show: function () { + var dimension + , scroll + , actives + , hasData + + if (this.transitioning) return + + dimension = this.dimension() + scroll = $.camelCase(['scroll', dimension].join('-')) + actives = this.$parent && this.$parent.find('> .accordion-group > .in') + + if (actives && actives.length) { + hasData = actives.data('collapse') + if (hasData && hasData.transitioning) return + actives.collapse('hide') + hasData || actives.data('collapse', null) + } + + this.$element[dimension](0) + this.transition('addClass', $.Event('show'), 'shown') + this.$element[dimension](this.$element[0][scroll]) + } + + , hide: function () { + var dimension + if (this.transitioning) return + dimension = this.dimension() + this.reset(this.$element[dimension]()) + this.transition('removeClass', $.Event('hide'), 'hidden') + this.$element[dimension](0) + } + + , reset: function (size) { + var dimension = this.dimension() + + this.$element + .removeClass('collapse') + [dimension](size || 'auto') + [0].offsetWidth + + this.$element[size !== null ? 'addClass' : 'removeClass']('collapse') + + return this + } + + , transition: function (method, startEvent, completeEvent) { + var that = this + , complete = function () { + if (startEvent.type == 'show') that.reset() + that.transitioning = 0 + that.$element.trigger(completeEvent) + } + + this.$element.trigger(startEvent) + + if (startEvent.isDefaultPrevented()) return + + this.transitioning = 1 + + this.$element[method]('in') + + $.support.transition && this.$element.hasClass('collapse') ? + this.$element.one($.support.transition.end, complete) : + complete() + } + + , toggle: function () { + this[this.$element.hasClass('in') ? 'hide' : 'show']() + } + + } + + + /* COLLAPSIBLE PLUGIN DEFINITION + * ============================== */ + + $.fn.collapse = function (option) { + return this.each(function () { + var $this = $(this) + , data = $this.data('collapse') + , options = typeof option == 'object' && option + if (!data) $this.data('collapse', (data = new Collapse(this, options))) + if (typeof option == 'string') data[option]() + }) + } + + $.fn.collapse.defaults = { + toggle: true + } + + $.fn.collapse.Constructor = Collapse + + + /* COLLAPSIBLE DATA-API + * ==================== */ + + $(function () { + $('body').on('click.collapse.data-api', '[data-toggle=collapse]', function ( e ) { + var $this = $(this), href + , target = $this.attr('data-target') + || e.preventDefault() + || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') //strip for ie7 + , option = $(target).data('collapse') ? 'toggle' : $this.data() + $(target).collapse(option) + }) + }) + +}(window.jQuery); \ No newline at end of file diff --git a/symposion_project/static/bootstrap/js/bootstrap-dropdown.js b/symposion_project/static/bootstrap/js/bootstrap-dropdown.js new file mode 100644 index 00000000..454a9684 --- /dev/null +++ b/symposion_project/static/bootstrap/js/bootstrap-dropdown.js @@ -0,0 +1,100 @@ +/* ============================================================ + * bootstrap-dropdown.js v2.0.4 + * http://twitter.github.com/bootstrap/javascript.html#dropdowns + * ============================================================ + * Copyright 2012 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============================================================ */ + + +!function ($) { + + "use strict"; // jshint ;_; + + + /* DROPDOWN CLASS DEFINITION + * ========================= */ + + var toggle = '[data-toggle="dropdown"]' + , Dropdown = function (element) { + var $el = $(element).on('click.dropdown.data-api', this.toggle) + $('html').on('click.dropdown.data-api', function () { + $el.parent().removeClass('open') + }) + } + + Dropdown.prototype = { + + constructor: Dropdown + + , toggle: function (e) { + var $this = $(this) + , $parent + , selector + , isActive + + if ($this.is('.disabled, :disabled')) return + + selector = $this.attr('data-target') + + if (!selector) { + selector = $this.attr('href') + selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7 + } + + $parent = $(selector) + $parent.length || ($parent = $this.parent()) + + isActive = $parent.hasClass('open') + + clearMenus() + + if (!isActive) $parent.toggleClass('open') + + return false + } + + } + + function clearMenus() { + $(toggle).parent().removeClass('open') + } + + + /* DROPDOWN PLUGIN DEFINITION + * ========================== */ + + $.fn.dropdown = function (option) { + return this.each(function () { + var $this = $(this) + , data = $this.data('dropdown') + if (!data) $this.data('dropdown', (data = new Dropdown(this))) + if (typeof option == 'string') data[option].call($this) + }) + } + + $.fn.dropdown.Constructor = Dropdown + + + /* APPLY TO STANDARD DROPDOWN ELEMENTS + * =================================== */ + + $(function () { + $('html').on('click.dropdown.data-api', clearMenus) + $('body') + .on('click.dropdown', '.dropdown form', function (e) { e.stopPropagation() }) + .on('click.dropdown.data-api', toggle, Dropdown.prototype.toggle) + }) + +}(window.jQuery); \ No newline at end of file diff --git a/symposion_project/static/bootstrap/js/bootstrap-modal.js b/symposion_project/static/bootstrap/js/bootstrap-modal.js new file mode 100644 index 00000000..38fd0c84 --- /dev/null +++ b/symposion_project/static/bootstrap/js/bootstrap-modal.js @@ -0,0 +1,218 @@ +/* ========================================================= + * bootstrap-modal.js v2.0.4 + * http://twitter.github.com/bootstrap/javascript.html#modals + * ========================================================= + * Copyright 2012 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ========================================================= */ + + +!function ($) { + + "use strict"; // jshint ;_; + + + /* MODAL CLASS DEFINITION + * ====================== */ + + var Modal = function (content, options) { + this.options = options + this.$element = $(content) + .delegate('[data-dismiss="modal"]', 'click.dismiss.modal', $.proxy(this.hide, this)) + } + + Modal.prototype = { + + constructor: Modal + + , toggle: function () { + return this[!this.isShown ? 'show' : 'hide']() + } + + , show: function () { + var that = this + , e = $.Event('show') + + this.$element.trigger(e) + + if (this.isShown || e.isDefaultPrevented()) return + + $('body').addClass('modal-open') + + this.isShown = true + + escape.call(this) + backdrop.call(this, function () { + var transition = $.support.transition && that.$element.hasClass('fade') + + if (!that.$element.parent().length) { + that.$element.appendTo(document.body) //don't move modals dom position + } + + that.$element + .show() + + if (transition) { + that.$element[0].offsetWidth // force reflow + } + + that.$element.addClass('in') + + transition ? + that.$element.one($.support.transition.end, function () { that.$element.trigger('shown') }) : + that.$element.trigger('shown') + + }) + } + + , hide: function (e) { + e && e.preventDefault() + + var that = this + + e = $.Event('hide') + + this.$element.trigger(e) + + if (!this.isShown || e.isDefaultPrevented()) return + + this.isShown = false + + $('body').removeClass('modal-open') + + escape.call(this) + + this.$element.removeClass('in') + + $.support.transition && this.$element.hasClass('fade') ? + hideWithTransition.call(this) : + hideModal.call(this) + } + + } + + + /* MODAL PRIVATE METHODS + * ===================== */ + + function hideWithTransition() { + var that = this + , timeout = setTimeout(function () { + that.$element.off($.support.transition.end) + hideModal.call(that) + }, 500) + + this.$element.one($.support.transition.end, function () { + clearTimeout(timeout) + hideModal.call(that) + }) + } + + function hideModal(that) { + this.$element + .hide() + .trigger('hidden') + + backdrop.call(this) + } + + function backdrop(callback) { + var that = this + , animate = this.$element.hasClass('fade') ? 'fade' : '' + + if (this.isShown && this.options.backdrop) { + var doAnimate = $.support.transition && animate + + this.$backdrop = $(' - -
    -
    - -

    {% trans "Teams" %}

    + + {% available_teams as available_teams %} + {% if user.memberships.exists or available_teams %} +
    +
    + +

    {% trans "Teams" %}

    +
    + +
    + {% if user.memberships.exists %} +

    Your Teams

    + + {% for membership in user.memberships.all %} + + + + + + {% endfor %} +
    + {{ membership.team.name }} + {% if membership.team.description %}
    {{ membership.team.description }}{% endif %} +
    + {{ membership.get_state_display }} + + {% if membership.state == "manager" or user.is_staff %} + {% if membership.team.applicants %}{{ membership.team.applicants.count }} applicant{{ membership.team.applicants.count|pluralize }}{% endif %} + {% endif %} +
    + {% endif %} + {% if available_teams %} +

    Available Teams

    + + {% for team in available_teams %} + + + + + {% endfor %} +
    + {{ team }} + {% if team.description %}
    {{ team.description }}{% endif %} +
    + {{ team.get_access_display }} +
    + {% endif %} +
    - -
    - {% if user.memberships.exists %} -

    Your Teams

    - - {% for membership in user.memberships.all %} - - - - - - {% endfor %} -
    - {{ membership.team.name }} - {% if membership.team.description %}
    {{ membership.team.description }}{% endif %} -
    - {{ membership.get_state_display }} - - {% if membership.state == "manager" or user.is_staff %} - {% if membership.team.applicants %}{{ membership.team.applicants.count }} applicant{{ membership.team.applicants.count|pluralize }}{% endif %} - {% endif %} -
    - {% endif %} - {% available_teams as available_teams %} - {% if available_teams %} -

    Available Teams

    - - {% for team in available_teams %} - - - - - - {% endfor %} -
    - {{ team }} - {% if team.description %}
    {{ team.description }}{% endif %} -
    - {{ team.get_access_display }} -
    - {% endif %} -
    -
    + {% endif %} {% endblock %} From 70351b37140e864c6882b32cede52c69a3328764 Mon Sep 17 00:00:00 2001 From: Brian Rosner Date: Wed, 22 Aug 2012 16:28:42 -0600 Subject: [PATCH 227/751] moved Audience Level to a more logical spot in _proposal_fields --- symposion/templates/proposals/_proposal_fields.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/symposion/templates/proposals/_proposal_fields.html b/symposion/templates/proposals/_proposal_fields.html index 4f1816a8..3c85820c 100644 --- a/symposion/templates/proposals/_proposal_fields.html +++ b/symposion/templates/proposals/_proposal_fields.html @@ -7,6 +7,9 @@
    {% trans "Track" %}
    {{ proposal.track }} 
    +
    {% trans "Audience Level" %}
    +
    {{ proposal.get_audience_level_display }} 
    + {% if proposal.additional_speakers.all %}
    {% trans "Addl Speakers" %}
    @@ -31,9 +34,6 @@
    {% trans "Notes" %}
    {{ proposal.additional_notes|safe }} 
    -
    {% trans "Audience Level" %}
    -
    {{ proposal.get_audience_level_display }} 
    -
    {% trans "Documents" %}
    {% if proposal.supporting_documents.exists %} From be877c19d64c8d186ab6f552c6be447c679ec384 Mon Sep 17 00:00:00 2001 From: Brian Rosner Date: Wed, 22 Aug 2012 16:29:12 -0600 Subject: [PATCH 228/751] spelt out Additional --- symposion/templates/proposals/_proposal_fields.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/symposion/templates/proposals/_proposal_fields.html b/symposion/templates/proposals/_proposal_fields.html index 3c85820c..2739a3d3 100644 --- a/symposion/templates/proposals/_proposal_fields.html +++ b/symposion/templates/proposals/_proposal_fields.html @@ -11,7 +11,7 @@
    {{ proposal.get_audience_level_display }} 
    {% if proposal.additional_speakers.all %} -
    {% trans "Addl Speakers" %}
    +
    {% trans "Additional Speakers" %}
    {% for speaker in proposal.additional_speakers.all %}
  • From 32155ff1ea5308f3608c4a9780cd5b98a9dd202a Mon Sep 17 00:00:00 2001 From: Brian Rosner Date: Wed, 22 Aug 2012 16:32:47 -0600 Subject: [PATCH 229/751] added Speaker Bio to proposal display fields --- symposion/templates/proposals/_proposal_fields.html | 3 +++ 1 file changed, 3 insertions(+) diff --git a/symposion/templates/proposals/_proposal_fields.html b/symposion/templates/proposals/_proposal_fields.html index 2739a3d3..a1040625 100644 --- a/symposion/templates/proposals/_proposal_fields.html +++ b/symposion/templates/proposals/_proposal_fields.html @@ -34,6 +34,9 @@
    {% trans "Notes" %}
    {{ proposal.additional_notes|safe }} 
    +
    {% trans "Speaker Bio" %}
    +
    {{ proposal.speaker.biography|safe }} 
    +
    {% trans "Documents" %}
    {% if proposal.supporting_documents.exists %} From 8bd7998034b5fa8c55c2d9450a8ee78b4677f799 Mon Sep 17 00:00:00 2001 From: Luke Hatcher Date: Fri, 24 Aug 2012 11:46:17 -0400 Subject: [PATCH 230/751] add proposal leave template --- symposion/templates/proposals/proposal_leave.html | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 symposion/templates/proposals/proposal_leave.html diff --git a/symposion/templates/proposals/proposal_leave.html b/symposion/templates/proposals/proposal_leave.html new file mode 100644 index 00000000..3041ae1d --- /dev/null +++ b/symposion/templates/proposals/proposal_leave.html @@ -0,0 +1,13 @@ +{% extends "proposals/base.html" %} + +{% load uni_form_tags %} + +{% block body %} +

    Leaving {{ proposal.title }}

    + +
    + {% csrf_token %} +

    Are you sure you wish to leave as a speaker on {{ proposal.title }}?

    + +
    +{% endblock %} From 86b698a228ddf1309e8f2006726724af05c5fca1 Mon Sep 17 00:00:00 2001 From: Luke Hatcher Date: Sat, 25 Aug 2012 19:04:32 -0400 Subject: [PATCH 231/751] bump version --- symposion/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/symposion/__init__.py b/symposion/__init__.py index 6046eebc..10a02826 100644 --- a/symposion/__init__.py +++ b/symposion/__init__.py @@ -1 +1 @@ -__version__ = "1.0b1.dev11" +__version__ = "1.0b1.dev12" From 1cb22cea953de84dcf38b8458161f37a58a751a1 Mon Sep 17 00:00:00 2001 From: Luke Hatcher Date: Sat, 25 Aug 2012 19:52:07 -0400 Subject: [PATCH 232/751] restrict status view to reviewers --- symposion/reviews/views.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/symposion/reviews/views.py b/symposion/reviews/views.py index 683b6a1a..7da34e4b 100644 --- a/symposion/reviews/views.py +++ b/symposion/reviews/views.py @@ -275,6 +275,9 @@ def review_delete(request, pk): @login_required def review_status(request, section_slug=None, key=None): + if not request.user.has_perm("reviews.can_review_%s" % section_slug): + return access_not_permitted(request) + VOTE_THRESHOLD = settings.SYMPOSION_VOTE_THRESHOLD ctx = { From d427ef2941694093fb43634aa0033186ed567426 Mon Sep 17 00:00:00 2001 From: Luke Hatcher Date: Sat, 25 Aug 2012 19:52:58 -0400 Subject: [PATCH 233/751] proposal_count is unused --- symposion/reviews/views.py | 1 - 1 file changed, 1 deletion(-) diff --git a/symposion/reviews/views.py b/symposion/reviews/views.py index 7da34e4b..62316e0f 100644 --- a/symposion/reviews/views.py +++ b/symposion/reviews/views.py @@ -308,7 +308,6 @@ def review_status(request, section_slug=None, key=None): ctx.update({ "key": key, "proposals": proposals_generator(request, proposals[key], check_speaker=not admin), - "proposal_count": proposals[key].count(), }) else: ctx["proposals"] = proposals From 6ee3ff5d4599d487a9bd4b5394156c84cf3672e6 Mon Sep 17 00:00:00 2001 From: Luke Hatcher Date: Sat, 25 Aug 2012 19:54:17 -0400 Subject: [PATCH 234/751] use generator to ensure proposals are filtered by speaker --- symposion/reviews/views.py | 7 +++++-- symposion/templates/reviews/review_stats.html | 10 +++++----- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/symposion/reviews/views.py b/symposion/reviews/views.py index 62316e0f..a62b1988 100644 --- a/symposion/reviews/views.py +++ b/symposion/reviews/views.py @@ -301,13 +301,16 @@ def review_status(request, section_slug=None, key=None): # proposals with fewer than VOTE_THRESHOLD reviews "too_few": queryset.filter(result__vote_count__lt=VOTE_THRESHOLD).order_by("result__vote_count"), } - + admin = request.user.has_perm("reviews.can_manage_%s" % section_slug) + for status in proposals: + proposals[status] = list(proposals_generator(request, proposals[status], check_speaker=not admin)) + if key: ctx.update({ "key": key, - "proposals": proposals_generator(request, proposals[key], check_speaker=not admin), + "proposals": proposals[key], }) else: ctx["proposals"] = proposals diff --git a/symposion/templates/reviews/review_stats.html b/symposion/templates/reviews/review_stats.html index 6451457f..8f4a09cb 100644 --- a/symposion/templates/reviews/review_stats.html +++ b/symposion/templates/reviews/review_stats.html @@ -43,35 +43,35 @@
    Positive - {{ proposals.positive.count }} + {{ proposals.positive|length }}
    proposals with at least {{ vote_threshold }} vote{{ vote_threshold|pluralize }} and at least one +1 and no −1s
    Negative - {{ proposals.negative.count }} + {{ proposals.negative|length }}
    proposals with at least {{ vote_threshold }} vote{{ vote_threshold|pluralize }} and at least one −1 and no +1s
    Indifferent - {{ proposals.indifferent.count }} + {{ proposals.indifferent|length }}
    proposals with at least {{ vote_threshold }} vote{{ vote_threshold|pluralize }} and neither a +1 or a −1
    Controversial - {{ proposals.controversial.count }} + {{ proposals.controversial|length }}
    proposals with at least {{ vote_threshold }} vote{{ vote_threshold|pluralize }} and both a +1 and −1
    Too Few Reviews - {{ proposals.too_few.count }} + {{ proposals.too_few|length }}
    proposals with fewer than {{ vote_threshold }} vote{{ vote_threshold|pluralize }} From 54991cd4d187ce027e1dfc122abe789611be9e58 Mon Sep 17 00:00:00 2001 From: Luke Hatcher Date: Sat, 25 Aug 2012 20:00:13 -0400 Subject: [PATCH 235/751] only run generator on proposals with a selected status --- symposion/reviews/views.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/symposion/reviews/views.py b/symposion/reviews/views.py index a62b1988..9a60c152 100644 --- a/symposion/reviews/views.py +++ b/symposion/reviews/views.py @@ -305,6 +305,8 @@ def review_status(request, section_slug=None, key=None): admin = request.user.has_perm("reviews.can_manage_%s" % section_slug) for status in proposals: + if key and key != status: + continue proposals[status] = list(proposals_generator(request, proposals[status], check_speaker=not admin)) if key: From 2da59c13bf4ff062fd6bd360ce2eadb1baaf9131 Mon Sep 17 00:00:00 2001 From: Luke Hatcher Date: Sat, 25 Aug 2012 20:01:48 -0400 Subject: [PATCH 236/751] bump version --- symposion/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/symposion/__init__.py b/symposion/__init__.py index 10a02826..ea02d4ee 100644 --- a/symposion/__init__.py +++ b/symposion/__init__.py @@ -1 +1 @@ -__version__ = "1.0b1.dev12" +__version__ = "1.0b1.dev13" From 6bfd617e99a2d91b88665ce267acfae8273b6861 Mon Sep 17 00:00:00 2001 From: Luke Hatcher Date: Mon, 27 Aug 2012 14:12:19 -0400 Subject: [PATCH 237/751] add message to reviewers when proposal is updated --- symposion/proposals/views.py | 10 ++++++++++ .../templates/emails/proposal_updated/message.html | 8 ++++++++ .../templates/emails/proposal_updated/subject.txt | 1 + 3 files changed, 19 insertions(+) create mode 100644 symposion/templates/emails/proposal_updated/message.html create mode 100644 symposion/templates/emails/proposal_updated/subject.txt diff --git a/symposion/proposals/views.py b/symposion/proposals/views.py index e7d65f44..fe7aec96 100644 --- a/symposion/proposals/views.py +++ b/symposion/proposals/views.py @@ -187,6 +187,16 @@ def proposal_edit(request, pk): form = form_class(request.POST, instance=proposal) if form.is_valid(): form.save() + if hasattr(proposal, "reviews"): + for review in proposal.reviews.distinct("user"): + ctx = { + "user": request.user, + "proposal": proposal, + } + send_email( + [review.user.email], "proposal_updated", + context=ctx + ) messages.success(request, "Proposal updated.") return redirect("proposal_detail", proposal.pk) else: diff --git a/symposion/templates/emails/proposal_updated/message.html b/symposion/templates/emails/proposal_updated/message.html new file mode 100644 index 00000000..f2724074 --- /dev/null +++ b/symposion/templates/emails/proposal_updated/message.html @@ -0,0 +1,8 @@ +{% load account_tags %} +

    + {% user_display user %} has made changes to {{ proposal.title }} which you have previously reviewed. +

    +

    + {% url review_detail proposal.pk as detail_url %} + View the latest version of the proposal online at http://{{ current_site }}{{ detail_url }} +

    \ No newline at end of file diff --git a/symposion/templates/emails/proposal_updated/subject.txt b/symposion/templates/emails/proposal_updated/subject.txt new file mode 100644 index 00000000..ba9117c1 --- /dev/null +++ b/symposion/templates/emails/proposal_updated/subject.txt @@ -0,0 +1 @@ +{% load account_tags %}"{{ proposal.title }}" has been updated by {% user_display user %} \ No newline at end of file From 17e9016981122383b9163bf915ed66f62292304b Mon Sep 17 00:00:00 2001 From: Luke Hatcher Date: Mon, 27 Aug 2012 14:32:59 -0400 Subject: [PATCH 238/751] add datatables and tabletools --- .../datatables/js/dataTables.bootstrap.js | 138 + .../datatables/js/jquery.dataTables.min.js | 154 ++ symposion/static/tabletools/js/TableTools.js | 2424 +++++++++++++++++ .../static/tabletools/js/TableTools.min.js | 76 + .../static/tabletools/js/TableTools.min.js.gz | Bin 0 -> 8702 bytes .../static/tabletools/js/ZeroClipboard.js | 367 +++ .../static/tabletools/swf/copy_csv_xls.swf | Bin 0 -> 2140 bytes .../tabletools/swf/copy_csv_xls_pdf.swf | Bin 0 -> 58816 bytes 8 files changed, 3159 insertions(+) create mode 100644 symposion/static/datatables/js/dataTables.bootstrap.js create mode 100644 symposion/static/datatables/js/jquery.dataTables.min.js create mode 100755 symposion/static/tabletools/js/TableTools.js create mode 100644 symposion/static/tabletools/js/TableTools.min.js create mode 100644 symposion/static/tabletools/js/TableTools.min.js.gz create mode 100755 symposion/static/tabletools/js/ZeroClipboard.js create mode 100644 symposion/static/tabletools/swf/copy_csv_xls.swf create mode 100644 symposion/static/tabletools/swf/copy_csv_xls_pdf.swf diff --git a/symposion/static/datatables/js/dataTables.bootstrap.js b/symposion/static/datatables/js/dataTables.bootstrap.js new file mode 100644 index 00000000..b58e5f77 --- /dev/null +++ b/symposion/static/datatables/js/dataTables.bootstrap.js @@ -0,0 +1,138 @@ +$(function() { + /* Default class modification */ + $.extend( $.fn.dataTableExt.oStdClasses, { + "sWrapper": "dataTables_wrapper form-inline" + } ); + + /* API method to get paging information */ + $.fn.dataTableExt.oApi.fnPagingInfo = function ( oSettings ) + { + return { + "iStart": oSettings._iDisplayStart, + "iEnd": oSettings.fnDisplayEnd(), + "iLength": oSettings._iDisplayLength, + "iTotal": oSettings.fnRecordsTotal(), + "iFilteredTotal": oSettings.fnRecordsDisplay(), + "iPage": Math.ceil( oSettings._iDisplayStart / oSettings._iDisplayLength ), + "iTotalPages": Math.ceil( oSettings.fnRecordsDisplay() / oSettings._iDisplayLength ) + }; + } + + /* Bootstrap style pagination control */ + $.extend( $.fn.dataTableExt.oPagination, { + "bootstrap": { + "fnInit": function( oSettings, nPaging, fnDraw ) { + var oLang = oSettings.oLanguage.oPaginate; + var fnClickHandler = function ( e ) { + e.preventDefault(); + if ( oSettings.oApi._fnPageChange(oSettings, e.data.action) ) { + fnDraw( oSettings ); + } + }; + + $(nPaging).addClass('pagination').append( + '' + ); + var els = $('a', nPaging); + $(els[0]).bind( 'click.DT', { action: "previous" }, fnClickHandler ); + $(els[1]).bind( 'click.DT', { action: "next" }, fnClickHandler ); + }, + + "fnUpdate": function ( oSettings, fnDraw ) { + var iListLength = 5; + var oPaging = oSettings.oInstance.fnPagingInfo(); + var an = oSettings.aanFeatures.p; + var i, j, sClass, iStart, iEnd, iHalf=Math.floor(iListLength/2); + + if ( oPaging.iTotalPages < iListLength) { + iStart = 1; + iEnd = oPaging.iTotalPages; + } + else if ( oPaging.iPage <= iHalf ) { + iStart = 1; + iEnd = iListLength; + } else if ( oPaging.iPage >= (oPaging.iTotalPages-iHalf) ) { + iStart = oPaging.iTotalPages - iListLength + 1; + iEnd = oPaging.iTotalPages; + } else { + iStart = oPaging.iPage - iHalf + 1; + iEnd = iStart + iListLength - 1; + } + + for ( i=0, iLen=an.length ; i'+j+'
  • ') + .insertBefore( $('li:last', an[i])[0] ) + .bind('click', function (e) { + e.preventDefault(); + oSettings._iDisplayStart = (parseInt($('a', this).text(),10)-1) * oPaging.iLength; + fnDraw( oSettings ); + } ); + } + + // Add / remove disabled classes from the static elements + if ( oPaging.iPage === 0 ) { + $('li:first', an[i]).addClass('disabled'); + } else { + $('li:first', an[i]).removeClass('disabled'); + } + + if ( oPaging.iPage === oPaging.iTotalPages-1 || oPaging.iTotalPages === 0 ) { + $('li:last', an[i]).addClass('disabled'); + } else { + $('li:last', an[i]).removeClass('disabled'); + } + } + } + } + } ); + + $.extend($.fn.dataTableExt.oStdClasses, { + "sWrapper": "dataTables_wrapper form-inline" + }); + + /* + * TableTools Bootstrap compatibility + * Required TableTools 2.1+ + */ + if ( $.fn.DataTable.TableTools ) { + // Set the classes that TableTools uses to something suitable for Bootstrap + $.extend( true, $.fn.DataTable.TableTools.classes, { + "container": "DTTT btn-group", + "buttons": { + "normal": "btn", + "disabled": "disabled" + }, + "collection": { + "container": "DTTT_dropdown dropdown-menu", + "buttons": { + "normal": "", + "disabled": "disabled" + } + }, + "print": { + "info": "DTTT_print_info modal" + }, + "select": { + "row": "active" + } + } ); + + // Have the collection use a bootstrap compatible dropdown + $.extend( true, $.fn.DataTable.TableTools.DEFAULTS.oTags, { + "collection": { + "container": "ul", + "button": "li", + "liner": "a" + } + } ); + } +}); diff --git a/symposion/static/datatables/js/jquery.dataTables.min.js b/symposion/static/datatables/js/jquery.dataTables.min.js new file mode 100644 index 00000000..ce18c935 --- /dev/null +++ b/symposion/static/datatables/js/jquery.dataTables.min.js @@ -0,0 +1,154 @@ +/* + * File: jquery.dataTables.min.js + * Version: 1.9.2 + * Author: Allan Jardine (www.sprymedia.co.uk) + * Info: www.datatables.net + * + * Copyright 2008-2012 Allan Jardine, all rights reserved. + * + * This source file is free software, under either the GPL v2 license or a + * BSD style license, available at: + * http://datatables.net/license_gpl2 + * http://datatables.net/license_bsd + * + * This source file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details. + */ +(function(i,V,l,n){var j=function(e){function o(a,b){var c=j.defaults.columns,d=a.aoColumns.length,c=i.extend({},j.models.oColumn,c,{sSortingClass:a.oClasses.sSortable,sSortingClassJUI:a.oClasses.sSortJUI,nTh:b?b:l.createElement("th"),sTitle:c.sTitle?c.sTitle:b?b.innerHTML:"",aDataSort:c.aDataSort?c.aDataSort:[d],mDataProp:c.mDataProp?c.oDefaults:d});a.aoColumns.push(c);if(a.aoPreSearchCols[d]===n||null===a.aoPreSearchCols[d])a.aoPreSearchCols[d]=i.extend({},j.models.oSearch);else if(c=a.aoPreSearchCols[d], +c.bRegex===n&&(c.bRegex=!0),c.bSmart===n&&(c.bSmart=!0),c.bCaseInsensitive===n)c.bCaseInsensitive=!0;r(a,d,null)}function r(a,b,c){b=a.aoColumns[b];c!==n&&null!==c&&(c.sType!==n&&(b.sType=c.sType,b._bAutoType=!1),i.extend(b,c),p(b,c,"sWidth","sWidthOrig"),c.iDataSort!==n&&(b.aDataSort=[c.iDataSort]),p(b,c,"aDataSort"));b.fnGetData=W(b.mDataProp);b.fnSetData=ta(b.mDataProp);a.oFeatures.bSort||(b.bSortable=!1);!b.bSortable||-1==i.inArray("asc",b.asSorting)&&-1==i.inArray("desc",b.asSorting)?(b.sSortingClass= +a.oClasses.sSortableNone,b.sSortingClassJUI=""):b.bSortable||-1==i.inArray("asc",b.asSorting)&&-1==i.inArray("desc",b.asSorting)?(b.sSortingClass=a.oClasses.sSortable,b.sSortingClassJUI=a.oClasses.sSortJUI):-1!=i.inArray("asc",b.asSorting)&&-1==i.inArray("desc",b.asSorting)?(b.sSortingClass=a.oClasses.sSortableAsc,b.sSortingClassJUI=a.oClasses.sSortJUIAscAllowed):-1==i.inArray("asc",b.asSorting)&&-1!=i.inArray("desc",b.asSorting)&&(b.sSortingClass=a.oClasses.sSortableDesc,b.sSortingClassJUI=a.oClasses.sSortJUIDescAllowed)} +function k(a){if(!1===a.oFeatures.bAutoWidth)return!1;ba(a);for(var b=0,c=a.aoColumns.length;bm[f])d(a.aoColumns.length+m[f],b[g]);else if("string"===typeof m[f]){e=0;for(s=a.aoColumns.length;eb&&a[d]--; -1!=c&&a.splice(c,1)}function R(a,b,c){var d=a.aoColumns[c];return d.fnRender({iDataRow:b,iDataColumn:c,oSettings:a,aData:a.aoData[b]._aData,mDataProp:d.mDataProp},w(a,b,c,"display"))}function ca(a,b){var c=a.aoData[b],d;if(null===c.nTr){c.nTr=l.createElement("tr");c.nTr._DT_RowIndex=b;c._aData.DT_RowId&&(c.nTr.id=c._aData.DT_RowId);c._aData.DT_RowClass&&i(c.nTr).addClass(c._aData.DT_RowClass);for(var g=0,f=a.aoColumns.length;g=a.fnRecordsDisplay()?0:a.iInitDisplayStart,a.iInitDisplayStart=-1,A(a));if(a.bDeferLoading)a.bDeferLoading=!1,a.iDraw++;else if(a.oFeatures.bServerSide){if(!a.bDestroying&&!wa(a))return}else a.iDraw++;if(0!==a.aiDisplay.length){var h=a._iDisplayStart;d=a._iDisplayEnd;a.oFeatures.bServerSide&&(h=0,d=a.aoData.length);for(;h
    ")[0];a.nTable.parentNode.insertBefore(b,a.nTable);a.nTableWrapper=i('
    ')[0];a.nTableReinsertBefore=a.nTable.nextSibling;for(var c=a.nTableWrapper,d=a.sDom.split(""),g,f,h,e,s,m,o,k=0;k
    ")[0];s=d[k+1];if("'"==s||'"'==s){m="";for(o=2;d[k+o]!=s;)m+=d[k+o],o++;"H"==m?m=a.oClasses.sJUIHeader:"F"==m&&(m=a.oClasses.sJUIFooter); +-1!=m.indexOf(".")?(s=m.split("."),e.id=s[0].substr(1,s[0].length-1),e.className=s[1]):"#"==m.charAt(0)?e.id=m.substr(1,m.length-1):e.className=m;k+=o}c.appendChild(e);c=e}else if(">"==h)c=c.parentNode;else if("l"==h&&a.oFeatures.bPaginate&&a.oFeatures.bLengthChange)g=ya(a),f=1;else if("f"==h&&a.oFeatures.bFilter)g=za(a),f=1;else if("r"==h&&a.oFeatures.bProcessing)g=Aa(a),f=1;else if("t"==h)g=Ba(a),f=1;else if("i"==h&&a.oFeatures.bInfo)g=Ca(a),f=1;else if("p"==h&&a.oFeatures.bPaginate)g=Da(a),f=1; +else if(0!==j.ext.aoFeatures.length){e=j.ext.aoFeatures;o=0;for(s=e.length;o'): +""===c?'':c+' ',d=l.createElement("div");d.className=a.oClasses.sFilter;d.innerHTML="";a.aanFeatures.f||(d.id=a.sTableId+"_filter");c=i('input[type="text"]',d);d._DT_Input=c[0];c.val(b.sSearch.replace('"',"""));c.bind("keyup.DT",function(){for(var c=a.aanFeatures.f,d=this.value===""?"":this.value,h=0,e=c.length;h=b.length)a.aiDisplay.splice(0,a.aiDisplay.length),a.aiDisplay=a.aiDisplayMaster.slice();else if(a.aiDisplay.length==a.aiDisplayMaster.length||g.sSearch.length>b.length||1==c||0!==b.indexOf(g.sSearch)){a.aiDisplay.splice(0,a.aiDisplay.length);ja(a,1);for(b=0;b/g,""):"string"===typeof a?a.replace(/[\r\n]/g," "):a}function na(a){return a.replace(RegExp("(\\/|\\.|\\*|\\+|\\?|\\||\\(|\\)|\\[|\\]|\\{|\\}|\\\\|\\$|\\^|\\-)", +"g"),"\\$1")}function Ca(a){var b=l.createElement("div");b.className=a.oClasses.sInfo;a.aanFeatures.i||(a.aoDrawCallback.push({fn:Ja,sName:"information"}),b.id=a.sTableId+"_info");a.nTable.setAttribute("aria-describedby",a.sTableId+"_info");return b}function Ja(a){if(a.oFeatures.bInfo&&0!==a.aanFeatures.i.length){var b=a.oLanguage,c=a._iDisplayStart+1,d=a.fnDisplayEnd(),g=a.fnRecordsTotal(),f=a.fnRecordsDisplay(),h;h=0===f&&f==g?b.sInfoEmpty:0===f?b.sInfoEmpty+" "+b.sInfoFiltered:f==g?b.sInfo:b.sInfo+ +" "+b.sInfoFiltered;h+=b.sInfoPostFix;h=ha(a,h);null!==b.fnInfoCallback&&(h=b.fnInfoCallback.call(a.oInstance,a,c,d,g,f,h));a=a.aanFeatures.i;b=0;for(c=a.length;b",c,d,g=a.aLengthMenu;if(2==g.length&&"object"===typeof g[0]&&"object"===typeof g[1]){c=0;for(d=g[0].length;c'+g[1][c]+""}else{c=0;for(d=g.length;c'+g[c]+""}b+= +"";g=l.createElement("div");a.aanFeatures.l||(g.id=a.sTableId+"_length");g.className=a.oClasses.sLength;g.innerHTML="";i('select option[value="'+a._iDisplayLength+'"]',g).attr("selected",!0);i("select",g).bind("change.DT",function(){var b=i(this).val(),g=a.aanFeatures.l;c=0;for(d=g.length;ca.aiDisplay.length||-1==a._iDisplayLength?a.aiDisplay.length:a._iDisplayStart+a._iDisplayLength}function Da(a){if(a.oScroll.bInfinite)return null;var b=l.createElement("div");b.className=a.oClasses.sPaging+a.sPaginationType; +j.ext.oPagination[a.sPaginationType].fnInit(a,b,function(a){A(a);y(a)});a.aanFeatures.p||a.aoDrawCallback.push({fn:function(a){j.ext.oPagination[a.sPaginationType].fnUpdate(a,function(a){A(a);y(a)})},sName:"pagination"});return b}function pa(a,b){var c=a._iDisplayStart;if("number"===typeof b)a._iDisplayStart=b*a._iDisplayLength,a._iDisplayStart>a.fnRecordsDisplay()&&(a._iDisplayStart=0);else if("first"==b)a._iDisplayStart=0;else if("previous"==b)a._iDisplayStart=0<=a._iDisplayLength?a._iDisplayStart- +a._iDisplayLength:0,0>a._iDisplayStart&&(a._iDisplayStart=0);else if("next"==b)0<=a._iDisplayLength?a._iDisplayStart+a._iDisplayLengthi(a.nTable).height()-a.oScroll.iLoadGap&&a.fnDisplayEnd()=i.browser.version;i(a.nTable).children("thead, tfoot").remove();h=i(a.nTHead).clone()[0];a.nTable.insertBefore(h,a.nTable.childNodes[0]);null!==a.nTFoot&&(j=i(a.nTFoot).clone()[0],a.nTable.insertBefore(j,a.nTable.childNodes[1]));""===a.oScroll.sX&&(d.style.width="100%",b.parentNode.style.width="100%");var t=O(a,h);g=0;for(f=t.length;gd.offsetHeight||"scroll"==i(d).css("overflow-y")))a.nTable.style.width=q(i(a.nTable).outerWidth()-a.oScroll.iBarWidth)}else""!==a.oScroll.sXInner?a.nTable.style.width=q(a.oScroll.sXInner):g==i(d).width()&&i(d).height()g-a.oScroll.iBarWidth&& +(a.nTable.style.width=q(g))):a.nTable.style.width=q(g);g=i(a.nTable).outerWidth();f=a.nTHead.getElementsByTagName("tr");h=h.getElementsByTagName("tr");N(function(a,b){m=a.style;m.paddingTop="0";m.paddingBottom="0";m.borderTopWidth="0";m.borderBottomWidth="0";m.height=0;k=i(a).width();b.style.width=q(k);n.push(k)},h,f);i(h).height(0);null!==a.nTFoot&&(e=j.getElementsByTagName("tr"),j=a.nTFoot.getElementsByTagName("tr"),N(function(a,b){m=a.style;m.paddingTop="0";m.paddingBottom="0";m.borderTopWidth= +"0";m.borderBottomWidth="0";m.height=0;k=i(a).width();b.style.width=q(k);n.push(k)},e,j),i(e).height(0));N(function(a){a.innerHTML="";a.style.width=q(n.shift())},h);null!==a.nTFoot&&N(function(a){a.innerHTML="";a.style.width=q(n.shift())},e);if(i(a.nTable).outerWidth()d.offsetHeight||"scroll"==i(d).css("overflow-y")?g+a.oScroll.iBarWidth:g;if(l&&(d.scrollHeight>d.offsetHeight||"scroll"==i(d).css("overflow-y")))a.nTable.style.width=q(e-a.oScroll.iBarWidth);d.style.width=q(e);b.parentNode.style.width= +q(e);null!==a.nTFoot&&(r.parentNode.style.width=q(e));""===a.oScroll.sX?E(a,1,"The table cannot fit into the current element which will cause column misalignment. The table has been drawn at its minimum possible width."):""!==a.oScroll.sXInner&&E(a,1,"The table cannot fit into the current element which will cause column misalignment. Increase the sScrollXInner value or remove it to allow automatic calculation")}else d.style.width=q("100%"),b.parentNode.style.width=q("100%"),null!==a.nTFoot&&(r.parentNode.style.width= +q("100%"));""===a.oScroll.sY&&l&&(d.style.height=q(a.nTable.offsetHeight+a.oScroll.iBarWidth));""!==a.oScroll.sY&&a.oScroll.bCollapse&&(d.style.height=q(a.oScroll.sY),l=""!==a.oScroll.sX&&a.nTable.offsetWidth>d.offsetWidth?a.oScroll.iBarWidth:0,a.nTable.offsetHeightd.clientHeight||"scroll"==i(d).css("overflow-y");b.style.paddingRight=c?a.oScroll.iBarWidth+ +"px":"0px";null!==a.nTFoot&&(p.style.width=q(l),r.style.width=q(l),r.style.paddingRight=c?a.oScroll.iBarWidth+"px":"0px");i(d).scroll();if(a.bSorted||a.bFiltered)d.scrollTop=0}function N(a,b,c){for(var d=0,g=b.length;dtd",b));h=O(a,f);for(f=d=0;fc)return null;if(null===a.aoData[c].nTr){var d=l.createElement("td");d.innerHTML=w(a,c,b,"");return d}return L(a,c)[b]}function Oa(a,b){for(var c= +-1,d=-1,g=0;g/g,"");f.length>c&&(c=f.length,d=g)}return d}function q(a){if(null===a)return"0px";if("number"==typeof a)return 0>a?"0px":a+"px";var b=a.charCodeAt(a.length-1);return 48>b||57/g,""),g=l[c].nTh,g.removeAttribute("aria-sort"),g.removeAttribute("aria-label"),l[c].bSortable?0=h)for(b=0;be&&e++}}}function qa(a){if(a.oFeatures.bStateSave&&!a.bDestroying){var b,c;b=a.oScroll.bInfinite;var d={iCreate:(new Date).getTime(),iStart:b?0:a._iDisplayStart, +iEnd:b?a._iDisplayLength:a._iDisplayEnd,iLength:a._iDisplayLength,aaSorting:i.extend(!0,[],a.aaSorting),oSearch:i.extend(!0,{},a.oPreviousSearch),aoSearchCols:i.extend(!0,[],a.aoPreSearchCols),abVisCols:[]};b=0;for(c=a.aoColumns.length;b=d.fnRecordsDisplay()&&(d._iDisplayStart-=d._iDisplayLength,0>d._iDisplayStart&&(d._iDisplayStart=0));if(c===n||c)A(d),y(d);return h};this.fnDestroy=function(a){var b=u(this[j.ext.iApiIndex]),c=b.nTableWrapper.parentNode,d=b.nTBody,g,e,a=a===n?!1:!0;b.bDestroying=!0;C(b,"aoDestroyCallback","destroy",[b]);g=0;for(e=b.aoColumns.length;gtr>td."+b.oClasses.sRowEmpty,b.nTable).parent().remove();b.nTable!=b.nTHead.parentNode&&(i(b.nTable).children("thead").remove(),b.nTable.appendChild(b.nTHead));b.nTFoot&&b.nTable!=b.nTFoot.parentNode&&(i(b.nTable).children("tfoot").remove(),b.nTable.appendChild(b.nTFoot));b.nTable.parentNode.removeChild(b.nTable);i(b.nTableWrapper).remove();b.aaSorting=[];b.aaSortingFixed=[];Q(b);i(S(b)).removeClass(b.asStripeClasses.join(" ")); +i("th, td",b.nTHead).removeClass([b.oClasses.sSortable,b.oClasses.sSortableAsc,b.oClasses.sSortableDesc,b.oClasses.sSortableNone].join(" "));b.bJUI&&(i("th span."+b.oClasses.sSortIcon+", td span."+b.oClasses.sSortIcon,b.nTHead).remove(),i("th, td",b.nTHead).each(function(){var a=i("div."+b.oClasses.sSortJUIWrapper,this),c=a.contents();i(this).append(c);a.remove()}));!a&&b.nTableReinsertBefore?c.insertBefore(b.nTable,b.nTableReinsertBefore):a||c.appendChild(b.nTable);g=0;for(e=b.aoData.length;g=v(d);if(!m)for(e=a;et<"F"ip>')):i.extend(h.oClasses,j.ext.oStdClasses);i(this).addClass(h.oClasses.sTable); +if(""!==h.oScroll.sX||""!==h.oScroll.sY)h.oScroll.iBarWidth=Pa();h.iInitDisplayStart===n&&(h.iInitDisplayStart=e.iDisplayStart,h._iDisplayStart=e.iDisplayStart);e.bStateSave&&(h.oFeatures.bStateSave=!0,Ra(h,e),B(h,"aoDrawCallback",qa,"state_save"));null!==e.iDeferLoading&&(h.bDeferLoading=!0,a=i.isArray(e.iDeferLoading),h._iRecordsDisplay=a?e.iDeferLoading[0]:e.iDeferLoading,h._iRecordsTotal=a?e.iDeferLoading[1]:e.iDeferLoading);null!==e.aaData&&(f=!0);""!==e.oLanguage.sUrl?(h.oLanguage.sUrl=e.oLanguage.sUrl, +i.getJSON(h.oLanguage.sUrl,null,function(a){oa(a);i.extend(true,h.oLanguage,e.oLanguage,a);aa(h)}),g=!0):i.extend(!0,h.oLanguage,e.oLanguage);null===e.asStripeClasses&&(h.asStripeClasses=[h.oClasses.sStripeOdd,h.oClasses.sStripeEven]);c=!1;d=i(this).children("tbody").children("tr");a=0;for(b=h.asStripeClasses.length;a=h.aoColumns.length&&(h.aaSorting[a][0]=0);var k=h.aoColumns[h.aaSorting[a][0]];h.aaSorting[a][2]===n&&(h.aaSorting[a][2]=0);e.aaSorting===n&&h.saved_aaSorting===n&&(h.aaSorting[a][1]=k.asSorting[0]);c=0;for(d=k.asSorting.length;c=parseInt(n,10)};j.fnIsDataTable=function(e){for(var i=j.settings,r=0;re)return e;for(var i=e+"",e=i.split(""),j="",i=i.length,k=0;k'+k.sPrevious+''+k.sNext+"":'';i(j).append(k);var t=i("a",j),k=t[0],t=t[1];e.oApi._fnBindAction(k,{action:"previous"},l);e.oApi._fnBindAction(t,{action:"next"},l); +e.aanFeatures.p||(j.id=e.sTableId+"_paginate",k.id=e.sTableId+"_previous",t.id=e.sTableId+"_next",k.setAttribute("aria-controls",e.sTableId),t.setAttribute("aria-controls",e.sTableId))},fnUpdate:function(e){if(e.aanFeatures.p)for(var i=e.oClasses,j=e.aanFeatures.p,k=0,n=j.length;k'+k.sFirst+''+k.sPrevious+''+k.sNext+''+k.sLast+"");var v=i("a",j),k=v[0],l=v[1],z=v[2],v=v[3];e.oApi._fnBindAction(k,{action:"first"},t);e.oApi._fnBindAction(l,{action:"previous"},t);e.oApi._fnBindAction(z,{action:"next"},t);e.oApi._fnBindAction(v,{action:"last"},t);e.aanFeatures.p||(j.id=e.sTableId+"_paginate",k.id=e.sTableId+"_first",l.id=e.sTableId+"_previous",z.id=e.sTableId+"_next",v.id=e.sTableId+"_last")},fnUpdate:function(e,o){if(e.aanFeatures.p){var l=j.ext.oPagination.iFullNumbersShowPages,k=Math.floor(l/ +2),n=Math.ceil(e.fnRecordsDisplay()/e._iDisplayLength),t=Math.ceil(e._iDisplayStart/e._iDisplayLength)+1,v="",z,D=e.oClasses,x,J=e.aanFeatures.p,H=function(i){e.oApi._fnBindAction(this,{page:i+z-1},function(i){e.oApi._fnPageChange(e,i.data.page);o(e);i.preventDefault()})};-1===e._iDisplayLength?t=k=z=1:n=n-k?(z=n-l+1,k=n):(z=t-Math.ceil(l/2)+1,k=z+l-1);for(l=z;l<=k;l++)v+=t!==l?''+e.fnFormatNumber(l)+"":''+e.fnFormatNumber(l)+"";l=0;for(k=J.length;li?1:0},"string-desc":function(e,i){return ei?-1:0},"html-pre":function(e){return e.replace(/<.*?>/g,"").toLowerCase()},"html-asc":function(e,i){return ei?1:0},"html-desc":function(e,i){return ei?-1:0},"date-pre":function(e){e=Date.parse(e);if(isNaN(e)||""===e)e=Date.parse("01/01/1970 00:00:00"); +return e},"date-asc":function(e,i){return e-i},"date-desc":function(e,i){return i-e},"numeric-pre":function(e){return"-"==e||""===e?0:1*e},"numeric-asc":function(e,i){return e-i},"numeric-desc":function(e,i){return i-e}});i.extend(j.ext.aTypes,[function(e){if("number"===typeof e)return"numeric";if("string"!==typeof e)return null;var i,j=!1;i=e.charAt(0);if(-1=="0123456789-".indexOf(i))return null;for(var k=1;k")?"html":null}]);i.fn.DataTable=j;i.fn.dataTable=j;i.fn.dataTableSettings=j.settings;i.fn.dataTableExt=j.ext})(jQuery,window,document,void 0); diff --git a/symposion/static/tabletools/js/TableTools.js b/symposion/static/tabletools/js/TableTools.js new file mode 100755 index 00000000..162b1c5e --- /dev/null +++ b/symposion/static/tabletools/js/TableTools.js @@ -0,0 +1,2424 @@ +/* + * File: TableTools.js + * Version: 2.1.3 + * Description: Tools and buttons for DataTables + * Author: Allan Jardine (www.sprymedia.co.uk) + * Language: Javascript + * License: GPL v2 or BSD 3 point style + * Project: DataTables + * + * Copyright 2009-2012 Allan Jardine, all rights reserved. + * + * This source file is free software, under either the GPL v2 license or a + * BSD style license, available at: + * http://datatables.net/license_gpl2 + * http://datatables.net/license_bsd + */ + +/* Global scope for TableTools */ +var TableTools; + +(function($, window, document) { + +/** + * TableTools provides flexible buttons and other tools for a DataTables enhanced table + * @class TableTools + * @constructor + * @param {Object} oDT DataTables instance + * @param {Object} oOpts TableTools options + * @param {String} oOpts.sSwfPath ZeroClipboard SWF path + * @param {String} oOpts.sRowSelect Row selection options - 'none', 'single' or 'multi' + * @param {Function} oOpts.fnPreRowSelect Callback function just prior to row selection + * @param {Function} oOpts.fnRowSelected Callback function just after row selection + * @param {Function} oOpts.fnRowDeselected Callback function when row is deselected + * @param {Array} oOpts.aButtons List of buttons to be used + */ +TableTools = function( oDT, oOpts ) +{ + /* Santiy check that we are a new instance */ + if ( ! this instanceof TableTools ) + { + alert( "Warning: TableTools must be initialised with the keyword 'new'" ); + } + + + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Public class variables + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + + /** + * @namespace Settings object which contains customisable information for TableTools instance + */ + this.s = { + /** + * Store 'this' so the instance can be retrieved from the settings object + * @property that + * @type object + * @default this + */ + "that": this, + + /** + * DataTables settings objects + * @property dt + * @type object + * @default From the oDT init option + */ + "dt": oDT.fnSettings(), + + /** + * @namespace Print specific information + */ + "print": { + /** + * DataTables draw 'start' point before the printing display was shown + * @property saveStart + * @type int + * @default -1 + */ + "saveStart": -1, + + /** + * DataTables draw 'length' point before the printing display was shown + * @property saveLength + * @type int + * @default -1 + */ + "saveLength": -1, + + /** + * Page scrolling point before the printing display was shown so it can be restored + * @property saveScroll + * @type int + * @default -1 + */ + "saveScroll": -1, + + /** + * Wrapped function to end the print display (to maintain scope) + * @property funcEnd + * @type Function + * @default function () {} + */ + "funcEnd": function () {} + }, + + /** + * A unique ID is assigned to each button in each instance + * @property buttonCounter + * @type int + * @default 0 + */ + "buttonCounter": 0, + + /** + * @namespace Select rows specific information + */ + "select": { + /** + * Select type - can be 'none', 'single' or 'multi' + * @property type + * @type string + * @default "" + */ + "type": "", + + /** + * Array of nodes which are currently selected + * @property selected + * @type array + * @default [] + */ + "selected": [], + + /** + * Function to run before the selection can take place. Will cancel the select if the + * function returns false + * @property preRowSelect + * @type Function + * @default null + */ + "preRowSelect": null, + + /** + * Function to run when a row is selected + * @property postSelected + * @type Function + * @default null + */ + "postSelected": null, + + /** + * Function to run when a row is deselected + * @property postDeselected + * @type Function + * @default null + */ + "postDeselected": null, + + /** + * Indicate if all rows are selected (needed for server-side processing) + * @property all + * @type boolean + * @default false + */ + "all": false, + + /** + * Class name to add to selected TR nodes + * @property selectedClass + * @type String + * @default "" + */ + "selectedClass": "" + }, + + /** + * Store of the user input customisation object + * @property custom + * @type object + * @default {} + */ + "custom": {}, + + /** + * SWF movie path + * @property swfPath + * @type string + * @default "" + */ + "swfPath": "", + + /** + * Default button set + * @property buttonSet + * @type array + * @default [] + */ + "buttonSet": [], + + /** + * When there is more than one TableTools instance for a DataTable, there must be a + * master which controls events (row selection etc) + * @property master + * @type boolean + * @default false + */ + "master": false, + + /** + * Tag names that are used for creating collections and buttons + * @namesapce + */ + "tags": {} + }; + + + /** + * @namespace Common and useful DOM elements for the class instance + */ + this.dom = { + /** + * DIV element that is create and all TableTools buttons (and their children) put into + * @property container + * @type node + * @default null + */ + "container": null, + + /** + * The table node to which TableTools will be applied + * @property table + * @type node + * @default null + */ + "table": null, + + /** + * @namespace Nodes used for the print display + */ + "print": { + /** + * Nodes which have been removed from the display by setting them to display none + * @property hidden + * @type array + * @default [] + */ + "hidden": [], + + /** + * The information display saying telling the user about the print display + * @property message + * @type node + * @default null + */ + "message": null + }, + + /** + * @namespace Nodes used for a collection display. This contains the currently used collection + */ + "collection": { + /** + * The div wrapper containing the buttons in the collection (i.e. the menu) + * @property collection + * @type node + * @default null + */ + "collection": null, + + /** + * Background display to provide focus and capture events + * @property background + * @type node + * @default null + */ + "background": null + } + }; + + /** + * @namespace Name space for the classes that this TableTools instance will use + * @extends TableTools.classes + */ + this.classes = $.extend( true, {}, TableTools.classes ); + if ( this.s.dt.bJUI ) + { + $.extend( true, this.classes, TableTools.classes_themeroller ); + } + + + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Public class methods + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + + /** + * Retreieve the settings object from an instance + * @method fnSettings + * @returns {object} TableTools settings object + */ + this.fnSettings = function () { + return this.s; + }; + + + /* Constructor logic */ + if ( typeof oOpts == 'undefined' ) + { + oOpts = {}; + } + + this._fnConstruct( oOpts ); + + return this; +}; + + + +TableTools.prototype = { + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Public methods + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + + /** + * Retreieve the settings object from an instance + * @returns {array} List of TR nodes which are currently selected + */ + "fnGetSelected": function () + { + var out=[]; + var data=this.s.dt.aoData; + var i, iLen; + + for ( i=0, iLen=data.length ; i 0 ) + { + sTitle = anTitle[0].innerHTML; + } + } + + /* Strip characters which the OS will object to - checking for UTF8 support in the scripting + * engine + */ + if ( "\u00A1".toString().length < 4 ) { + return sTitle.replace(/[^a-zA-Z0-9_\u00A1-\uFFFF\.,\-_ !\(\)]/g, ""); + } else { + return sTitle.replace(/[^a-zA-Z0-9_\.,\-_ !\(\)]/g, ""); + } + }, + + + /** + * Calculate a unity array with the column width by proportion for a set of columns to be + * included for a button. This is particularly useful for PDF creation, where we can use the + * column widths calculated by the browser to size the columns in the PDF. + * @param {Object} oConfig Button configuration object + * @returns {Array} Unity array of column ratios + */ + "fnCalcColRatios": function ( oConfig ) + { + var + aoCols = this.s.dt.aoColumns, + aColumnsInc = this._fnColumnTargets( oConfig.mColumns ), + aColWidths = [], + iWidth = 0, iTotal = 0, i, iLen; + + for ( i=0, iLen=aColumnsInc.length ; i
    {% endblock %} diff --git a/symposion/templates/proposals/proposal_speaker_manage.html b/symposion/templates/proposals/proposal_speaker_manage.html index b1e9990d..2faa7085 100644 --- a/symposion/templates/proposals/proposal_speaker_manage.html +++ b/symposion/templates/proposals/proposal_speaker_manage.html @@ -1,4 +1,7 @@ {% extends "proposals/base.html" %} + +{% load url from future %} + {% load i18n %} {% load bootstrap_tags %} @@ -7,7 +10,7 @@

    {% trans 'Proposal:' %} {{ proposal.title }}

    - {% trans 'Edit proposal' %} + {% trans 'Edit proposal' %}

    diff --git a/symposion/templates/proposals/proposal_submit.html b/symposion/templates/proposals/proposal_submit.html index 0a1b34e5..23175054 100644 --- a/symposion/templates/proposals/proposal_submit.html +++ b/symposion/templates/proposals/proposal_submit.html @@ -1,5 +1,8 @@ {% extends "proposals/base.html" %} +{% load url from future %} + + {% load boxes_tags %} {% load i18n %} @@ -13,7 +16,7 @@ {% else %} From 4c912da4aca6c953ac35a3cb5b40aeff0588ba77 Mon Sep 17 00:00:00 2001 From: Rebecca Lovewell Date: Wed, 15 Jan 2014 08:57:57 -0500 Subject: [PATCH 508/751] Use {% load url from future %} in reviews templates Using https://github.com/futurecolors/django-future-url --- .../templates/reviews/_review_table.html | 3 ++- symposion/templates/reviews/base.html | 11 +++++---- .../reviews/result_notification.html | 13 +++++++---- .../reviews/result_notification_prepare.html | 7 ++++-- symposion/templates/reviews/review_admin.html | 5 +++- .../templates/reviews/review_assignment.html | 7 ++++-- .../templates/reviews/review_detail.html | 5 +++- .../templates/reviews/review_review.html | 7 ++++-- symposion/templates/reviews/review_stats.html | 23 +++++++++++-------- 9 files changed, 53 insertions(+), 28 deletions(-) diff --git a/symposion/templates/reviews/_review_table.html b/symposion/templates/reviews/_review_table.html index 9c1804b7..9e94cf83 100644 --- a/symposion/templates/reviews/_review_table.html +++ b/symposion/templates/reviews/_review_table.html @@ -1,3 +1,4 @@ +{% load url from future %} {% load i18n %} @@ -18,7 +19,7 @@ - + - + - + - + - + - + diff --git a/symposion/templates/proposals/proposal_cancel.html b/symposion/templates/proposals/proposal_cancel.html index 93a1204a..5e57c80a 100644 --- a/symposion/templates/proposals/proposal_cancel.html +++ b/symposion/templates/proposals/proposal_cancel.html @@ -8,11 +8,11 @@ {% block body %}

    Cancel: {{ proposal.title }}

    - + {% csrf_token %}

    Are you sure you want to cancel {{ proposal.title }}?

    - {% trans 'No, keep it for now' %} + {% trans 'No, keep it for now' %} {% endblock %} diff --git a/symposion/templates/proposals/proposal_detail.html b/symposion/templates/proposals/proposal_detail.html index a9b881e7..7f02504c 100644 --- a/symposion/templates/proposals/proposal_detail.html +++ b/symposion/templates/proposals/proposal_detail.html @@ -10,14 +10,14 @@
    {% if not proposal.cancelled %} {% if request.user == proposal.speaker.user %} - + {% trans "Edit this proposal" %} - + {% trans "Cancel this proposal" %} {% else %} - + {% trans "Remove me from this proposal" %} {% endif %} @@ -25,9 +25,9 @@ {% trans 'Cancelled' } {% endif %}
    - +

    #{{ proposal.number }}: {{ proposal.title }} ({{ proposal.speaker }}, Track: {{ proposal.track }})

    - +
    {{ proposal.number }} - + {{ proposal.speaker }}
    {{ proposal.title }} diff --git a/symposion/templates/reviews/base.html b/symposion/templates/reviews/base.html index 695d9c8c..a09bde0e 100644 --- a/symposion/templates/reviews/base.html +++ b/symposion/templates/reviews/base.html @@ -1,5 +1,8 @@ {% extends "site_base.html" %} +{% load url from future %} + + {% load i18n %} {% load sitetree %} @@ -60,25 +63,25 @@ {{ section }}
  • - + {% trans "All Reviews" %}
  • {% comment %}
  • - + {% trans "Your Assignments" %}
  • {% endcomment %}
  • - + {% trans "Voting Status" %}
  • {% if request.user.is_staff %}
  • - Result Notification + Result Notification
  • {% endif %} {% endfor %} diff --git a/symposion/templates/reviews/result_notification.html b/symposion/templates/reviews/result_notification.html index cbd5c4c2..af250baa 100644 --- a/symposion/templates/reviews/result_notification.html +++ b/symposion/templates/reviews/result_notification.html @@ -1,5 +1,8 @@ {% extends "reviews/base.html" %} +{% load url from future %} + + {% load i18n %} {% block extra_style %} @@ -13,14 +16,14 @@ {% block body %}

    Result Notification

    -
    + {% csrf_token %} @@ -54,7 +57,7 @@
    {{ proposal.number }} - + {{ proposal.speaker }}
    {{ proposal.title }} diff --git a/symposion/templates/reviews/result_notification_prepare.html b/symposion/templates/reviews/result_notification_prepare.html index 2248c204..bfe2e433 100644 --- a/symposion/templates/reviews/result_notification_prepare.html +++ b/symposion/templates/reviews/result_notification_prepare.html @@ -1,5 +1,8 @@ {% extends "reviews/base.html" %} +{% load url from future %} + + {% load i18n %} {% block body %} @@ -23,7 +26,7 @@
    diff --git a/symposion/templates/reviews/review_admin.html b/symposion/templates/reviews/review_admin.html index 1e762416..24ac217f 100644 --- a/symposion/templates/reviews/review_admin.html +++ b/symposion/templates/reviews/review_admin.html @@ -1,5 +1,8 @@ {% extends "reviews/base.html" %} +{% load url from future %} + + {% block body %}

    Reviewers

    @@ -30,7 +33,7 @@ {% for reviewer in reviewers %}
    - {{ reviewer.get_full_name }} + {{ reviewer.get_full_name }} {{ reviewer.total_votes }} diff --git a/symposion/templates/reviews/review_assignment.html b/symposion/templates/reviews/review_assignment.html index 8bcc1fef..448b85c0 100644 --- a/symposion/templates/reviews/review_assignment.html +++ b/symposion/templates/reviews/review_assignment.html @@ -1,5 +1,8 @@ {% extends "reviews/base.html" %} +{% load url from future %} + + {% block body %}

    Review Assignments

    @@ -13,12 +16,12 @@ {% for assignment in assignments %}
    - + {{ assignment.proposal.title }} -
    + {% csrf_token %}
    diff --git a/symposion/templates/reviews/review_detail.html b/symposion/templates/reviews/review_detail.html index 9a2de066..98b4043a 100644 --- a/symposion/templates/reviews/review_detail.html +++ b/symposion/templates/reviews/review_detail.html @@ -1,5 +1,8 @@ {% extends "reviews/base.html" %} +{% load url from future %} + + {% load i18n %} {% load markitup_tags %} {% load bootstrap_tags %} @@ -126,7 +129,7 @@ {% if is_manager %}
    -
    + {% csrf_token %}
    diff --git a/symposion/templates/reviews/review_review.html b/symposion/templates/reviews/review_review.html index 2168e899..7f753ddc 100644 --- a/symposion/templates/reviews/review_review.html +++ b/symposion/templates/reviews/review_review.html @@ -1,5 +1,8 @@ {% extends "site_base.html" %} +{% load url from future %} + + {% load markitup_tags %} {% load uni_form_tags %} @@ -55,7 +58,7 @@

    Review

    -
    + {% csrf_token %}
    {{ review_form|as_uni_form }} @@ -67,7 +70,7 @@

    Comment

    - + {% csrf_token %}
    {{ comment_form|as_uni_form }} diff --git a/symposion/templates/reviews/review_stats.html b/symposion/templates/reviews/review_stats.html index 8f4a09cb..40936af5 100644 --- a/symposion/templates/reviews/review_stats.html +++ b/symposion/templates/reviews/review_stats.html @@ -1,15 +1,18 @@ {% extends "reviews/base.html" %} +{% load url from future %} + + {% block body %}

    Voting Status ({{ section_slug }})

    {% if key %}
    @@ -42,35 +45,35 @@
    - Positive + Positive {{ proposals.positive|length }}
    proposals with at least {{ vote_threshold }} vote{{ vote_threshold|pluralize }} and at least one +1 and no −1s
    - Negative + Negative {{ proposals.negative|length }}
    proposals with at least {{ vote_threshold }} vote{{ vote_threshold|pluralize }} and at least one −1 and no +1s
    - Indifferent + Indifferent {{ proposals.indifferent|length }}
    proposals with at least {{ vote_threshold }} vote{{ vote_threshold|pluralize }} and neither a +1 or a −1
    - Controversial + Controversial {{ proposals.controversial|length }}
    proposals with at least {{ vote_threshold }} vote{{ vote_threshold|pluralize }} and both a +1 and −1
    - Too Few Reviews + Too Few Reviews {{ proposals.too_few|length }}
    From 76a5e08e2fe5a5e4052b700efbf48f50ebc91150 Mon Sep 17 00:00:00 2001 From: Rebecca Lovewell Date: Wed, 15 Jan 2014 08:58:21 -0500 Subject: [PATCH 509/751] Use {% load url from future %} in schedule templates Using https://github.com/futurecolors/django-future-url --- symposion/templates/schedule/_edit_grid.html | 7 ++++--- symposion/templates/schedule/_grid.html | 3 ++- symposion/templates/schedule/_slot_edit.html | 3 ++- symposion/templates/schedule/presentation_detail.html | 5 ++++- symposion/templates/schedule/schedule_list.html | 5 ++++- 5 files changed, 16 insertions(+), 7 deletions(-) diff --git a/symposion/templates/schedule/_edit_grid.html b/symposion/templates/schedule/_edit_grid.html index ff9afe78..870391b5 100644 --- a/symposion/templates/schedule/_edit_grid.html +++ b/symposion/templates/schedule/_edit_grid.html @@ -1,3 +1,4 @@ +{% load url from future %} @@ -15,9 +16,9 @@ {% endfor %} diff --git a/symposion/templates/schedule/_grid.html b/symposion/templates/schedule/_grid.html index be62d912..71f98448 100644 --- a/symposion/templates/schedule/_grid.html +++ b/symposion/templates/schedule/_grid.html @@ -1,3 +1,4 @@ +{% load url from future %}
    {% if slot.kind.label == "talk" or slot.kind.label == "tutorial" %} {% if not slot.content %} - + + + {% else %} - {{ slot.content.title }} + {{ slot.content.title }} {{ slot.content.speaker }} {% endif %} {% else %} @@ -26,7 +27,7 @@ {% else %} {{ slot.kind.label }} {% endif %} - — edit + — edit {% endif %}
    @@ -17,7 +18,7 @@ {% if not slot.content %} {% else %} - {{ slot.content.title }} + {{ slot.content.title }} {{ slot.content.speakers|join:", " }} diff --git a/symposion/templates/schedule/_slot_edit.html b/symposion/templates/schedule/_slot_edit.html index d8b5ae9e..385ff89f 100644 --- a/symposion/templates/schedule/_slot_edit.html +++ b/symposion/templates/schedule/_slot_edit.html @@ -1,5 +1,6 @@ +{% load url from future %} {% load i18n bootstrap_tags %} - + {% endfor %} @@ -57,7 +60,7 @@ {% endfor %} @@ -70,8 +73,8 @@ {% endfor %} From 280a2c2d2098329ebabdf86c66351946134f31d1 Mon Sep 17 00:00:00 2001 From: Rebecca Lovewell Date: Wed, 15 Jan 2014 09:00:15 -0500 Subject: [PATCH 513/751] Use {% load url from future %} in dashboard template Using https://github.com/futurecolors/django-future-url --- symposion/templates/dashboard.html | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/symposion/templates/dashboard.html b/symposion/templates/dashboard.html index a88b4a3f..d47c2729 100644 --- a/symposion/templates/dashboard.html +++ b/symposion/templates/dashboard.html @@ -1,5 +1,8 @@ {% extends "site_base.html" %} +{% load url from future %} + + {% load i18n %} {% load proposal_tags %} {% load review_tags %} @@ -16,14 +19,14 @@

    {% trans "Speaking" %}

    {% if not user.speaker_profile %} - + Create a speaker profile {% else %} - + Edit your speaker profile - + Submit a new proposal {% endif %} @@ -32,7 +35,7 @@
    {% if not user.speaker_profile %} -

    To submit a proposal, you must first create a speaker profile.

    +

    To submit a proposal, you must first create a speaker profile.

    {% else %}

    Your Proposals

    {% if user.speaker_profile.proposals.exists %} @@ -92,7 +95,7 @@

    {% trans "Sponsorship" %}

    {% if not user.sponsorships.exists %} - + Apply to be a sponsor {% endif %} @@ -101,13 +104,13 @@
    {% if not user.sponsorships.exists %} -

    If you or your organization would be interested in sponsorship opportunities, use our online form to apply to be a sponsor. +

    If you or your organization would be interested in sponsorship opportunities, use our online form to apply to be a sponsor. {% else %}

    Your Sponsorship

    - + {% for user in users %} @@ -68,9 +68,9 @@ diff --git a/symposion/templates/dashboard.html b/symposion/templates/dashboard.html index a88b4a3f..f4ef8775 100644 --- a/symposion/templates/dashboard.html +++ b/symposion/templates/dashboard.html @@ -16,23 +16,23 @@

    {% trans "Speaking" %}

    {% if not user.speaker_profile %} - + Create a speaker profile {% else %} - + Edit your speaker profile - + Submit a new proposal {% endif %}
    - +
    {% if not user.speaker_profile %} -

    To submit a proposal, you must first create a speaker profile.

    +

    To submit a proposal, you must first create a speaker profile.

    {% else %}

    Your Proposals

    {% if user.speaker_profile.proposals.exists %} @@ -50,7 +50,7 @@ {% else %}

    No proposals submitted yet.

    {% endif %} - + {% associated_proposals as associated_proposals %} {% if associated_proposals %}

    Proposals you have joined as an additional speaker

    @@ -66,7 +66,7 @@ {% endfor %}
    {{ membership.user.email }}{% if user == membership.user %} you{% endif %} -
    {% csrf_token %}
    +
    {% csrf_token %}
    {{ membership.user.email }}{% if user == membership.user %} you{% endif %} -
    {% csrf_token %}
    +
    {% csrf_token %}
    {{ membership.user.email }} -
    {% csrf_token %}
    -
    {% csrf_token %}
    +
    {% csrf_token %}
    +
    {% csrf_token %}
    - {{ membership.team.name }} + {{ membership.team.name }} {% if membership.team.description %}
    {{ membership.team.description }}{% endif %}
    @@ -204,7 +207,7 @@ {% for team in available_teams %}
    - {{ team }} + {{ team }} {% if team.description %}
    {{ team.description }}{% endif %}
    From e9c97a9586f2215bfe7a4714d5467e33201881bb Mon Sep 17 00:00:00 2001 From: Rebecca Lovewell Date: Wed, 15 Jan 2014 09:05:08 -0500 Subject: [PATCH 514/751] mimetype -> content_type mimetype is deprecated and to be removed in Django 1.7. --- symposion/proposals/actions.py | 2 +- symposion/schedule/views.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/symposion/proposals/actions.py b/symposion/proposals/actions.py index e46e2524..c502c0fe 100644 --- a/symposion/proposals/actions.py +++ b/symposion/proposals/actions.py @@ -23,7 +23,7 @@ def export_as_csv_action( elif exclude: excludeset = set(exclude) field_names = field_names - excludeset - response = HttpResponse(mimetype="text/csv") + response = HttpResponse(content_type="text/csv") response["Content-Disposition"] = "attachment; filename=%s.csv" % unicode(opts).replace(".", "_") writer = csv.writer(response) if header: diff --git a/symposion/schedule/views.py b/symposion/schedule/views.py index 98771b59..1367808b 100644 --- a/symposion/schedule/views.py +++ b/symposion/schedule/views.py @@ -78,7 +78,7 @@ def schedule_list_csv(request, slug=None): presentations = Presentation.objects.filter(section=schedule.section) presentations = presentations.exclude(cancelled=True).order_by("id") - response = HttpResponse(mimetype="text/csv") + response = HttpResponse(content_type="text/csv") if slug: file_slug = slug else: From dff5c4b1bb043cf02e1f504eb1e8fba151a73733 Mon Sep 17 00:00:00 2001 From: Rebecca Lovewell Date: Wed, 15 Jan 2014 09:22:16 -0500 Subject: [PATCH 515/751] Use hashlib rather than django.utils.hashcompat --- symposion/proposals/views.py | 62 ++++++++++++++++++------------------ 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/symposion/proposals/views.py b/symposion/proposals/views.py index cf532c2b..f8dde53c 100644 --- a/symposion/proposals/views.py +++ b/symposion/proposals/views.py @@ -1,3 +1,4 @@ +import hashlib import random import sys @@ -6,7 +7,6 @@ from django.core.exceptions import ObjectDoesNotExist from django.db.models import Q from django.http import Http404, HttpResponse, HttpResponseForbidden from django.shortcuts import render, redirect, get_object_or_404 -from django.utils.hashcompat import sha_constructor from django.views import static from django.contrib import messages @@ -37,21 +37,21 @@ def proposal_submit(request): request.user.speaker_profile except ObjectDoesNotExist: return redirect("dashboard") - + kinds = [] for proposal_section in ProposalSection.available(): for kind in proposal_section.section.proposal_kinds.all(): kinds.append(kind) - + return render(request, "proposals/proposal_submit.html", { "kinds": kinds, }) def proposal_submit_kind(request, kind_slug): - + kind = get_object_or_404(ProposalKind, slug=kind_slug) - + if not request.user.is_authenticated(): return redirect("home") # @@@ unauth'd speaker info page? else: @@ -59,12 +59,12 @@ def proposal_submit_kind(request, kind_slug): speaker_profile = request.user.speaker_profile except ObjectDoesNotExist: return redirect("dashboard") - + if not kind.section.proposalsection.is_available(): return redirect("proposal_submit") - + form_class = get_form(settings.PROPOSAL_FORMS[kind_slug]) - + if request.method == "POST": form = form_class(request.POST) if form.is_valid(): @@ -79,7 +79,7 @@ def proposal_submit_kind(request, kind_slug): return redirect("dashboard") else: form = form_class() - + return render(request, "proposals/proposal_submit_kind.html", { "kind": kind, "form": form, @@ -91,17 +91,17 @@ def proposal_speaker_manage(request, pk): queryset = ProposalBase.objects.select_related("speaker") proposal = get_object_or_404(queryset, pk=pk) proposal = ProposalBase.objects.get_subclass(pk=proposal.pk) - + if proposal.speaker != request.user.speaker_profile: raise Http404() - + if request.method == "POST": add_speaker_form = AddSpeakerForm(request.POST, proposal=proposal) if add_speaker_form.is_valid(): message_ctx = { "proposal": proposal, } - + def create_speaker_token(email_address): # create token and look for an existing speaker to prevent # duplicate tokens and confusing the pending speaker @@ -110,8 +110,8 @@ def proposal_speaker_manage(request, pk): Q(user=None, invite_email=email_address) ) except Speaker.DoesNotExist: - salt = sha_constructor(str(random.random())).hexdigest()[:5] - token = sha_constructor(salt + email_address).hexdigest() + salt = hashlib.sha1(str(random.random())).hexdigest()[:5] + token = hashlib.sha1(salt + email_address).hexdigest() pending = Speaker.objects.create( invite_email=email_address, invite_token=token, @@ -173,14 +173,14 @@ def proposal_edit(request, pk): if request.user != proposal.speaker.user: raise Http404() - + if not proposal.can_edit(): ctx = { "title": "Proposal editing closed", "body": "Proposal editing is closed for this session type." } return render(request, "proposals/proposal_error.html", ctx) - + form_class = get_form(settings.PROPOSAL_FORMS[proposal.kind.slug]) if request.method == "POST": @@ -206,7 +206,7 @@ def proposal_edit(request, pk): return redirect("proposal_detail", proposal.pk) else: form = form_class(instance=proposal) - + return render(request, "proposals/proposal_edit.html", { "proposal": proposal, "form": form, @@ -218,22 +218,22 @@ def proposal_detail(request, pk): queryset = ProposalBase.objects.select_related("speaker", "speaker__user") proposal = get_object_or_404(queryset, pk=pk) proposal = ProposalBase.objects.get_subclass(pk=proposal.pk) - + if request.user not in [p.user for p in proposal.speakers()]: raise Http404() - + if "symposion.reviews" in settings.INSTALLED_APPS: from symposion.reviews.forms import SpeakerCommentForm message_form = SpeakerCommentForm() if request.method == "POST": message_form = SpeakerCommentForm(request.POST) if message_form.is_valid(): - + message = message_form.save(commit=False) message.user = request.user message.proposal = proposal message.save() - + ProposalMessage = SpeakerCommentForm.Meta.model reviewers = User.objects.filter( id__in=ProposalMessage.objects.filter( @@ -242,7 +242,7 @@ def proposal_detail(request, pk): user=request.user ).distinct().values_list("user", flat=True) ) - + for reviewer in reviewers: ctx = { "proposal": proposal, @@ -253,13 +253,13 @@ def proposal_detail(request, pk): [reviewer.email], "proposal_new_message", context=ctx ) - + return redirect(request.path) else: message_form = SpeakerCommentForm() else: message_form = None - + return render(request, "proposals/proposal_detail.html", { "proposal": proposal, "message_form": message_form @@ -271,7 +271,7 @@ def proposal_cancel(request, pk): queryset = ProposalBase.objects.select_related("speaker") proposal = get_object_or_404(queryset, pk=pk) proposal = ProposalBase.objects.get_subclass(pk=proposal.pk) - + if proposal.speaker.user != request.user: return HttpResponseForbidden() @@ -281,7 +281,7 @@ def proposal_cancel(request, pk): # @@@ fire off email to submitter and other speakers messages.success(request, "%s has been cancelled" % proposal.title) return redirect("dashboard") - + return render(request, "proposals/proposal_cancel.html", { "proposal": proposal, }) @@ -339,10 +339,10 @@ def document_create(request, proposal_pk): queryset = ProposalBase.objects.select_related("speaker") proposal = get_object_or_404(queryset, pk=proposal_pk) proposal = ProposalBase.objects.get_subclass(pk=proposal.pk) - + if proposal.cancelled: return HttpResponseForbidden() - + if request.method == "POST": form = SupportingDocumentCreateForm(request.POST, request.FILES) if form.is_valid(): @@ -353,7 +353,7 @@ def document_create(request, proposal_pk): return redirect("proposal_detail", proposal.pk) else: form = SupportingDocumentCreateForm() - + return render(request, "proposals/document_create.html", { "proposal": proposal, "form": form, @@ -378,8 +378,8 @@ def document_download(request, pk, *args): def document_delete(request, pk): document = get_object_or_404(SupportingDocument, pk=pk, uploaded_by=request.user) proposal_pk = document.proposal.pk - + if request.method == "POST": document.delete() - + return redirect("proposal_detail", proposal_pk) From b78f7ebb91092221ca4a1948d0870f8e20ab88d9 Mon Sep 17 00:00:00 2001 From: Rebecca Lovewell Date: Mon, 13 Jan 2014 16:36:22 -0500 Subject: [PATCH 516/751] Create a requirements file --- requirements/base.txt | 13 +++++++++++++ requirements-docs.txt => requirements/docs.txt | 0 2 files changed, 13 insertions(+) create mode 100644 requirements/base.txt rename requirements-docs.txt => requirements/docs.txt (100%) diff --git a/requirements/base.txt b/requirements/base.txt new file mode 100644 index 00000000..2ee30364 --- /dev/null +++ b/requirements/base.txt @@ -0,0 +1,13 @@ +Django==1.4.5 +django-appconf==0.5 +django-forms-bootstrap==2.0.3.post2 +django-markitup==1.0.0 +django-model-utils==1.1.0 +django-reversion==1.7 +django-sitetree==0.9.4 +django-taggit==0.9.3 +django-timezones==0.2 +django-user-accounts==1.0b13 +easy-thumbnails==1.2 +html5lib==0.95 +markdown==2.3.1 diff --git a/requirements-docs.txt b/requirements/docs.txt similarity index 100% rename from requirements-docs.txt rename to requirements/docs.txt From e799ead0947b24d2a8cc9acf04d9be4e0ea7cff1 Mon Sep 17 00:00:00 2001 From: Rebecca Lovewell Date: Mon, 13 Jan 2014 16:41:10 -0500 Subject: [PATCH 517/751] Remove outdated fixture_gen files http://github.com/alex/fixture_generator has not been updated since Django 1.2. --- symposion/reviews/fixture_gen.py | 9 --------- symposion/speakers/fixture_gen.py | 29 ----------------------------- 2 files changed, 38 deletions(-) delete mode 100644 symposion/reviews/fixture_gen.py delete mode 100644 symposion/speakers/fixture_gen.py diff --git a/symposion/reviews/fixture_gen.py b/symposion/reviews/fixture_gen.py deleted file mode 100644 index 3a6aaa40..00000000 --- a/symposion/reviews/fixture_gen.py +++ /dev/null @@ -1,9 +0,0 @@ -from django.contrib.auth.models import Group - -from fixture_generator import fixture_generator - - -@fixture_generator(Group) -def initial_data(): - Group.objects.create(name="reviewers") - Group.objects.create(name="reviewers-admins") diff --git a/symposion/speakers/fixture_gen.py b/symposion/speakers/fixture_gen.py deleted file mode 100644 index fc38463f..00000000 --- a/symposion/speakers/fixture_gen.py +++ /dev/null @@ -1,29 +0,0 @@ -from django.contrib.auth.models import User - -from fixture_generator import fixture_generator - -from symposion.speakers.models import Speaker - - -@fixture_generator(Speaker, User) -def speakers(): - guido = User.objects.create_user("guido", "guido@python.org", "pythonisawesome") - matz = User.objects.create_user("matz", "matz@ruby.org", "pythonsucks") - larry = User.objects.create_user("larryw", "larry@perl.org", "linenoisehere") - - Speaker.objects.create( - user=guido, - name="Guido van Rossum", - biography="I wrote Python, and named it after Monty Python", - ) - Speaker.objects.create( - user=matz, - name="Yukihiro Matsumoto", - biography="I wrote Ruby, and named it after the rare gem Ruby, a pun " - "on Perl/pearl.", - ) - Speaker.objects.create( - user=larry, - name="Larry Wall", - biography="I wrote Perl, and named it after the Parable of the Pearl", - ) From 2413421324d3ba9f79523ba7506c0df70f61f0e6 Mon Sep 17 00:00:00 2001 From: Rebecca Lovewell Date: Mon, 13 Jan 2014 16:42:31 -0500 Subject: [PATCH 518/751] Remove old tests file --- symposion/reviews/tests.py | 143 ------------------------------------- 1 file changed, 143 deletions(-) delete mode 100644 symposion/reviews/tests.py diff --git a/symposion/reviews/tests.py b/symposion/reviews/tests.py deleted file mode 100644 index e5fe7caf..00000000 --- a/symposion/reviews/tests.py +++ /dev/null @@ -1,143 +0,0 @@ -from django.core.urlresolvers import reverse -from django.test import TestCase - -from django.contrib.auth.models import User, Group - -from symposion.proposals.models import Proposal -from symposion.reviews.models import Review, ReviewAssignment - - -class login(object): - def __init__(self, testcase, user, password): - self.testcase = testcase - success = testcase.client.login(username=user, password=password) - self.testcase.assertTrue( - success, - "login with username=%r, password=%r failed" % (user, password) - ) - - def __enter__(self): - pass - - def __exit__(self, *args): - self.testcase.client.logout() - - -class ReviewTests(TestCase): - fixtures = ["proposals"] - - def get(self, url_name, *args, **kwargs): - return self.client.get(reverse(url_name, args=args, kwargs=kwargs)) - - def post(self, url_name, *args, **kwargs): - data = kwargs.pop("data") - return self.client.post(reverse(url_name, args=args, kwargs=kwargs), data) - - def login(self, user, password): - return login(self, user, password) - - def test_detail_perms(self): - guidos_proposal = Proposal.objects.all()[0] - response = self.get("review_detail", pk=guidos_proposal.pk) - - # Not logged in - self.assertEqual(response.status_code, 302) - - with self.login("guido", "pythonisawesome"): - response = self.get("review_detail", pk=guidos_proposal.pk) - # Guido can see his own proposal. - self.assertEqual(response.status_code, 200) - - with self.login("matz", "pythonsucks"): - response = self.get("review_detail", pk=guidos_proposal.pk) - # Matz can't see guido's proposal - self.assertEqual(response.status_code, 302) - - larry = User.objects.get(username="larryw") - # Larry is a trustworthy guy, he's a reviewer. - larry.groups.add(Group.objects.get(name="reviewers")) - with self.login("larryw", "linenoisehere"): - response = self.get("review_detail", pk=guidos_proposal.pk) - # Reviewers can see a review detail page. - self.assertEqual(response.status_code, 200) - - def test_reviewing(self): - guidos_proposal = Proposal.objects.all()[0] - - with self.login("guido", "pythonisawesome"): - response = self.post("review_review", pk=guidos_proposal.pk, data={ - "vote": "+1", - }) - # It redirects, but... - self.assertEqual(response.status_code, 302) - # ... no vote recorded - self.assertEqual(guidos_proposal.reviews.count(), 0) - - larry = User.objects.get(username="larryw") - # Larry is a trustworthy guy, he's a reviewer. - larry.groups.add(Group.objects.get(name="reviewers")) - with self.login("larryw", "linenoisehere"): - response = self.post("review_review", pk=guidos_proposal.pk, data={ - "vote": "+0", - "text": "Looks like a decent proposal, and Guido is a smart guy", - }) - self.assertEqual(response.status_code, 302) - self.assertEqual(guidos_proposal.reviews.count(), 1) - self.assertEqual(ReviewAssignment.objects.count(), 1) - assignment = ReviewAssignment.objects.get() - self.assertEqual(assignment.proposal, guidos_proposal) - self.assertEqual(assignment.origin, ReviewAssignment.OPT_IN) - self.assertEqual(guidos_proposal.comments.count(), 1) - comment = guidos_proposal.comments.get() - self.assertFalse(comment.public) - - response = self.post("review_review", pk=guidos_proposal.pk, data={ - "vote": "+1", - "text": "Actually Perl is dead, we really need a talk on the future", - }) - self.assertEqual(guidos_proposal.reviews.count(), 2) - self.assertEqual(ReviewAssignment.objects.count(), 1) - assignment = ReviewAssignment.objects.get() - self.assertEqual(assignment.review, Review.objects.order_by("-id")[0]) - self.assertEqual(guidos_proposal.comments.count(), 2) - - # Larry's a big fan... - response = self.post("review_review", pk=guidos_proposal.pk, data={ - "vote": "+20", - }) - self.assertEqual(guidos_proposal.reviews.count(), 2) - - def test_speaker_commenting(self): - guidos_proposal = Proposal.objects.all()[0] - - with self.login("guido", "pythonisawesome"): - response = self.get("review_comment", pk=guidos_proposal.pk) - # Guido can comment on his proposal. - self.assertEqual(response.status_code, 200) - - response = self.post("review_comment", pk=guidos_proposal.pk, data={ - "text": "FYI I can do this as a 30-minute or 45-minute talk.", - }) - self.assertEqual(response.status_code, 302) - self.assertEqual(guidos_proposal.comments.count(), 1) - comment = guidos_proposal.comments.get() - self.assertTrue(comment.public) - - larry = User.objects.get(username="larryw") - # Larry is a trustworthy guy, he's a reviewer. - larry.groups.add(Group.objects.get(name="reviewers")) - with self.login("larryw", "linenoisehere"): - response = self.get("review_comment", pk=guidos_proposal.pk) - # Larry can comment, since he's a reviewer - self.assertEqual(response.status_code, 200) - - response = self.post("review_comment", pk=guidos_proposal.pk, data={ - "text": "Thanks for the heads-up Guido." - }) - self.assertEqual(response.status_code, 302) - self.assertEqual(guidos_proposal.comments.count(), 2) - - with self.login("matz", "pythonsucks"): - response = self.get("review_comment", pk=guidos_proposal.pk) - # Matz can't comment. - self.assertEqual(response.status_code, 302) From f2c3cbb3365b351abb97082969d40c9edd26d1c8 Mon Sep 17 00:00:00 2001 From: Rebecca Lovewell Date: Wed, 15 Jan 2014 10:07:04 -0500 Subject: [PATCH 519/751] Add install requirements to setup.py --- setup.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 74eebbc5..2018f9a9 100644 --- a/setup.py +++ b/setup.py @@ -1,10 +1,20 @@ #!/usr/bin/env python - +import os from setuptools import setup, find_packages import symposion +def read_file(filename): + """Read a file into a string.""" + path = os.path.abspath(os.path.dirname(__file__)) + filepath = os.path.ojoin(path, filename) + try: + return open(filepath).read() + except IOError: + return '' + + setup( name="symposion", author="James Tauber", @@ -12,7 +22,7 @@ setup( version=symposion.__version__, description="A collection of Django apps for conference websites.", url="http://eldarion.com/symposion/", - packages=find_packages(exclude=["symposion_project"]), + packages=find_packages(), include_package_data=True, classifiers=( "Development Status :: 4 - Beta", @@ -22,4 +32,5 @@ setup( "Natural Language :: English", "License :: OSI Approved :: MIT License", ), + install_requires=read_file("requirements/base.txt").splitlines(), ) From c8087ec23346bad31ba4a4b002e3f933a7bd8fb0 Mon Sep 17 00:00:00 2001 From: Rebecca Lovewell Date: Wed, 15 Jan 2014 10:19:40 -0500 Subject: [PATCH 520/751] Update quickstart instructions in README --- README.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index cad1f6fa..f0f04932 100644 --- a/README.md +++ b/README.md @@ -4,11 +4,17 @@ A conference management solution from Eldarion. Built with the generous support of the Python Software Foundation. -See http://eldarion.com/symposion/ for commercial support, customization and hosting +See http://eldarion.com/symposion/ for commercial support, customization and +hosting. ## Quickstart -If you're interested in running symposion locally, we have built a [basic +To install Symposion, run: + + pip install symposion + +Symposion is a Django app. You will need to create a Django project to +customize and manage your Symposion installation. We have built a [basic Django startproject template that includes Symposion][1]. [1]: https://github.com/pinax/pinax-project-symposion From 18896aa6da36825769e27a4b5bfc3f9c5d71c1ae Mon Sep 17 00:00:00 2001 From: David Ray Date: Thu, 16 Jan 2014 08:27:29 -0500 Subject: [PATCH 521/751] fix typo --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 2018f9a9..bdf75e6f 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ import symposion def read_file(filename): """Read a file into a string.""" path = os.path.abspath(os.path.dirname(__file__)) - filepath = os.path.ojoin(path, filename) + filepath = os.path.join(path, filename) try: return open(filepath).read() except IOError: From 0163ded85b5cb7a260f923a5e800cdb672450739 Mon Sep 17 00:00:00 2001 From: David Ray Date: Thu, 16 Jan 2014 08:28:45 -0500 Subject: [PATCH 522/751] fix typo --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 2018f9a9..bdf75e6f 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ import symposion def read_file(filename): """Read a file into a string.""" path = os.path.abspath(os.path.dirname(__file__)) - filepath = os.path.ojoin(path, filename) + filepath = os.path.join(path, filename) try: return open(filepath).read() except IOError: From c654c5aabbf1db1d1aab690619e05d45bb730d92 Mon Sep 17 00:00:00 2001 From: David Ray Date: Thu, 16 Jan 2014 10:48:39 -0500 Subject: [PATCH 523/751] restore settings import as django-appconf relies on it being there --- symposion/conf.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/symposion/conf.py b/symposion/conf.py index dd2a6d50..ad9a1324 100644 --- a/symposion/conf.py +++ b/symposion/conf.py @@ -1,3 +1,5 @@ +from django.conf import settings + from appconf import AppConf From 6ab7e498ece973bacfe12726a713e5693e52171e Mon Sep 17 00:00:00 2001 From: Rebecca Lovewell Date: Fri, 31 Jan 2014 17:27:58 -0500 Subject: [PATCH 524/751] Upgrade django-markitup from 1.0.0 -> 2.1 For Django 1.6 compatibility. --- requirements/base.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/base.txt b/requirements/base.txt index 2ee30364..5c625849 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -1,7 +1,7 @@ Django==1.4.5 django-appconf==0.5 django-forms-bootstrap==2.0.3.post2 -django-markitup==1.0.0 +django-markitup==2.1 django-model-utils==1.1.0 django-reversion==1.7 django-sitetree==0.9.4 From 8b7b09731484f5bf8fd2d50d54beaf85a188d6b5 Mon Sep 17 00:00:00 2001 From: Rebecca Lovewell Date: Fri, 31 Jan 2014 17:28:54 -0500 Subject: [PATCH 525/751] Update django-reversion from 1.7 -> 1.8 For Django 1.6 compatibility. --- requirements/base.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/base.txt b/requirements/base.txt index 5c625849..47519e56 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -3,7 +3,7 @@ django-appconf==0.5 django-forms-bootstrap==2.0.3.post2 django-markitup==2.1 django-model-utils==1.1.0 -django-reversion==1.7 +django-reversion==1.8 django-sitetree==0.9.4 django-taggit==0.9.3 django-timezones==0.2 From 6d4e7b1eb1660cab6095ca5ba50eb49c276abaf8 Mon Sep 17 00:00:00 2001 From: Rebecca Lovewell Date: Fri, 31 Jan 2014 17:29:36 -0500 Subject: [PATCH 526/751] Update django-sitetree from 0.9.4 -> 1.0.0 For Django 1.6 compatibility. --- requirements/base.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/base.txt b/requirements/base.txt index 47519e56..03bdf391 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -4,7 +4,7 @@ django-forms-bootstrap==2.0.3.post2 django-markitup==2.1 django-model-utils==1.1.0 django-reversion==1.8 -django-sitetree==0.9.4 +django-sitetree==1.0.0 django-taggit==0.9.3 django-timezones==0.2 django-user-accounts==1.0b13 From 8e852901071a320a0796701ef0ae8bf331877e94 Mon Sep 17 00:00:00 2001 From: Rebecca Lovewell Date: Fri, 31 Jan 2014 17:30:05 -0500 Subject: [PATCH 527/751] Update django-taggit from 0.9.3 -> 0.11.2 For Django 1.6 compatibility. --- requirements/base.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/base.txt b/requirements/base.txt index 03bdf391..31452f50 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -5,7 +5,7 @@ django-markitup==2.1 django-model-utils==1.1.0 django-reversion==1.8 django-sitetree==1.0.0 -django-taggit==0.9.3 +django-taggit==0.11.2 django-timezones==0.2 django-user-accounts==1.0b13 easy-thumbnails==1.2 From e92dee1e0e33c87dce88387364996b460a9c3d36 Mon Sep 17 00:00:00 2001 From: Rebecca Lovewell Date: Fri, 31 Jan 2014 17:30:32 -0500 Subject: [PATCH 528/751] Update easy-thumbnails from 1.2 -> 1.4 For Django 1.6 compatibility. --- requirements/base.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/base.txt b/requirements/base.txt index 31452f50..bffba110 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -8,6 +8,6 @@ django-sitetree==1.0.0 django-taggit==0.11.2 django-timezones==0.2 django-user-accounts==1.0b13 -easy-thumbnails==1.2 +easy-thumbnails==1.4 html5lib==0.95 markdown==2.3.1 From 8d5461d05de53197cb3d90ed9098877418917392 Mon Sep 17 00:00:00 2001 From: Rebecca Lovewell Date: Fri, 31 Jan 2014 17:31:32 -0500 Subject: [PATCH 529/751] Loosen Django requirement So that each project may control which version of Django to use. Not all of the latest requirements are compatible with Django 1.4 anymore. --- requirements/base.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/base.txt b/requirements/base.txt index bffba110..11be4891 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -1,4 +1,4 @@ -Django==1.4.5 +Django>=1.5,<=1.6 django-appconf==0.5 django-forms-bootstrap==2.0.3.post2 django-markitup==2.1 From 9833a6a27cb7107b4b8d5f55c3c74f69229fe2d0 Mon Sep 17 00:00:00 2001 From: Rebecca Lovewell Date: Tue, 11 Feb 2014 09:24:03 -0500 Subject: [PATCH 530/751] Loosen django-forms-bootstrap requirement --- requirements/base.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/base.txt b/requirements/base.txt index 11be4891..05a5a605 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -1,6 +1,6 @@ Django>=1.5,<=1.6 django-appconf==0.5 -django-forms-bootstrap==2.0.3.post2 +django-forms-bootstrap>=2.0.3.post2 django-markitup==2.1 django-model-utils==1.1.0 django-reversion==1.8 From bc3dfd6c4c2585520377307e1299c37e3500019c Mon Sep 17 00:00:00 2001 From: Rebecca Lovewell Date: Tue, 11 Feb 2014 11:26:56 -0500 Subject: [PATCH 531/751] Redirect to dashboard if there are no benefits to edit. The sponsor object has been created, and we only need to collect details about the potential-sponsor's benefits. If there are no benefits, the user should be redirected to the dashboard rather than forced to submit what appears to be the same form twice. --- symposion/sponsorship/views.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/symposion/sponsorship/views.py b/symposion/sponsorship/views.py index 22b4f243..4c39f812 100644 --- a/symposion/sponsorship/views.py +++ b/symposion/sponsorship/views.py @@ -15,7 +15,16 @@ def sponsor_apply(request): form = SponsorApplicationForm(request.POST, user=request.user) if form.is_valid(): sponsor = form.save() - return redirect("sponsor_detail", pk=sponsor.pk) + 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.") + return redirect("sponsor_detail", pk=sponsor.pk) + else: + messages.success(request, "Thank you for your sponsorship " + "application.") + return redirect("dashboard") else: form = SponsorApplicationForm(user=request.user) From 6de322216d9585670c53caf3c159ced8e1256737 Mon Sep 17 00:00:00 2001 From: Rebecca Lovewell Date: Tue, 25 Feb 2014 21:36:32 -0500 Subject: [PATCH 532/751] Show message when user does not have permission to access proposal submission page --- symposion/proposals/views.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/symposion/proposals/views.py b/symposion/proposals/views.py index f8dde53c..c23fc803 100644 --- a/symposion/proposals/views.py +++ b/symposion/proposals/views.py @@ -4,6 +4,7 @@ import sys from django.conf import settings from django.core.exceptions import ObjectDoesNotExist +from django.core.urlresolvers import reverse from django.db.models import Q from django.http import Http404, HttpResponse, HttpResponseForbidden from django.shortcuts import render, redirect, get_object_or_404 @@ -31,11 +32,18 @@ def get_form(name): def proposal_submit(request): if not request.user.is_authenticated(): + messages.info(request, "To submit a proposal, please " + "log in and create a speaker profile " + "via the dashboard.".format(settings.LOGIN_URL)) return redirect("home") # @@@ unauth'd speaker info page? else: try: request.user.speaker_profile except ObjectDoesNotExist: + url = reverse("speaker_create") + messages.info(request, "To submit a proposal, first " + "create a speaker " + "profile.".format(url)) return redirect("dashboard") kinds = [] From 2f75033dc5c8369b150fcd964e0e99de8b3f08de Mon Sep 17 00:00:00 2001 From: David Ray Date: Wed, 26 Feb 2014 13:56:19 -0500 Subject: [PATCH 533/751] update dep --- requirements/base.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/base.txt b/requirements/base.txt index 05a5a605..b503a4ba 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -2,7 +2,7 @@ Django>=1.5,<=1.6 django-appconf==0.5 django-forms-bootstrap>=2.0.3.post2 django-markitup==2.1 -django-model-utils==1.1.0 +django-model-utils==2.0.2 django-reversion==1.8 django-sitetree==1.0.0 django-taggit==0.11.2 From 4d1e9cf78e3108fab2d2b8c206ae3c87d29d93a0 Mon Sep 17 00:00:00 2001 From: David Ray Date: Fri, 28 Feb 2014 10:55:54 -0500 Subject: [PATCH 534/751] work on #6, needs tests --- symposion/schedule/forms.py | 119 +++++++++++++++++- symposion/schedule/views.py | 16 ++- .../templates/schedule/schedule_edit.html | 24 +++- 3 files changed, 152 insertions(+), 7 deletions(-) diff --git a/symposion/schedule/forms.py b/symposion/schedule/forms.py index 63316743..3e4c9ff4 100644 --- a/symposion/schedule/forms.py +++ b/symposion/schedule/forms.py @@ -1,13 +1,21 @@ +import csv +import time + +from datetime import datetime + from django import forms +from django.contrib import messages +from django.db import IntegrityError, transaction from django.db.models import Q from markitup.widgets import MarkItUpWidget -from symposion.schedule.models import Presentation +from symposion.schedule.models import (Day, Presentation, Room, SlotKind, Slot, + SlotRoom) class SlotEditForm(forms.Form): - + def __init__(self, *args, **kwargs): self.slot = kwargs.pop("slot") super(SlotEditForm, self).__init__(*args, **kwargs) @@ -16,7 +24,7 @@ class SlotEditForm(forms.Form): self.fields["presentation"] = self.build_presentation_field() else: self.fields["content_override"] = self.build_content_override_field() - + def build_presentation_field(self): kwargs = {} queryset = Presentation.objects.all() @@ -31,7 +39,7 @@ class SlotEditForm(forms.Form): kwargs["required"] = True kwargs["queryset"] = queryset return forms.ModelChoiceField(**kwargs) - + def build_content_override_field(self): kwargs = { "label": "Content", @@ -40,3 +48,106 @@ class SlotEditForm(forms.Form): "initial": self.slot.content_override, } return forms.CharField(**kwargs) + + +class ScheduleSectionForm(forms.Form): + ROOM_KEY = 'room' + DATE_KEY = 'date' + START_KEY = 'time_start' + END_KEY = 'time_end' + KIND = 'kind' + + filename = forms.FileField( + label='Select a CSV file to import:', + required=False + ) + + def __init__(self, *args, **kwargs): + self.schedule = kwargs.pop("schedule") + super(ScheduleSectionForm, self).__init__(*args, **kwargs) + + def clean_filename(self): + if 'submit' in self.data: + fname = self.cleaned_data.get('filename') + if not fname or not fname.name.endswith('.csv'): + raise forms.ValidationError(u'Please upload a .csv file') + return fname + + def _get_start_end_times(self, data): + "Return start and end time objects" + start_time = time.strptime(data[self.START_KEY], '%I:%M %p') + start = datetime(100, 1, 1, start_time.tm_hour, start_time.tm_min, 00) + end_time = time.strptime(data[self.END_KEY], '%I:%M %p') + end = datetime(100, 1, 1, end_time.tm_hour, end_time.tm_min, 00) + return start.time(), end.time() + + def _build_rooms(self, data): + "Get or Create Rooms based on schedule type and set of Tracks" + created_rooms = [] + rooms = sorted(set([x[self.ROOM_KEY] for x in data])) + for i, room in enumerate(rooms): + room, created = Room.objects.get_or_create( + schedule=self.schedule, name=room, order=i + ) + if created: + created_rooms.append(room) + return created_rooms + + def _build_days(self, data): + "Get or Create Days based on schedule type and set of Days" + created_days = [] + days = set([x[self.DATE_KEY] for x in data]) + for day in days: + date = datetime.strptime(day, "%m/%d/%Y") + day, created = Day.objects.get_or_create( + schedule=self.schedule, date=date + ) + if created: + created_days.append(day) + return created_days + + def build_schedule(self): + created_items = [] + reader = csv.DictReader(self.cleaned_data.get('filename')) + data = [dict((k.strip(), v.strip()) for k, v in x.items()) for x in reader] + # build rooms + created_items.extend(self._build_rooms(data)) + # build_days + created_items.extend(self._build_days(data)) + # build Slot -> SlotRoom + for row in data: + room = Room.objects.get( + schedule=self.schedule, name=row[self.ROOM_KEY] + ) + date = datetime.strptime(row[self.DATE_KEY], "%m/%d/%Y") + day = Day.objects.get(schedule=self.schedule, date=date) + start, end = self._get_start_end_times(row) + slot_kind, created = SlotKind.objects.get_or_create( + label=row[self.KIND], schedule=self.schedule + ) + if created: + created_items.append(slot_kind) + if row[self.KIND] == 'plenary': + slot, created = Slot.objects.get_or_create( + kind=slot_kind, day=day, start=start, end=end + ) + if created: + created_items.append(slot) + else: + slot = Slot.objects.create( + kind=slot_kind, day=day, start=start, end=end + ) + created_items.append(slot) + try: + SlotRoom.objects.create(slot=slot, room=room) + except IntegrityError: + transaction.rollback() + # delete all created objects and report error + for x in created_items: + x.delete() + return messages.ERROR, u'An overlap occurred; the import was cancelled.' + return messages.SUCCESS, u'Your schedule has been imported.' + + def delete_schedule(self): + self.schedule.day_set.all().delete() + return messages.SUCCESS, u'Your schedule has been deleted.' diff --git a/symposion/schedule/views.py b/symposion/schedule/views.py index 1367808b..6b229c09 100644 --- a/symposion/schedule/views.py +++ b/symposion/schedule/views.py @@ -3,8 +3,9 @@ from django.shortcuts import render, get_object_or_404, redirect from django.template import loader, Context from django.contrib.auth.decorators import login_required +from django.contrib import messages -from symposion.schedule.forms import SlotEditForm +from symposion.schedule.forms import SlotEditForm, ScheduleSectionForm from symposion.schedule.models import Schedule, Day, Slot, Presentation from symposion.schedule.timetable import TimeTable @@ -100,11 +101,24 @@ def schedule_edit(request, slug=None): schedule = fetch_schedule(slug) + if request.method == "POST": + form = ScheduleSectionForm( + request.POST, request.FILES, schedule=schedule + ) + if form.is_valid(): + if 'submit' in form.data: + msg = form.build_schedule() + elif 'delete' in form.data: + msg = form.delete_schedule() + messages.add_message(request, msg[0], msg[1]) + else: + form = ScheduleSectionForm(schedule=schedule) days_qs = Day.objects.filter(schedule=schedule) days = [TimeTable(day) for day in days_qs] ctx = { "schedule": schedule, "days": days, + "form": form } return render(request, "schedule/schedule_edit.html", ctx) diff --git a/symposion/templates/schedule/schedule_edit.html b/symposion/templates/schedule/schedule_edit.html index 5de11af2..d6713aa5 100644 --- a/symposion/templates/schedule/schedule_edit.html +++ b/symposion/templates/schedule/schedule_edit.html @@ -18,13 +18,19 @@

    Schedule Edit

    - + {% for timetable in days %}

    {{ timetable.day.date }}

    {% include "schedule/_edit_grid.html" %} {% endfor %}
    - + {% if request.user.is_staff %} +
    {% csrf_token %} + {{ form.as_p }} + + +
    + {% endif %}
    {% endblock %} @@ -41,5 +47,19 @@ e.preventDefault(); }); }); + $(function() { + //submit event handler + $("form#schedule-builder :submit").click(function(e) { + var name = this.name; + if(name == 'delete') { + if (!confirm("Are you sure you want to delete the schedule?")) + { + e.preventDefault(); + return; + } + } + }); + }); + {% endblock %} From 5f27b014522327c6cf7fa867bc020d1ea32c1384 Mon Sep 17 00:00:00 2001 From: David Ray Date: Fri, 28 Feb 2014 12:17:48 -0500 Subject: [PATCH 535/751] remove unecessary check --- symposion/templates/schedule/schedule_edit.html | 2 -- 1 file changed, 2 deletions(-) diff --git a/symposion/templates/schedule/schedule_edit.html b/symposion/templates/schedule/schedule_edit.html index d6713aa5..9b1ab864 100644 --- a/symposion/templates/schedule/schedule_edit.html +++ b/symposion/templates/schedule/schedule_edit.html @@ -24,13 +24,11 @@ {% include "schedule/_edit_grid.html" %} {% endfor %} - {% if request.user.is_staff %}
    {% csrf_token %} {{ form.as_p }}
    - {% endif %} {% endblock %} From 6102c4e5e4070458030433292bf307e3b3335bbf Mon Sep 17 00:00:00 2001 From: David Ray Date: Mon, 3 Mar 2014 08:38:47 -0500 Subject: [PATCH 536/751] adding back in ws --- symposion/schedule/forms.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/symposion/schedule/forms.py b/symposion/schedule/forms.py index 3e4c9ff4..16187b29 100644 --- a/symposion/schedule/forms.py +++ b/symposion/schedule/forms.py @@ -15,7 +15,7 @@ from symposion.schedule.models import (Day, Presentation, Room, SlotKind, Slot, class SlotEditForm(forms.Form): - + def __init__(self, *args, **kwargs): self.slot = kwargs.pop("slot") super(SlotEditForm, self).__init__(*args, **kwargs) @@ -24,7 +24,7 @@ class SlotEditForm(forms.Form): self.fields["presentation"] = self.build_presentation_field() else: self.fields["content_override"] = self.build_content_override_field() - + def build_presentation_field(self): kwargs = {} queryset = Presentation.objects.all() @@ -39,7 +39,7 @@ class SlotEditForm(forms.Form): kwargs["required"] = True kwargs["queryset"] = queryset return forms.ModelChoiceField(**kwargs) - + def build_content_override_field(self): kwargs = { "label": "Content", From 62289cad36c57905a778a5b53875b91474f7840e Mon Sep 17 00:00:00 2001 From: David Ray Date: Mon, 3 Mar 2014 13:47:46 -0500 Subject: [PATCH 537/751] make private methods more robust --- symposion/schedule/forms.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/symposion/schedule/forms.py b/symposion/schedule/forms.py index 16187b29..8d709197 100644 --- a/symposion/schedule/forms.py +++ b/symposion/schedule/forms.py @@ -75,11 +75,15 @@ class ScheduleSectionForm(forms.Form): def _get_start_end_times(self, data): "Return start and end time objects" - start_time = time.strptime(data[self.START_KEY], '%I:%M %p') - start = datetime(100, 1, 1, start_time.tm_hour, start_time.tm_min, 00) - end_time = time.strptime(data[self.END_KEY], '%I:%M %p') - end = datetime(100, 1, 1, end_time.tm_hour, end_time.tm_min, 00) - return start.time(), end.time() + times = [] + for x in [data[self.START_KEY], data[self.END_KEY]]: + try: + time_obj = time.strptime(x, '%I:%M %p') + except: + return messages.ERROR, u'Malformed time found: %s.' % x + time_obj = datetime(100, 1, 1, time_obj.tm_hour, time_obj.tm_min, 00) + times.append(time_obj.time()) + return times def _build_rooms(self, data): "Get or Create Rooms based on schedule type and set of Tracks" @@ -98,7 +102,11 @@ class ScheduleSectionForm(forms.Form): created_days = [] days = set([x[self.DATE_KEY] for x in data]) for day in days: - date = datetime.strptime(day, "%m/%d/%Y") + try: + date = datetime.strptime(day, "%m/%d/%Y") + except ValueError: + [x.delete() for x in created_days] + return messages.ERROR, u'Malformed data found: %s.' % day day, created = Day.objects.get_or_create( schedule=self.schedule, date=date ) @@ -139,9 +147,9 @@ class ScheduleSectionForm(forms.Form): ) created_items.append(slot) try: - SlotRoom.objects.create(slot=slot, room=room) + with transaction.atomic(): + SlotRoom.objects.create(slot=slot, room=room) except IntegrityError: - transaction.rollback() # delete all created objects and report error for x in created_items: x.delete() From 0ce5e36c24fbca4d5ea6466a50d6349c88ada11c Mon Sep 17 00:00:00 2001 From: David Ray Date: Mon, 3 Mar 2014 13:48:24 -0500 Subject: [PATCH 538/751] add tests for build schedule form --- symposion/schedule/tests/__init__.py | 0 symposion/schedule/tests/data/schedule.csv | 13 ++ .../schedule/tests/data/schedule_overlap.csv | 14 ++ symposion/schedule/tests/test_forms.py | 156 ++++++++++++++++++ 4 files changed, 183 insertions(+) create mode 100644 symposion/schedule/tests/__init__.py create mode 100644 symposion/schedule/tests/data/schedule.csv create mode 100644 symposion/schedule/tests/data/schedule_overlap.csv create mode 100644 symposion/schedule/tests/test_forms.py diff --git a/symposion/schedule/tests/__init__.py b/symposion/schedule/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/symposion/schedule/tests/data/schedule.csv b/symposion/schedule/tests/data/schedule.csv new file mode 100644 index 00000000..66a87bdc --- /dev/null +++ b/symposion/schedule/tests/data/schedule.csv @@ -0,0 +1,13 @@ +"date","time_start","time_end","kind"," room " +"12/12/2013","10:00 AM","11:00 AM","plenary","Room2" +"12/12/2013","10:00 AM","11:00 AM","plenary","Room1" +"12/12/2013","11:00 AM","12:00 PM","talk","Room1" +"12/12/2013","11:00 AM","12:00 PM","talk","Room2" +"12/12/2013","12:00 PM","12:45 PM","plenary","Room1" +"12/12/2013","12:00 PM","12:45 PM","plenary","Room2" +"12/13/2013","10:00 AM","11:00 AM","plenary","Room2" +"12/13/2013","10:00 AM","11:00 AM","plenary","Room1" +"12/13/2013","11:00 AM","12:00 PM","talk","Room1" +"12/13/2013","11:00 AM","12:00 PM","talk","Room2" +"12/13/2013","12:00 PM","12:45 PM","plenary","Room1" +"12/13/2013","12:00 PM","12:45 PM","plenary","Room2" diff --git a/symposion/schedule/tests/data/schedule_overlap.csv b/symposion/schedule/tests/data/schedule_overlap.csv new file mode 100644 index 00000000..3c02ce2b --- /dev/null +++ b/symposion/schedule/tests/data/schedule_overlap.csv @@ -0,0 +1,14 @@ +"date","time_start","time_end","kind"," room " +"12/12/2013","10:00 AM","11:00 AM","plenary","Room2" +"12/12/2013","10:00 AM","11:00 AM","plenary","Room1" +"12/12/2013","11:00 AM","12:00 PM","talk","Room1" +"12/12/2013","11:00 AM","12:00 PM","talk","Room2" +"12/12/2013","12:00 PM","12:45 PM","plenary","Room1" +"12/12/2013","12:00 PM","12:45 PM","plenary","Room2" +"12/13/2013","10:00 AM","11:00 AM","plenary","Room2" +"12/13/2013","10:00 AM","11:00 AM","plenary","Room1" +"12/13/2013","11:00 AM","12:00 PM","talk","Room1" +"12/13/2013","11:00 AM","12:00 PM","talk","Room2" +"12/13/2013","12:00 PM","12:45 PM","plenary","Room1" +"12/13/2013","12:00 PM","12:45 PM","plenary","Room2" +"12/13/2013","12:00 PM","12:45 PM","plenary","Room2" diff --git a/symposion/schedule/tests/test_forms.py b/symposion/schedule/tests/test_forms.py new file mode 100644 index 00000000..883d7ac0 --- /dev/null +++ b/symposion/schedule/tests/test_forms.py @@ -0,0 +1,156 @@ +import os + +from datetime import datetime, timedelta + +from django.core.files.uploadedfile import SimpleUploadedFile +from django.test import TestCase + +from symposion.conference.models import Conference, Section + +from ..forms import ScheduleSectionForm +from ..models import Day, Room, Schedule, Slot, SlotKind + +DATA_DIR = os.path.join(os.path.dirname(__file__), 'data') + + +class ScheduleSectionFormTests(TestCase): + + def setUp(self): + self.conference = Conference.objects.create(title='test') + self.section = Section.objects.create( + conference=self.conference, + name='test') + self.schedule = Schedule.objects.create(section=self.section) + self.today = datetime.now() + self.tomorrow = self.today + timedelta(days=1) + + def test_clean_filename(self): + """Ensure a file is provided if the submit action was utilized""" + data = {'submit': 'Submit'} + form = ScheduleSectionForm(data=data, schedule=self.schedule) + self.assertIn('filename', form.errors) + + def test_clean_filename_not_required(self): + """Ensure file is not required if the delete action was utilize""" + data = {'delete': 'Delete'} + form = ScheduleSectionForm(data=data, schedule=self.schedule) + self.assertTrue(form.is_valid()) + + def test_delete(self): + """Delete schedule (Days) for supplied section""" + Day.objects.create(schedule=self.schedule, date=self.today) + Day.objects.create(schedule=self.schedule, date=self.tomorrow) + other_section = Section.objects.create(conference=self.conference, name='other') + other_schedule = Schedule.objects.create(section=other_section) + other_day = Day.objects.create(schedule=other_schedule, date=self.tomorrow) + self.assertEqual(3, Day.objects.all().count()) + data = {'delete': 'Delete'} + form = ScheduleSectionForm(data=data, schedule=self.schedule) + form.delete_schedule() + days = Day.objects.all() + self.assertEqual(1, days.count()) + self.assertIn(other_day, days) + + def test_build_days(self): + """Test private method to build days based off ingested CSV""" + form = ScheduleSectionForm(schedule=self.schedule) + data = ( + {'date': datetime.strftime(self.today, "%m/%d/%Y")}, + {'date': datetime.strftime(self.today, "%m/%d/%Y")}, + {'date': datetime.strftime(self.tomorrow, "%m/%d/%Y")}, + ) + self.assertEqual(0, Day.objects.all().count()) + form._build_days(data) + self.assertEqual(2, Day.objects.all().count()) + + def test_build_days_malformed(self): + """Test failure for malformed date in CSV""" + form = ScheduleSectionForm(schedule=self.schedule) + data = ( + {'date': datetime.strftime(self.today, "%m/%d/%Y")}, + {'date': '12-12-12'} + ) + self.assertEqual(0, Day.objects.all().count()) + msg_type, msg = form._build_days(data) + self.assertEqual(0, Day.objects.all().count()) + self.assertEqual(40, msg_type) + self.assertIn('12-12-12', msg) + + def test_build_rooms(self): + """Test private method to build rooms based off ingested CSV""" + form = ScheduleSectionForm(schedule=self.schedule) + data = ( + {'room': 'foo'}, + {'room': 'bar'}, + {'room': 'foo'}, + ) + self.assertEqual(0, Room.objects.all().count()) + form._build_rooms(data) + self.assertEqual(2, Room.objects.all().count()) + + def test_get_start_end_times(self): + """ + Test private method to convert start and end times based off + ingested CSV + """ + form = ScheduleSectionForm(schedule=self.schedule) + start = '12:00 PM' + end = '01:00 PM' + data = {'time_start': start, 'time_end': end} + start_time, end_time = form._get_start_end_times(data) + self.assertEqual(start, start_time.strftime('%I:%M %p')) + self.assertEqual(end, end_time.strftime('%I:%M %p')) + + def test_get_start_end_times_malformed(self): + """ + Test private method for malformed time based off ingested CSV + """ + form = ScheduleSectionForm(schedule=self.schedule) + start = '12:00' + end = '01:00' + data = {'time_start': start, 'time_end': end} + msg_type, msg = form._get_start_end_times(data) + self.assertEqual(40, msg_type) + self.assertIn('Malformed', msg) + + def test_build_schedule(self): + """ + Test successful schedule build based off ingested CSV + """ + self.assertEqual(0, Day.objects.all().count()) + self.assertEqual(0, Room.objects.all().count()) + self.assertEqual(0, Slot.objects.all().count()) + self.assertEqual(0, SlotKind.objects.all().count()) + schedule_csv = open(os.path.join(DATA_DIR, 'schedule.csv'), 'rb') + file_data = {'filename': SimpleUploadedFile(schedule_csv.name, schedule_csv.read())} + data = {'submit': 'Submit'} + form = ScheduleSectionForm(data, file_data, schedule=self.schedule) + form.is_valid() + msg_type, msg = form.build_schedule() + self.assertEqual(25, msg_type) + self.assertIn('imported', msg) + self.assertEqual(2, Day.objects.all().count()) + self.assertEqual(2, Room.objects.all().count()) + self.assertEqual(8, Slot.objects.all().count()) + self.assertEqual(2, SlotKind.objects.all().count()) + + def test_build_schedule_overlap(self): + """ + Test rolledback schedule build based off ingested CSV with Slot overlap + """ + self.assertEqual(0, Day.objects.all().count()) + self.assertEqual(0, Room.objects.all().count()) + self.assertEqual(0, Slot.objects.all().count()) + self.assertEqual(0, SlotKind.objects.all().count()) + schedule_csv = open(os.path.join(DATA_DIR, 'schedule_overlap.csv'), 'rb') + file_data = {'filename': SimpleUploadedFile(schedule_csv.name, schedule_csv.read())} + data = {'submit': 'Submit'} + form = ScheduleSectionForm(data, file_data, schedule=self.schedule) + form.is_valid() + msg_type, msg = form.build_schedule() + self.assertEqual(40, msg_type) + self.assertIn('overlap', msg) + self.assertEqual(0, Day.objects.all().count()) + self.assertEqual(0, Room.objects.all().count()) + self.assertEqual(0, Slot.objects.all().count()) + self.assertEqual(0, SlotKind.objects.all().count()) From 55ad74ab1206a54d56104ec965d472f322066395 Mon Sep 17 00:00:00 2001 From: David Ray Date: Mon, 3 Mar 2014 14:10:28 -0500 Subject: [PATCH 539/751] add schedule docs --- docs/index.rst | 1 + docs/schedule.rst | 49 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 docs/schedule.rst diff --git a/docs/index.rst b/docs/index.rst index d8e9391e..732670ca 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -19,6 +19,7 @@ Apps: sponsorship speakers proposals + schedule Indices and tables diff --git a/docs/schedule.rst b/docs/schedule.rst new file mode 100644 index 00000000..13d942a8 --- /dev/null +++ b/docs/schedule.rst @@ -0,0 +1,49 @@ +Schedule App +=========== + +The ``schedule`` app allows staff members to create the schedule for the +conference's presentations, breaks, lunches, etc. + +The ```schedule``` app has a number of models that facilitate building the +structured schedule: + + * Schedule: A high level container that maps to each Conference Section. + * Day: A Day associated with a Schedule. + * Room: A Room associated with a Schedule. + * Slot Kind: A type of Slot associated with a Schedule. + * Slot: A discreet time period for a Schedule. + * Slot Room: A mapping of a Room and Slot for a given Schedule. + * Presentation: A mapping of a Slot to an approved Proposal from the ```proposals``` app. + +Schedule Builder Form +--------------------- + +It can be cumbersone to generate a schedule through the admin. With that in mind, +a generic schedule builder is available via a Schedule's edit view. For instance, +if a Conference site has a Talks Section and Schedule, the form would be +available for Staff at:: + +/schedule/talks/edit + +The form consume a structured CSV file, from which it will build the schedule. +Sample CSV data is included below:: + +"date","time_start","time_end","kind"," room " +"12/12/2013","10:00 AM","11:00 AM","plenary","Room2" +"12/12/2013","10:00 AM","11:00 AM","plenary","Room1" +"12/12/2013","11:00 AM","12:00 PM","talk","Room1" +"12/12/2013","11:00 AM","12:00 PM","talk","Room2" +"12/12/2013","12:00 PM","12:45 PM","plenary","Room1" +"12/12/2013","12:00 PM","12:45 PM","plenary","Room2" +"12/13/2013","10:00 AM","11:00 AM","plenary","Room2" +"12/13/2013","10:00 AM","11:00 AM","plenary","Room1" +"12/13/2013","11:00 AM","12:00 PM","talk","Room1" +"12/13/2013","11:00 AM","12:00 PM","talk","Room2" +"12/13/2013","12:00 PM","12:45 PM","plenary","Room1" +"12/13/2013","12:00 PM","12:45 PM","plenary","Room2" + +It is worth noting that this generates the **structure** of the schedule. It +does not create Presentation objects. This will need to be done manually. + +One can also **delete** an existing schedule via the delete action. This is +irreversible (save for a database restore). From 0e4dcef2a0b847f6abbaaddf2398591b33d69c5c Mon Sep 17 00:00:00 2001 From: David Ray Date: Tue, 4 Mar 2014 08:47:19 -0500 Subject: [PATCH 540/751] fix typos --- docs/schedule.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/schedule.rst b/docs/schedule.rst index 13d942a8..15d37fa1 100644 --- a/docs/schedule.rst +++ b/docs/schedule.rst @@ -11,21 +11,21 @@ structured schedule: * Day: A Day associated with a Schedule. * Room: A Room associated with a Schedule. * Slot Kind: A type of Slot associated with a Schedule. - * Slot: A discreet time period for a Schedule. + * Slot: A discrete time period for a Schedule. * Slot Room: A mapping of a Room and Slot for a given Schedule. * Presentation: A mapping of a Slot to an approved Proposal from the ```proposals``` app. Schedule Builder Form --------------------- -It can be cumbersone to generate a schedule through the admin. With that in mind, +It can be cumbersome to generate a schedule through the admin. With that in mind, a generic schedule builder is available via a Schedule's edit view. For instance, if a Conference site has a Talks Section and Schedule, the form would be available for Staff at:: /schedule/talks/edit -The form consume a structured CSV file, from which it will build the schedule. +The form consumes a structured CSV file, from which it will build the schedule. Sample CSV data is included below:: "date","time_start","time_end","kind"," room " From 1861ad29263dc86a4a63841334cd76c69e53ae54 Mon Sep 17 00:00:00 2001 From: Rebecca Lovewell Date: Tue, 11 Feb 2014 11:26:56 -0500 Subject: [PATCH 541/751] Redirect to dashboard if there are no benefits to edit. The sponsor object has been created, and we only need to collect details about the potential-sponsor's benefits. If there are no benefits, the user should be redirected to the dashboard rather than forced to submit what appears to be the same form twice. --- symposion/sponsorship/views.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/symposion/sponsorship/views.py b/symposion/sponsorship/views.py index 22b4f243..4c39f812 100644 --- a/symposion/sponsorship/views.py +++ b/symposion/sponsorship/views.py @@ -15,7 +15,16 @@ def sponsor_apply(request): form = SponsorApplicationForm(request.POST, user=request.user) if form.is_valid(): sponsor = form.save() - return redirect("sponsor_detail", pk=sponsor.pk) + 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.") + return redirect("sponsor_detail", pk=sponsor.pk) + else: + messages.success(request, "Thank you for your sponsorship " + "application.") + return redirect("dashboard") else: form = SponsorApplicationForm(user=request.user) From c5aad692ffc1dc6d3c38fcb32ece1933be83be1c Mon Sep 17 00:00:00 2001 From: Rebecca Lovewell Date: Tue, 25 Feb 2014 21:36:32 -0500 Subject: [PATCH 542/751] Show message when user does not have permission to access proposal submission page --- symposion/proposals/views.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/symposion/proposals/views.py b/symposion/proposals/views.py index cf532c2b..71b6a549 100644 --- a/symposion/proposals/views.py +++ b/symposion/proposals/views.py @@ -3,6 +3,7 @@ import sys from django.conf import settings from django.core.exceptions import ObjectDoesNotExist +from django.core.urlresolvers import reverse from django.db.models import Q from django.http import Http404, HttpResponse, HttpResponseForbidden from django.shortcuts import render, redirect, get_object_or_404 @@ -31,11 +32,18 @@ def get_form(name): def proposal_submit(request): if not request.user.is_authenticated(): + messages.info(request, "To submit a proposal, please " + "log in and create a speaker profile " + "via the dashboard.".format(settings.LOGIN_URL)) return redirect("home") # @@@ unauth'd speaker info page? else: try: request.user.speaker_profile except ObjectDoesNotExist: + url = reverse("speaker_create") + messages.info(request, "To submit a proposal, first " + "create a speaker " + "profile.".format(url)) return redirect("dashboard") kinds = [] From 64a39b87062db22904b97fb82df4d2063ff8ed35 Mon Sep 17 00:00:00 2001 From: David Ray Date: Fri, 28 Feb 2014 12:17:48 -0500 Subject: [PATCH 543/751] resolve merge conflict --- symposion/templates/schedule/schedule_edit.html | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/symposion/templates/schedule/schedule_edit.html b/symposion/templates/schedule/schedule_edit.html index 5de11af2..b5335506 100644 --- a/symposion/templates/schedule/schedule_edit.html +++ b/symposion/templates/schedule/schedule_edit.html @@ -24,7 +24,11 @@ {% include "schedule/_edit_grid.html" %} {% endfor %} - +
    {% csrf_token %} + {{ form.as_p }} + + +
    {% endblock %} From 650ddd2bcc36f3ea7948fc8d3478e439b3f7fbe2 Mon Sep 17 00:00:00 2001 From: David Ray Date: Mon, 3 Mar 2014 13:47:46 -0500 Subject: [PATCH 545/751] resolve merge conf --- symposion/schedule/forms.py | 111 ++++++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) diff --git a/symposion/schedule/forms.py b/symposion/schedule/forms.py index 63316743..c66364e0 100644 --- a/symposion/schedule/forms.py +++ b/symposion/schedule/forms.py @@ -40,3 +40,114 @@ class SlotEditForm(forms.Form): "initial": self.slot.content_override, } return forms.CharField(**kwargs) + + +class ScheduleSectionForm(forms.Form): + ROOM_KEY = 'room' + DATE_KEY = 'date' + START_KEY = 'time_start' + END_KEY = 'time_end' + KIND = 'kind' + + filename = forms.FileField( + label='Select a CSV file to import:', + required=False + ) + + def __init__(self, *args, **kwargs): + self.schedule = kwargs.pop("schedule") + super(ScheduleSectionForm, self).__init__(*args, **kwargs) + + def clean_filename(self): + if 'submit' in self.data: + fname = self.cleaned_data.get('filename') + if not fname or not fname.name.endswith('.csv'): + raise forms.ValidationError(u'Please upload a .csv file') + return fname + + def _get_start_end_times(self, data): + "Return start and end time objects" + times = [] + for x in [data[self.START_KEY], data[self.END_KEY]]: + try: + time_obj = time.strptime(x, '%I:%M %p') + except: + return messages.ERROR, u'Malformed time found: %s.' % x + time_obj = datetime(100, 1, 1, time_obj.tm_hour, time_obj.tm_min, 00) + times.append(time_obj.time()) + return times + + def _build_rooms(self, data): + "Get or Create Rooms based on schedule type and set of Tracks" + created_rooms = [] + rooms = sorted(set([x[self.ROOM_KEY] for x in data])) + for i, room in enumerate(rooms): + room, created = Room.objects.get_or_create( + schedule=self.schedule, name=room, order=i + ) + if created: + created_rooms.append(room) + return created_rooms + + def _build_days(self, data): + "Get or Create Days based on schedule type and set of Days" + created_days = [] + days = set([x[self.DATE_KEY] for x in data]) + for day in days: + try: + date = datetime.strptime(day, "%m/%d/%Y") + except ValueError: + [x.delete() for x in created_days] + return messages.ERROR, u'Malformed data found: %s.' % day + day, created = Day.objects.get_or_create( + schedule=self.schedule, date=date + ) + if created: + created_days.append(day) + return created_days + + def build_schedule(self): + created_items = [] + reader = csv.DictReader(self.cleaned_data.get('filename')) + data = [dict((k.strip(), v.strip()) for k, v in x.items()) for x in reader] + # build rooms + created_items.extend(self._build_rooms(data)) + # build_days + created_items.extend(self._build_days(data)) + # build Slot -> SlotRoom + for row in data: + room = Room.objects.get( + schedule=self.schedule, name=row[self.ROOM_KEY] + ) + date = datetime.strptime(row[self.DATE_KEY], "%m/%d/%Y") + day = Day.objects.get(schedule=self.schedule, date=date) + start, end = self._get_start_end_times(row) + slot_kind, created = SlotKind.objects.get_or_create( + label=row[self.KIND], schedule=self.schedule + ) + if created: + created_items.append(slot_kind) + if row[self.KIND] == 'plenary': + slot, created = Slot.objects.get_or_create( + kind=slot_kind, day=day, start=start, end=end + ) + if created: + created_items.append(slot) + else: + slot = Slot.objects.create( + kind=slot_kind, day=day, start=start, end=end + ) + created_items.append(slot) + try: + with transaction.atomic(): + SlotRoom.objects.create(slot=slot, room=room) + except IntegrityError: + # delete all created objects and report error + for x in created_items: + x.delete() + return messages.ERROR, u'An overlap occurred; the import was cancelled.' + return messages.SUCCESS, u'Your schedule has been imported.' + + def delete_schedule(self): + self.schedule.day_set.all().delete() + return messages.SUCCESS, u'Your schedule has been deleted.' From b7ecf8984c10ada47dc701f69e63abcb37096b74 Mon Sep 17 00:00:00 2001 From: David Ray Date: Mon, 3 Mar 2014 13:48:24 -0500 Subject: [PATCH 546/751] add tests for build schedule form --- symposion/schedule/tests/__init__.py | 0 symposion/schedule/tests/data/schedule.csv | 13 ++ .../schedule/tests/data/schedule_overlap.csv | 14 ++ symposion/schedule/tests/test_forms.py | 156 ++++++++++++++++++ 4 files changed, 183 insertions(+) create mode 100644 symposion/schedule/tests/__init__.py create mode 100644 symposion/schedule/tests/data/schedule.csv create mode 100644 symposion/schedule/tests/data/schedule_overlap.csv create mode 100644 symposion/schedule/tests/test_forms.py diff --git a/symposion/schedule/tests/__init__.py b/symposion/schedule/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/symposion/schedule/tests/data/schedule.csv b/symposion/schedule/tests/data/schedule.csv new file mode 100644 index 00000000..66a87bdc --- /dev/null +++ b/symposion/schedule/tests/data/schedule.csv @@ -0,0 +1,13 @@ +"date","time_start","time_end","kind"," room " +"12/12/2013","10:00 AM","11:00 AM","plenary","Room2" +"12/12/2013","10:00 AM","11:00 AM","plenary","Room1" +"12/12/2013","11:00 AM","12:00 PM","talk","Room1" +"12/12/2013","11:00 AM","12:00 PM","talk","Room2" +"12/12/2013","12:00 PM","12:45 PM","plenary","Room1" +"12/12/2013","12:00 PM","12:45 PM","plenary","Room2" +"12/13/2013","10:00 AM","11:00 AM","plenary","Room2" +"12/13/2013","10:00 AM","11:00 AM","plenary","Room1" +"12/13/2013","11:00 AM","12:00 PM","talk","Room1" +"12/13/2013","11:00 AM","12:00 PM","talk","Room2" +"12/13/2013","12:00 PM","12:45 PM","plenary","Room1" +"12/13/2013","12:00 PM","12:45 PM","plenary","Room2" diff --git a/symposion/schedule/tests/data/schedule_overlap.csv b/symposion/schedule/tests/data/schedule_overlap.csv new file mode 100644 index 00000000..3c02ce2b --- /dev/null +++ b/symposion/schedule/tests/data/schedule_overlap.csv @@ -0,0 +1,14 @@ +"date","time_start","time_end","kind"," room " +"12/12/2013","10:00 AM","11:00 AM","plenary","Room2" +"12/12/2013","10:00 AM","11:00 AM","plenary","Room1" +"12/12/2013","11:00 AM","12:00 PM","talk","Room1" +"12/12/2013","11:00 AM","12:00 PM","talk","Room2" +"12/12/2013","12:00 PM","12:45 PM","plenary","Room1" +"12/12/2013","12:00 PM","12:45 PM","plenary","Room2" +"12/13/2013","10:00 AM","11:00 AM","plenary","Room2" +"12/13/2013","10:00 AM","11:00 AM","plenary","Room1" +"12/13/2013","11:00 AM","12:00 PM","talk","Room1" +"12/13/2013","11:00 AM","12:00 PM","talk","Room2" +"12/13/2013","12:00 PM","12:45 PM","plenary","Room1" +"12/13/2013","12:00 PM","12:45 PM","plenary","Room2" +"12/13/2013","12:00 PM","12:45 PM","plenary","Room2" diff --git a/symposion/schedule/tests/test_forms.py b/symposion/schedule/tests/test_forms.py new file mode 100644 index 00000000..883d7ac0 --- /dev/null +++ b/symposion/schedule/tests/test_forms.py @@ -0,0 +1,156 @@ +import os + +from datetime import datetime, timedelta + +from django.core.files.uploadedfile import SimpleUploadedFile +from django.test import TestCase + +from symposion.conference.models import Conference, Section + +from ..forms import ScheduleSectionForm +from ..models import Day, Room, Schedule, Slot, SlotKind + +DATA_DIR = os.path.join(os.path.dirname(__file__), 'data') + + +class ScheduleSectionFormTests(TestCase): + + def setUp(self): + self.conference = Conference.objects.create(title='test') + self.section = Section.objects.create( + conference=self.conference, + name='test') + self.schedule = Schedule.objects.create(section=self.section) + self.today = datetime.now() + self.tomorrow = self.today + timedelta(days=1) + + def test_clean_filename(self): + """Ensure a file is provided if the submit action was utilized""" + data = {'submit': 'Submit'} + form = ScheduleSectionForm(data=data, schedule=self.schedule) + self.assertIn('filename', form.errors) + + def test_clean_filename_not_required(self): + """Ensure file is not required if the delete action was utilize""" + data = {'delete': 'Delete'} + form = ScheduleSectionForm(data=data, schedule=self.schedule) + self.assertTrue(form.is_valid()) + + def test_delete(self): + """Delete schedule (Days) for supplied section""" + Day.objects.create(schedule=self.schedule, date=self.today) + Day.objects.create(schedule=self.schedule, date=self.tomorrow) + other_section = Section.objects.create(conference=self.conference, name='other') + other_schedule = Schedule.objects.create(section=other_section) + other_day = Day.objects.create(schedule=other_schedule, date=self.tomorrow) + self.assertEqual(3, Day.objects.all().count()) + data = {'delete': 'Delete'} + form = ScheduleSectionForm(data=data, schedule=self.schedule) + form.delete_schedule() + days = Day.objects.all() + self.assertEqual(1, days.count()) + self.assertIn(other_day, days) + + def test_build_days(self): + """Test private method to build days based off ingested CSV""" + form = ScheduleSectionForm(schedule=self.schedule) + data = ( + {'date': datetime.strftime(self.today, "%m/%d/%Y")}, + {'date': datetime.strftime(self.today, "%m/%d/%Y")}, + {'date': datetime.strftime(self.tomorrow, "%m/%d/%Y")}, + ) + self.assertEqual(0, Day.objects.all().count()) + form._build_days(data) + self.assertEqual(2, Day.objects.all().count()) + + def test_build_days_malformed(self): + """Test failure for malformed date in CSV""" + form = ScheduleSectionForm(schedule=self.schedule) + data = ( + {'date': datetime.strftime(self.today, "%m/%d/%Y")}, + {'date': '12-12-12'} + ) + self.assertEqual(0, Day.objects.all().count()) + msg_type, msg = form._build_days(data) + self.assertEqual(0, Day.objects.all().count()) + self.assertEqual(40, msg_type) + self.assertIn('12-12-12', msg) + + def test_build_rooms(self): + """Test private method to build rooms based off ingested CSV""" + form = ScheduleSectionForm(schedule=self.schedule) + data = ( + {'room': 'foo'}, + {'room': 'bar'}, + {'room': 'foo'}, + ) + self.assertEqual(0, Room.objects.all().count()) + form._build_rooms(data) + self.assertEqual(2, Room.objects.all().count()) + + def test_get_start_end_times(self): + """ + Test private method to convert start and end times based off + ingested CSV + """ + form = ScheduleSectionForm(schedule=self.schedule) + start = '12:00 PM' + end = '01:00 PM' + data = {'time_start': start, 'time_end': end} + start_time, end_time = form._get_start_end_times(data) + self.assertEqual(start, start_time.strftime('%I:%M %p')) + self.assertEqual(end, end_time.strftime('%I:%M %p')) + + def test_get_start_end_times_malformed(self): + """ + Test private method for malformed time based off ingested CSV + """ + form = ScheduleSectionForm(schedule=self.schedule) + start = '12:00' + end = '01:00' + data = {'time_start': start, 'time_end': end} + msg_type, msg = form._get_start_end_times(data) + self.assertEqual(40, msg_type) + self.assertIn('Malformed', msg) + + def test_build_schedule(self): + """ + Test successful schedule build based off ingested CSV + """ + self.assertEqual(0, Day.objects.all().count()) + self.assertEqual(0, Room.objects.all().count()) + self.assertEqual(0, Slot.objects.all().count()) + self.assertEqual(0, SlotKind.objects.all().count()) + schedule_csv = open(os.path.join(DATA_DIR, 'schedule.csv'), 'rb') + file_data = {'filename': SimpleUploadedFile(schedule_csv.name, schedule_csv.read())} + data = {'submit': 'Submit'} + form = ScheduleSectionForm(data, file_data, schedule=self.schedule) + form.is_valid() + msg_type, msg = form.build_schedule() + self.assertEqual(25, msg_type) + self.assertIn('imported', msg) + self.assertEqual(2, Day.objects.all().count()) + self.assertEqual(2, Room.objects.all().count()) + self.assertEqual(8, Slot.objects.all().count()) + self.assertEqual(2, SlotKind.objects.all().count()) + + def test_build_schedule_overlap(self): + """ + Test rolledback schedule build based off ingested CSV with Slot overlap + """ + self.assertEqual(0, Day.objects.all().count()) + self.assertEqual(0, Room.objects.all().count()) + self.assertEqual(0, Slot.objects.all().count()) + self.assertEqual(0, SlotKind.objects.all().count()) + schedule_csv = open(os.path.join(DATA_DIR, 'schedule_overlap.csv'), 'rb') + file_data = {'filename': SimpleUploadedFile(schedule_csv.name, schedule_csv.read())} + data = {'submit': 'Submit'} + form = ScheduleSectionForm(data, file_data, schedule=self.schedule) + form.is_valid() + msg_type, msg = form.build_schedule() + self.assertEqual(40, msg_type) + self.assertIn('overlap', msg) + self.assertEqual(0, Day.objects.all().count()) + self.assertEqual(0, Room.objects.all().count()) + self.assertEqual(0, Slot.objects.all().count()) + self.assertEqual(0, SlotKind.objects.all().count()) From 18077539a972794c50d98c850f4cd44a1cd12b75 Mon Sep 17 00:00:00 2001 From: David Ray Date: Mon, 3 Mar 2014 14:10:28 -0500 Subject: [PATCH 547/751] add schedule docs --- docs/index.rst | 1 + docs/schedule.rst | 49 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 docs/schedule.rst diff --git a/docs/index.rst b/docs/index.rst index d8e9391e..732670ca 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -19,6 +19,7 @@ Apps: sponsorship speakers proposals + schedule Indices and tables diff --git a/docs/schedule.rst b/docs/schedule.rst new file mode 100644 index 00000000..13d942a8 --- /dev/null +++ b/docs/schedule.rst @@ -0,0 +1,49 @@ +Schedule App +=========== + +The ``schedule`` app allows staff members to create the schedule for the +conference's presentations, breaks, lunches, etc. + +The ```schedule``` app has a number of models that facilitate building the +structured schedule: + + * Schedule: A high level container that maps to each Conference Section. + * Day: A Day associated with a Schedule. + * Room: A Room associated with a Schedule. + * Slot Kind: A type of Slot associated with a Schedule. + * Slot: A discreet time period for a Schedule. + * Slot Room: A mapping of a Room and Slot for a given Schedule. + * Presentation: A mapping of a Slot to an approved Proposal from the ```proposals``` app. + +Schedule Builder Form +--------------------- + +It can be cumbersone to generate a schedule through the admin. With that in mind, +a generic schedule builder is available via a Schedule's edit view. For instance, +if a Conference site has a Talks Section and Schedule, the form would be +available for Staff at:: + +/schedule/talks/edit + +The form consume a structured CSV file, from which it will build the schedule. +Sample CSV data is included below:: + +"date","time_start","time_end","kind"," room " +"12/12/2013","10:00 AM","11:00 AM","plenary","Room2" +"12/12/2013","10:00 AM","11:00 AM","plenary","Room1" +"12/12/2013","11:00 AM","12:00 PM","talk","Room1" +"12/12/2013","11:00 AM","12:00 PM","talk","Room2" +"12/12/2013","12:00 PM","12:45 PM","plenary","Room1" +"12/12/2013","12:00 PM","12:45 PM","plenary","Room2" +"12/13/2013","10:00 AM","11:00 AM","plenary","Room2" +"12/13/2013","10:00 AM","11:00 AM","plenary","Room1" +"12/13/2013","11:00 AM","12:00 PM","talk","Room1" +"12/13/2013","11:00 AM","12:00 PM","talk","Room2" +"12/13/2013","12:00 PM","12:45 PM","plenary","Room1" +"12/13/2013","12:00 PM","12:45 PM","plenary","Room2" + +It is worth noting that this generates the **structure** of the schedule. It +does not create Presentation objects. This will need to be done manually. + +One can also **delete** an existing schedule via the delete action. This is +irreversible (save for a database restore). From 17256038c7e8de5d0e5e7e779a0ee3a680280664 Mon Sep 17 00:00:00 2001 From: David Ray Date: Tue, 4 Mar 2014 08:47:19 -0500 Subject: [PATCH 548/751] fix typos --- docs/schedule.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/schedule.rst b/docs/schedule.rst index 13d942a8..15d37fa1 100644 --- a/docs/schedule.rst +++ b/docs/schedule.rst @@ -11,21 +11,21 @@ structured schedule: * Day: A Day associated with a Schedule. * Room: A Room associated with a Schedule. * Slot Kind: A type of Slot associated with a Schedule. - * Slot: A discreet time period for a Schedule. + * Slot: A discrete time period for a Schedule. * Slot Room: A mapping of a Room and Slot for a given Schedule. * Presentation: A mapping of a Slot to an approved Proposal from the ```proposals``` app. Schedule Builder Form --------------------- -It can be cumbersone to generate a schedule through the admin. With that in mind, +It can be cumbersome to generate a schedule through the admin. With that in mind, a generic schedule builder is available via a Schedule's edit view. For instance, if a Conference site has a Talks Section and Schedule, the form would be available for Staff at:: /schedule/talks/edit -The form consume a structured CSV file, from which it will build the schedule. +The form consumes a structured CSV file, from which it will build the schedule. Sample CSV data is included below:: "date","time_start","time_end","kind"," room " From bf4b682e8af7ef2b6fc6c292c257e7c13e3e55e5 Mon Sep 17 00:00:00 2001 From: Martin Brochhaus Date: Sun, 18 May 2014 15:50:51 +0800 Subject: [PATCH 549/751] Bugfix: Cannot unassign presentation on schedule/edit view --- symposion/schedule/models.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/symposion/schedule/models.py b/symposion/schedule/models.py index dc070917..e5680d90 100644 --- a/symposion/schedule/models.py +++ b/symposion/schedule/models.py @@ -79,8 +79,9 @@ class Slot(models.Model): Unassign the associated content with this slot. """ if self.content and self.content.slot_id: - self.content.slot = None - self.content.save() + presentation = self.content + presentation.slot = None + presentation.save() @property def content(self): From 5b853f9300827ea163fa04a8ed21b48e2175e8d0 Mon Sep 17 00:00:00 2001 From: David Ray Date: Thu, 19 Jun 2014 23:55:15 -0400 Subject: [PATCH 550/751] Fixes #11 --- symposion/reviews/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/symposion/reviews/views.py b/symposion/reviews/views.py index 8278778a..b1666f76 100644 --- a/symposion/reviews/views.py +++ b/symposion/reviews/views.py @@ -69,7 +69,7 @@ def review_section(request, section_slug, assigned=False, reviewed="all"): return access_not_permitted(request) section = get_object_or_404(ProposalSection, section__slug=section_slug) - queryset = ProposalBase.objects.filter(kind__section=section) + queryset = ProposalBase.objects.filter(kind__section=section.section) if assigned: assignments = ReviewAssignment.objects.filter(user=request.user).values_list("proposal__id") From 7596729ec10fed7570f021bf7c5051a2ea2556cf Mon Sep 17 00:00:00 2001 From: Volodymyr Hotsyk Date: Sat, 5 Jul 2014 16:11:43 +0300 Subject: [PATCH 551/751] Update to Django=1.6 --- symposion/boxes/urls.py | 7 ++--- symposion/cms/urls.py | 5 ++-- symposion/conference/urls.py | 5 ++-- symposion/proposals/urls.py | 28 ++++++++++++-------- symposion/reviews/urls.py | 48 ++++++++++++++++++++++------------- symposion/schedule/urls.py | 14 ++++++---- symposion/speakers/urls.py | 11 +++++--- symposion/sponsorship/urls.py | 10 +++++--- symposion/teams/urls.py | 7 ++--- symposion/utils/mail.py | 10 ++++---- symposion/views.py | 10 +++++--- 11 files changed, 96 insertions(+), 59 deletions(-) diff --git a/symposion/boxes/urls.py b/symposion/boxes/urls.py index dc57fe6b..c7076c91 100644 --- a/symposion/boxes/urls.py +++ b/symposion/boxes/urls.py @@ -1,6 +1,7 @@ -from django.conf.urls.defaults import url, patterns +from django.conf.urls import url, patterns -urlpatterns = patterns("symposion.boxes.views", +urlpatterns = patterns( + "symposion.boxes.views", url(r"^([-\w]+)/edit/$", "box_edit", name="box_edit"), -) \ No newline at end of file +) diff --git a/symposion/cms/urls.py b/symposion/cms/urls.py index 3297ce53..b9893dd7 100644 --- a/symposion/cms/urls.py +++ b/symposion/cms/urls.py @@ -1,8 +1,9 @@ -from django.conf.urls.defaults import url, patterns +from django.conf.urls import url, patterns PAGE_RE = r"(([\w-]{1,})(/[\w-]{1,})*)/" -urlpatterns = patterns("symposion.cms.views", +urlpatterns = patterns( + "symposion.cms.views", url(r"^files/$", "file_index", name="file_index"), url(r"^files/create/$", "file_create", name="file_create"), url(r"^files/(\d+)/([^/]+)$", "file_download", name="file_download"), diff --git a/symposion/conference/urls.py b/symposion/conference/urls.py index 6bd44c16..43a42a5b 100644 --- a/symposion/conference/urls.py +++ b/symposion/conference/urls.py @@ -1,6 +1,7 @@ -from django.conf.urls.defaults import * +from django.conf.urls import patterns, url -urlpatterns = patterns("symposion.conference.views", +urlpatterns = patterns( + "symposion.conference.views", url(r"^users/$", "user_list", name="user_list"), ) diff --git a/symposion/proposals/urls.py b/symposion/proposals/urls.py index 85e2bb1c..994fad30 100644 --- a/symposion/proposals/urls.py +++ b/symposion/proposals/urls.py @@ -1,18 +1,26 @@ -from django.conf.urls.defaults import * +from django.conf.urls import patterns, url -urlpatterns = patterns("symposion.proposals.views", +urlpatterns = patterns( + "symposion.proposals.views", url(r"^submit/$", "proposal_submit", name="proposal_submit"), - url(r"^submit/([\w\-]+)/$", "proposal_submit_kind", name="proposal_submit_kind"), + url(r"^submit/([\w\-]+)/$", "proposal_submit_kind", + name="proposal_submit_kind"), url(r"^(\d+)/$", "proposal_detail", name="proposal_detail"), url(r"^(\d+)/edit/$", "proposal_edit", name="proposal_edit"), - url(r"^(\d+)/speakers/$", "proposal_speaker_manage", name="proposal_speaker_manage"), + url(r"^(\d+)/speakers/$", "proposal_speaker_manage", + name="proposal_speaker_manage"), url(r"^(\d+)/cancel/$", "proposal_cancel", name="proposal_cancel"), url(r"^(\d+)/leave/$", "proposal_leave", name="proposal_leave"), - url(r"^(\d+)/join/$", "proposal_pending_join", name="proposal_pending_join"), - url(r"^(\d+)/decline/$", "proposal_pending_decline", name="proposal_pending_decline"), - - url(r"^(\d+)/document/create/$", "document_create", name="proposal_document_create"), - url(r"^document/(\d+)/delete/$", "document_delete", name="proposal_document_delete"), - url(r"^document/(\d+)/([^/]+)$", "document_download", name="proposal_document_download"), + url(r"^(\d+)/join/$", "proposal_pending_join", + name="proposal_pending_join"), + url(r"^(\d+)/decline/$", "proposal_pending_decline", + name="proposal_pending_decline"), + + url(r"^(\d+)/document/create/$", "document_create", + name="proposal_document_create"), + url(r"^document/(\d+)/delete/$", "document_delete", + name="proposal_document_delete"), + url(r"^document/(\d+)/([^/]+)$", "document_download", + name="proposal_document_download"), ) diff --git a/symposion/reviews/urls.py b/symposion/reviews/urls.py index 564bd35e..09264557 100644 --- a/symposion/reviews/urls.py +++ b/symposion/reviews/urls.py @@ -1,23 +1,37 @@ -from django.conf.urls.defaults import patterns, url +from django.conf.urls import patterns, url -urlpatterns = patterns("symposion.reviews.views", - url(r"^section/(?P[\w\-]+)/all/$", "review_section", {"reviewed": "all"}, name="review_section"), - url(r"^section/(?P[\w\-]+)/reviewed/$", "review_section", {"reviewed": "reviewed"}, name="user_reviewed"), - url(r"^section/(?P[\w\-]+)/not_reviewed/$", "review_section", {"reviewed": "not_reviewed"}, name="user_not_reviewed"), - url(r"^section/(?P[\w\-]+)/assignments/$", "review_section", {"assigned": True}, name="review_section_assignments"), - url(r"^section/(?P[\w\-]+)/status/$", "review_status", name="review_status"), - url(r"^section/(?P[\w\-]+)/status/(?P\w+)/$", "review_status", name="review_status"), - url(r"^section/(?P[\w\-]+)/list/(?P\d+)/$", "review_list", name="review_list_user"), - url(r"^section/(?P[\w\-]+)/admin/$", "review_admin", name="review_admin"), - url(r"^section/(?P[\w\-]+)/admin/accept/$", "review_bulk_accept", name="review_bulk_accept"), - url(r"^section/(?P[\w\-]+)/notification/(?P\w+)/$", "result_notification", name="result_notification"), - url(r"^section/(?P[\w\-]+)/notification/(?P\w+)/prepare/$", "result_notification_prepare", name="result_notification_prepare"), - url(r"^section/(?P[\w\-]+)/notification/(?P\w+)/send/$", "result_notification_send", name="result_notification_send"), - +urlpatterns = patterns( + "symposion.reviews.views", + url(r"^section/(?P[\w\-]+)/all/$", "review_section", + {"reviewed": "all"}, name="review_section"), + url(r"^section/(?P[\w\-]+)/reviewed/$", "review_section", + {"reviewed": "reviewed"}, name="user_reviewed"), + url(r"^section/(?P[\w\-]+)/not_reviewed/$", "review_section", + {"reviewed": "not_reviewed"}, name="user_not_reviewed"), + url(r"^section/(?P[\w\-]+)/assignments/$", "review_section", + {"assigned": True}, name="review_section_assignments"), + url(r"^section/(?P[\w\-]+)/status/$", "review_status", + name="review_status"), + url(r"^section/(?P[\w\-]+)/status/(?P\w+)/$", + "review_status", name="review_status"), + url(r"^section/(?P[\w\-]+)/list/(?P\d+)/$", + "review_list", name="review_list_user"), + url(r"^section/(?P[\w\-]+)/admin/$", "review_admin", + name="review_admin"), + url(r"^section/(?P[\w\-]+)/admin/accept/$", + "review_bulk_accept", name="review_bulk_accept"), + url(r"^section/(?P[\w\-]+)/notification/(?P\w+)/$", + "result_notification", name="result_notification"), + url(r"^section/(?P[\w\-]+)/notification/(?P\w+)/prepare/$", + "result_notification_prepare", name="result_notification_prepare"), + url(r"^section/(?P[\w\-]+)/notification/(?P\w+)/send/$", + "result_notification_send", name="result_notification_send"), + url(r"^review/(?P\d+)/$", "review_detail", name="review_detail"), - + url(r"^(?P\d+)/delete/$", "review_delete", name="review_delete"), url(r"^assignments/$", "review_assignments", name="review_assignments"), - url(r"^assignment/(?P\d+)/opt-out/$", "review_assignment_opt_out", name="review_assignment_opt_out"), + url(r"^assignment/(?P\d+)/opt-out/$", "review_assignment_opt_out", + name="review_assignment_opt_out"), ) diff --git a/symposion/schedule/urls.py b/symposion/schedule/urls.py index 58890827..b227de61 100644 --- a/symposion/schedule/urls.py +++ b/symposion/schedule/urls.py @@ -1,15 +1,19 @@ -from django.conf.urls.defaults import url, patterns +from django.conf.urls import url, patterns -urlpatterns = patterns("symposion.schedule.views", +urlpatterns = patterns( + "symposion.schedule.views", url(r"^$", "schedule_conference", name="schedule_conference"), url(r"^edit/$", "schedule_edit", name="schedule_edit"), url(r"^list/$", "schedule_list", name="schedule_list"), url(r"^presentations.csv$", "schedule_list_csv", name="schedule_list_csv"), - url(r"^presentation/(\d+)/$", "schedule_presentation_detail", name="schedule_presentation_detail"), + url(r"^presentation/(\d+)/$", "schedule_presentation_detail", + name="schedule_presentation_detail"), url(r"^([\w\-]+)/$", "schedule_detail", name="schedule_detail"), url(r"^([\w\-]+)/edit/$", "schedule_edit", name="schedule_edit"), url(r"^([\w\-]+)/list/$", "schedule_list", name="schedule_list"), - url(r"^([\w\-]+)/presentations.csv$", "schedule_list_csv", name="schedule_list_csv"), - url(r"^([\w\-]+)/edit/slot/(\d+)/", "schedule_slot_edit", name="schedule_slot_edit"), + url(r"^([\w\-]+)/presentations.csv$", "schedule_list_csv", + name="schedule_list_csv"), + url(r"^([\w\-]+)/edit/slot/(\d+)/", "schedule_slot_edit", + name="schedule_slot_edit"), ) diff --git a/symposion/speakers/urls.py b/symposion/speakers/urls.py index fa7055cf..fadead36 100644 --- a/symposion/speakers/urls.py +++ b/symposion/speakers/urls.py @@ -1,10 +1,13 @@ -from django.conf.urls.defaults import * +from django.conf.urls import patterns, url -urlpatterns = patterns("symposion.speakers.views", +urlpatterns = patterns( + "symposion.speakers.views", url(r"^create/$", "speaker_create", name="speaker_create"), - url(r"^create/(\w+)/$", "speaker_create_token", name="speaker_create_token"), + url(r"^create/(\w+)/$", "speaker_create_token", + name="speaker_create_token"), url(r"^edit/(?:(?P\d+)/)?$", "speaker_edit", name="speaker_edit"), url(r"^profile/(?P\d+)/$", "speaker_profile", name="speaker_profile"), - url(r"^staff/create/(\d+)/$", "speaker_create_staff", name="speaker_create_staff"), + url(r"^staff/create/(\d+)/$", "speaker_create_staff", + name="speaker_create_staff"), ) diff --git a/symposion/sponsorship/urls.py b/symposion/sponsorship/urls.py index e5d32bb0..8ae8c65d 100644 --- a/symposion/sponsorship/urls.py +++ b/symposion/sponsorship/urls.py @@ -1,9 +1,11 @@ -from django.conf.urls.defaults import patterns, url -from django.views.generic.simple import direct_to_template +from django.conf.urls import patterns, url +from django.views.generic import TemplateView -urlpatterns = patterns("symposion.sponsorship.views", - url(r"^$", direct_to_template, {"template": "sponsorship/list.html"}, name="sponsor_list"), +urlpatterns = patterns( + "symposion.sponsorship.views", + 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"^(?P\d+)/$", "sponsor_detail", name="sponsor_detail"), diff --git a/symposion/teams/urls.py b/symposion/teams/urls.py index 01145f41..f6ddaf9d 100644 --- a/symposion/teams/urls.py +++ b/symposion/teams/urls.py @@ -1,13 +1,14 @@ -from django.conf.urls.defaults import * +from django.conf.urls import patterns, url -urlpatterns = patterns("symposion.teams.views", +urlpatterns = patterns( + "symposion.teams.views", # team specific url(r"^(?P[\w\-]+)/$", "team_detail", name="team_detail"), url(r"^(?P[\w\-]+)/join/$", "team_join", name="team_join"), url(r"^(?P[\w\-]+)/leave/$", "team_leave", name="team_leave"), url(r"^(?P[\w\-]+)/apply/$", "team_apply", name="team_apply"), - + # membership specific url(r"^promote/(?P\d+)/$", "team_promote", name="team_promote"), url(r"^demote/(?P\d+)/$", "team_demote", name="team_demote"), diff --git a/symposion/utils/mail.py b/symposion/utils/mail.py index dc856871..59cdf09c 100644 --- a/symposion/utils/mail.py +++ b/symposion/utils/mail.py @@ -7,9 +7,9 @@ from django.contrib.sites.models import Site def send_email(to, kind, **kwargs): - + current_site = Site.objects.get_current() - + ctx = { "current_site": current_site, "STATIC_URL": settings.STATIC_URL, @@ -19,12 +19,12 @@ def send_email(to, kind, **kwargs): current_site.name, render_to_string("emails/%s/subject.txt" % kind, ctx).strip() ) - + message_html = render_to_string("emails/%s/message.html" % kind, ctx) message_plaintext = strip_tags(message_html) - + from_email = settings.DEFAULT_FROM_EMAIL - + email = EmailMultiAlternatives(subject, message_plaintext, from_email, to) email.attach_alternative(message_html, "text/html") email.send() diff --git a/symposion/views.py b/symposion/views.py index 669e2847..68e40559 100644 --- a/symposion/views.py +++ b/symposion/views.py @@ -12,18 +12,19 @@ import symposion.forms class SignupView(account.views.SignupView): - + form_class = symposion.forms.SignupForm form_kwargs = { "prefix": "signup", } - + def create_user(self, form, commit=True): user_kwargs = { "first_name": form.cleaned_data["first_name"], "last_name": form.cleaned_data["last_name"] } - return super(SignupView, self).create_user(form, commit=commit, **user_kwargs) + return super(SignupView, self).create_user(form, commit=commit, + **user_kwargs) def generate_username(self, form): def random_username(): @@ -51,5 +52,6 @@ class LoginView(account.views.LoginView): @login_required def dashboard(request): if request.session.get("pending-token"): - return redirect("speaker_create_token", request.session["pending-token"]) + return redirect("speaker_create_token", + request.session["pending-token"]) return render(request, "dashboard.html") From 3ad6d4cfef2261c01cff757cbd63603522e65bbd Mon Sep 17 00:00:00 2001 From: Volodymyr Hotsyk Date: Sat, 5 Jul 2014 16:21:49 +0300 Subject: [PATCH 552/751] Update sha_constructor to hashlib.sha256 --- symposion/proposals/views.py | 70 +++++++++++++++++++----------------- 1 file changed, 37 insertions(+), 33 deletions(-) diff --git a/symposion/proposals/views.py b/symposion/proposals/views.py index cf532c2b..53ddc280 100644 --- a/symposion/proposals/views.py +++ b/symposion/proposals/views.py @@ -6,7 +6,7 @@ from django.core.exceptions import ObjectDoesNotExist from django.db.models import Q from django.http import Http404, HttpResponse, HttpResponseForbidden from django.shortcuts import render, redirect, get_object_or_404 -from django.utils.hashcompat import sha_constructor +from hashlib import sha256 from django.views import static from django.contrib import messages @@ -14,12 +14,16 @@ from django.contrib.auth.models import User from django.contrib.auth.decorators import login_required from account.models import EmailAddress -from symposion.proposals.models import ProposalBase, ProposalSection, ProposalKind +from symposion.proposals.models import ( + ProposalBase, ProposalSection, ProposalKind +) from symposion.proposals.models import SupportingDocument, AdditionalSpeaker from symposion.speakers.models import Speaker from symposion.utils.mail import send_email -from symposion.proposals.forms import AddSpeakerForm, SupportingDocumentCreateForm +from symposion.proposals.forms import ( + AddSpeakerForm, SupportingDocumentCreateForm +) def get_form(name): @@ -37,21 +41,21 @@ def proposal_submit(request): request.user.speaker_profile except ObjectDoesNotExist: return redirect("dashboard") - + kinds = [] for proposal_section in ProposalSection.available(): for kind in proposal_section.section.proposal_kinds.all(): kinds.append(kind) - + return render(request, "proposals/proposal_submit.html", { "kinds": kinds, }) def proposal_submit_kind(request, kind_slug): - + kind = get_object_or_404(ProposalKind, slug=kind_slug) - + if not request.user.is_authenticated(): return redirect("home") # @@@ unauth'd speaker info page? else: @@ -59,12 +63,12 @@ def proposal_submit_kind(request, kind_slug): speaker_profile = request.user.speaker_profile except ObjectDoesNotExist: return redirect("dashboard") - + if not kind.section.proposalsection.is_available(): return redirect("proposal_submit") - + form_class = get_form(settings.PROPOSAL_FORMS[kind_slug]) - + if request.method == "POST": form = form_class(request.POST) if form.is_valid(): @@ -79,7 +83,7 @@ def proposal_submit_kind(request, kind_slug): return redirect("dashboard") else: form = form_class() - + return render(request, "proposals/proposal_submit_kind.html", { "kind": kind, "form": form, @@ -91,17 +95,17 @@ def proposal_speaker_manage(request, pk): queryset = ProposalBase.objects.select_related("speaker") proposal = get_object_or_404(queryset, pk=pk) proposal = ProposalBase.objects.get_subclass(pk=proposal.pk) - + if proposal.speaker != request.user.speaker_profile: raise Http404() - + if request.method == "POST": add_speaker_form = AddSpeakerForm(request.POST, proposal=proposal) if add_speaker_form.is_valid(): message_ctx = { "proposal": proposal, } - + def create_speaker_token(email_address): # create token and look for an existing speaker to prevent # duplicate tokens and confusing the pending speaker @@ -110,8 +114,8 @@ def proposal_speaker_manage(request, pk): Q(user=None, invite_email=email_address) ) except Speaker.DoesNotExist: - salt = sha_constructor(str(random.random())).hexdigest()[:5] - token = sha_constructor(salt + email_address).hexdigest() + salt = sha256(str(random.random())).hexdigest()[:5] + token = sha256(salt + email_address).hexdigest() pending = Speaker.objects.create( invite_email=email_address, invite_token=token, @@ -173,14 +177,14 @@ def proposal_edit(request, pk): if request.user != proposal.speaker.user: raise Http404() - + if not proposal.can_edit(): ctx = { "title": "Proposal editing closed", "body": "Proposal editing is closed for this session type." } return render(request, "proposals/proposal_error.html", ctx) - + form_class = get_form(settings.PROPOSAL_FORMS[proposal.kind.slug]) if request.method == "POST": @@ -206,7 +210,7 @@ def proposal_edit(request, pk): return redirect("proposal_detail", proposal.pk) else: form = form_class(instance=proposal) - + return render(request, "proposals/proposal_edit.html", { "proposal": proposal, "form": form, @@ -218,22 +222,22 @@ def proposal_detail(request, pk): queryset = ProposalBase.objects.select_related("speaker", "speaker__user") proposal = get_object_or_404(queryset, pk=pk) proposal = ProposalBase.objects.get_subclass(pk=proposal.pk) - + if request.user not in [p.user for p in proposal.speakers()]: raise Http404() - + if "symposion.reviews" in settings.INSTALLED_APPS: from symposion.reviews.forms import SpeakerCommentForm message_form = SpeakerCommentForm() if request.method == "POST": message_form = SpeakerCommentForm(request.POST) if message_form.is_valid(): - + message = message_form.save(commit=False) message.user = request.user message.proposal = proposal message.save() - + ProposalMessage = SpeakerCommentForm.Meta.model reviewers = User.objects.filter( id__in=ProposalMessage.objects.filter( @@ -242,7 +246,7 @@ def proposal_detail(request, pk): user=request.user ).distinct().values_list("user", flat=True) ) - + for reviewer in reviewers: ctx = { "proposal": proposal, @@ -253,13 +257,13 @@ def proposal_detail(request, pk): [reviewer.email], "proposal_new_message", context=ctx ) - + return redirect(request.path) else: message_form = SpeakerCommentForm() else: message_form = None - + return render(request, "proposals/proposal_detail.html", { "proposal": proposal, "message_form": message_form @@ -271,7 +275,7 @@ def proposal_cancel(request, pk): queryset = ProposalBase.objects.select_related("speaker") proposal = get_object_or_404(queryset, pk=pk) proposal = ProposalBase.objects.get_subclass(pk=proposal.pk) - + if proposal.speaker.user != request.user: return HttpResponseForbidden() @@ -281,7 +285,7 @@ def proposal_cancel(request, pk): # @@@ fire off email to submitter and other speakers messages.success(request, "%s has been cancelled" % proposal.title) return redirect("dashboard") - + return render(request, "proposals/proposal_cancel.html", { "proposal": proposal, }) @@ -339,10 +343,10 @@ def document_create(request, proposal_pk): queryset = ProposalBase.objects.select_related("speaker") proposal = get_object_or_404(queryset, pk=proposal_pk) proposal = ProposalBase.objects.get_subclass(pk=proposal.pk) - + if proposal.cancelled: return HttpResponseForbidden() - + if request.method == "POST": form = SupportingDocumentCreateForm(request.POST, request.FILES) if form.is_valid(): @@ -353,7 +357,7 @@ def document_create(request, proposal_pk): return redirect("proposal_detail", proposal.pk) else: form = SupportingDocumentCreateForm() - + return render(request, "proposals/document_create.html", { "proposal": proposal, "form": form, @@ -378,8 +382,8 @@ def document_download(request, pk, *args): def document_delete(request, pk): document = get_object_or_404(SupportingDocument, pk=pk, uploaded_by=request.user) proposal_pk = document.proposal.pk - + if request.method == "POST": document.delete() - + return redirect("proposal_detail", proposal_pk) From fc4b1c4542674d8a9b908fb8baf71b25c1dd5156 Mon Sep 17 00:00:00 2001 From: Volodymyr Hotsyk Date: Sat, 5 Jul 2014 16:46:01 +0300 Subject: [PATCH 553/751] Updated urls to 1.6 --- symposion/templates/cms/file_create.html | 4 +- symposion/templates/cms/file_index.html | 6 +-- symposion/templates/conference/user_list.html | 6 +-- symposion/templates/dashboard.html | 48 +++++++++---------- .../emails/proposal_new_message/message.html | 4 +- .../emails/proposal_updated/message.html | 2 +- .../emails/speaker_addition/message.html | 2 +- .../emails/speaker_invite/message.html | 2 +- .../proposals/_pending_proposal_row.html | 12 ++--- .../templates/proposals/_proposal_fields.html | 18 +++---- .../templates/proposals/_proposal_row.html | 12 ++--- .../templates/proposals/proposal_cancel.html | 4 +- .../templates/proposals/proposal_detail.html | 30 ++++++------ .../templates/proposals/proposal_edit.html | 8 ++-- .../proposals/proposal_speaker_manage.html | 12 ++--- .../templates/proposals/proposal_submit.html | 6 +-- .../templates/reviews/_review_table.html | 4 +- symposion/templates/reviews/base.html | 8 ++-- .../reviews/result_notification.html | 24 +++++----- .../reviews/result_notification_prepare.html | 16 +++---- symposion/templates/reviews/review_admin.html | 4 +- .../templates/reviews/review_assignment.html | 4 +- .../templates/reviews/review_detail.html | 20 ++++---- .../templates/reviews/review_review.html | 32 ++++++------- symposion/templates/reviews/review_stats.html | 26 +++++----- symposion/templates/schedule/_edit_grid.html | 8 ++-- symposion/templates/schedule/_grid.html | 2 +- symposion/templates/schedule/_slot_edit.html | 4 +- .../schedule/presentation_detail.html | 2 +- .../templates/schedule/schedule_list.html | 2 +- .../templates/speakers/speaker_create.html | 2 +- .../templates/speakers/speaker_edit.html | 2 +- .../templates/speakers/speaker_profile.html | 6 +-- symposion/templates/sponsorship/add.html | 4 +- symposion/templates/sponsorship/apply.html | 4 +- symposion/templates/sponsorship/detail.html | 4 +- symposion/templates/sponsorship/list.html | 2 +- symposion/templates/teams/team_detail.html | 26 +++++----- 38 files changed, 191 insertions(+), 191 deletions(-) diff --git a/symposion/templates/cms/file_create.html b/symposion/templates/cms/file_create.html index 547fc5d3..07f449c9 100644 --- a/symposion/templates/cms/file_create.html +++ b/symposion/templates/cms/file_create.html @@ -8,7 +8,7 @@

    Upload File

    -
    + {% csrf_token %} {{ form|as_bootstrap }}
    @@ -17,4 +17,4 @@
    -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/symposion/templates/cms/file_index.html b/symposion/templates/cms/file_index.html index bfe1337c..e9f367f0 100644 --- a/symposion/templates/cms/file_index.html +++ b/symposion/templates/cms/file_index.html @@ -6,10 +6,10 @@

    Files

    - + {% for file in files %}
    -
    + {% csrf_token %}
    @@ -20,7 +20,7 @@

    No uploaded files.

    {% endfor %}
    - + Add File diff --git a/symposion/templates/conference/user_list.html b/symposion/templates/conference/user_list.html index 22caaede..9886fde1 100644 --- a/symposion/templates/conference/user_list.html +++ b/symposion/templates/conference/user_list.html @@ -60,7 +60,7 @@
    {% trans "Name" %} {% trans "Speaker Profile?" %}
    {{ user.get_full_name }} {% if user.speaker_profile %} - {{ user.speaker_profile }} + {{ user.speaker_profile }} {% else %} - create + create {% endif %}
    {% endif %} - + {% pending_proposals as pending_proposals %} {% if pending_proposals %}

    Proposals you have been invited to join

    @@ -85,29 +85,29 @@ {% endif %}
    - +

    {% trans "Sponsorship" %}

    {% if not user.sponsorships.exists %} - + Apply to be a sponsor {% endif %}
    - +
    {% if not user.sponsorships.exists %} -

    If you or your organization would be interested in sponsorship opportunities, use our online form to apply to be a sponsor. +

    If you or your organization would be interested in sponsorship opportunities, use our online form to apply to be a sponsor. {% else %}

    Your Sponsorship

      {% for sponsorship in user.sponsorships.all %}
    • - {{ sponsorship.name }} + {{ sponsorship.name }} ({{ sponsorship.level }}) {% if not sponsorship.active %} awaiting approval @@ -118,32 +118,32 @@ {% endif %} {% if user.is_staff %}

      - As staff, you can directly add a sponsor if the organization isn't + As staff, you can directly add a sponsor if the organization isn't applying themselves.

      {% endif %}
    - + {% if review_sections %}

    {% trans "Reviews" %}

    - +

    Reviews by Section

    - + - + {% comment %}

    My Assignments

    @@ -163,11 +163,11 @@
    {% endcomment %} - +
    {% endif %} - + {% available_teams as available_teams %} {% if user.memberships.exists or available_teams %}
    @@ -175,7 +175,7 @@

    {% trans "Teams" %}

    - +
    {% if user.memberships.exists %}

    Your Teams

    @@ -183,7 +183,7 @@ {% for membership in user.memberships.all %}
    - {{ membership.team.name }} + {{ membership.team.name }} {% if membership.team.description %}
    {{ membership.team.description }}{% endif %}
    diff --git a/symposion/templates/emails/proposal_new_message/message.html b/symposion/templates/emails/proposal_new_message/message.html index 90bbd8c8..42c55ac2 100644 --- a/symposion/templates/emails/proposal_new_message/message.html +++ b/symposion/templates/emails/proposal_new_message/message.html @@ -6,6 +6,6 @@ {{ message.message|safe }}

    - {% if reviewer %}{% url review_detail proposal.pk as detail_url %}{% else %}{% url proposal_detail proposal.pk as detail_url %}{% endif %} + {% if reviewer %}{% url "review_detail" proposal.pk as detail_url %}{% else %}{% url "proposal_detail" proposal.pk as detail_url %}{% endif %} Respond online at http://{{ current_site }}{{ detail_url }}#proposal-feedback -

    \ No newline at end of file +

    diff --git a/symposion/templates/emails/proposal_updated/message.html b/symposion/templates/emails/proposal_updated/message.html index d5ed74ec..e20ed4f0 100644 --- a/symposion/templates/emails/proposal_updated/message.html +++ b/symposion/templates/emails/proposal_updated/message.html @@ -3,6 +3,6 @@ {% user_display user %} has made changes to {{ proposal.title }} which you have previously reviewed or commented on.

    - {% url review_detail proposal.pk as detail_url %} + {% url "review_detail" proposal.pk as detail_url %} View the latest version of the proposal online at http://{{ current_site }}{{ detail_url }}

    diff --git a/symposion/templates/emails/speaker_addition/message.html b/symposion/templates/emails/speaker_addition/message.html index dff0c7ba..1d01ab7e 100644 --- a/symposion/templates/emails/speaker_addition/message.html +++ b/symposion/templates/emails/speaker_addition/message.html @@ -2,5 +2,5 @@ talk proposal for {{ current_site.name }} entitled "{{ proposal.title }}".

    For more details, visit the {{ current_site.name }} speaker dashboard: - http://{{ current_site }}{% url dashboard %} + http://{{ current_site }}{% url "dashboard" %}

    diff --git a/symposion/templates/emails/speaker_invite/message.html b/symposion/templates/emails/speaker_invite/message.html index 5ab2c2e8..57cea710 100644 --- a/symposion/templates/emails/speaker_invite/message.html +++ b/symposion/templates/emails/speaker_invite/message.html @@ -3,7 +3,7 @@

    Go to

    -

    http://{{ current_site }}{% url speaker_create_token token %}

    +

    http://{{ current_site }}{% url "speaker_create_token" token %}

    to confirm.

    diff --git a/symposion/templates/proposals/_pending_proposal_row.html b/symposion/templates/proposals/_pending_proposal_row.html index fafd0ea0..2dda000b 100644 --- a/symposion/templates/proposals/_pending_proposal_row.html +++ b/symposion/templates/proposals/_pending_proposal_row.html @@ -2,11 +2,11 @@
    - {{ proposal.title }} + {{ proposal.title }} {{ proposal.kind.name }} {% if proposal.cancelled %} {% trans 'Cancelled' %} @@ -22,7 +22,7 @@ {% endif %} {% endif %} {% if not proposal.cancelled %} diff --git a/symposion/templates/proposals/_proposal_fields.html b/symposion/templates/proposals/_proposal_fields.html index a1040625..49f94aa9 100644 --- a/symposion/templates/proposals/_proposal_fields.html +++ b/symposion/templates/proposals/_proposal_fields.html @@ -3,13 +3,13 @@
    {% trans "Submitted by" %}
    {{ proposal.speaker }}
    - +
    {% trans "Track" %}
    {{ proposal.track }} 
    - +
    {% trans "Audience Level" %}
    {{ proposal.get_audience_level_display }} 
    - + {% if proposal.additional_speakers.all %}
    {% trans "Additional Speakers" %}
    @@ -24,19 +24,19 @@ {% endfor %}
    {% endif %} - +
    {% trans "Description" %}
    {{ proposal.description }} 
    - +
    {% trans "Abstract" %}
    {{ proposal.abstract|safe }} 
    - +
    {% trans "Notes" %}
    {{ proposal.additional_notes|safe }} 
    - +
    {% trans "Speaker Bio" %}
    {{ proposal.speaker.biography|safe }} 
    - +
    {% trans "Documents" %}
    {% if proposal.supporting_documents.exists %} @@ -45,7 +45,7 @@
    {{ document.description }} -
    + {% csrf_token %}
    diff --git a/symposion/templates/proposals/_proposal_row.html b/symposion/templates/proposals/_proposal_row.html index 6ed15607..de820cfd 100644 --- a/symposion/templates/proposals/_proposal_row.html +++ b/symposion/templates/proposals/_proposal_row.html @@ -1,10 +1,10 @@
    - {{ proposal.title }} + {{ proposal.title }} {{ proposal.kind.name }} {% if proposal.cancelled %} Cancelled @@ -20,12 +20,12 @@ {% endif %} {% endif %} {% if not proposal.cancelled %} {% if request.user == proposal.speaker.user and proposal.can_edit %} - Edit - Manage Additional Speakers + Edit + Manage Additional Speakers {% endif %} {% endif %}
    {% for document in proposal.supporting_documents.all %} {% endfor %} @@ -36,4 +36,4 @@ {% endfor %} -
    {{ document.description }} -
    + {% csrf_token %}
    @@ -66,12 +66,12 @@ {% trans 'Add Document' %} {% endif %} - + {% if message_form %}
    - +

    {% trans 'Conversation with Reviewers' %}

    - + {% for message in proposal.messages.all %}
    {{ message.message|safe }}
    @@ -79,11 +79,11 @@
    {% endfor %} - +

    {% trans 'Leave a Message' %}

    - +

    {% trans 'You can leave a message for the reviewers here.' %}

    - +
    {% csrf_token %}
    @@ -105,11 +105,11 @@ - + {% endblock %} diff --git a/symposion/templates/reviews/review_review.html b/symposion/templates/reviews/review_review.html index 2168e899..4202d70e 100644 --- a/symposion/templates/reviews/review_review.html +++ b/symposion/templates/reviews/review_review.html @@ -7,31 +7,31 @@ {% block body %}

    Proposal Review

    - +

    {{ proposal.title }}

    - +

    {% if proposal.cancelled %} Cancelled {% endif %}

    - +
    {{ proposal.description }}
    - +

    Type: {{ proposal.get_session_type_display }}

    - +

    Abstract

    {{ proposal.abstract_html|safe }}
    - +

    Audience level: {{ proposal.get_audience_level_display }}

    - +

    Submitting speaker: {{ proposal.speaker }}

    {# @@@ bio? #} - + {% if proposal.additional_speakers.all %}

    Additional speakers:

      @@ -44,18 +44,18 @@ {% endfor %}
    {% endif %} - +

    Additional Notes (private from submitter)

    {{ proposal.additional_notes }}
    - + {% markitup_media %}

    Review

    - - + + {% csrf_token %}
    {{ review_form|as_uni_form }} @@ -64,10 +64,10 @@
    - +

    Comment

    - -
    + + {% csrf_token %}
    {{ comment_form|as_uni_form }} @@ -76,5 +76,5 @@
    - + {% endblock %} diff --git a/symposion/templates/reviews/review_stats.html b/symposion/templates/reviews/review_stats.html index 8f4a09cb..fb613d09 100644 --- a/symposion/templates/reviews/review_stats.html +++ b/symposion/templates/reviews/review_stats.html @@ -2,14 +2,14 @@ {% block body %}

    Voting Status ({{ section_slug }})

    - + {% if key %}
    @@ -39,38 +39,38 @@
    {% else %}

    Reviews are placed into one of five buckets depending on the state of their votes:

    - +
    - Positive + Positive {{ proposals.positive|length }}
    proposals with at least {{ vote_threshold }} vote{{ vote_threshold|pluralize }} and at least one +1 and no −1s
    - Negative + Negative {{ proposals.negative|length }}
    proposals with at least {{ vote_threshold }} vote{{ vote_threshold|pluralize }} and at least one −1 and no +1s
    - Indifferent + Indifferent {{ proposals.indifferent|length }}
    proposals with at least {{ vote_threshold }} vote{{ vote_threshold|pluralize }} and neither a +1 or a −1
    - Controversial + Controversial {{ proposals.controversial|length }}
    proposals with at least {{ vote_threshold }} vote{{ vote_threshold|pluralize }} and both a +1 and −1
    - Too Few Reviews + Too Few Reviews {{ proposals.too_few|length }}
    @@ -79,4 +79,4 @@
    {% endif %} -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/symposion/templates/schedule/_edit_grid.html b/symposion/templates/schedule/_edit_grid.html index ff9afe78..8b4d4146 100644 --- a/symposion/templates/schedule/_edit_grid.html +++ b/symposion/templates/schedule/_edit_grid.html @@ -15,9 +15,9 @@
    {% if slot.kind.label == "talk" or slot.kind.label == "tutorial" %} {% if not slot.content %} - + + + {% else %} - {{ slot.content.title }} + {{ slot.content.title }} {{ slot.content.speaker }} {% endif %} {% else %} @@ -26,7 +26,7 @@ {% else %} {{ slot.kind.label }} {% endif %} - — edit + — edit {% endif %}
    \ No newline at end of file + diff --git a/symposion/templates/schedule/_grid.html b/symposion/templates/schedule/_grid.html index be62d912..5df035b0 100644 --- a/symposion/templates/schedule/_grid.html +++ b/symposion/templates/schedule/_grid.html @@ -17,7 +17,7 @@ {% if not slot.content %} {% else %} - {{ slot.content.title }} + {{ slot.content.title }} {{ slot.content.speakers|join:", " }} diff --git a/symposion/templates/schedule/_slot_edit.html b/symposion/templates/schedule/_slot_edit.html index d8b5ae9e..81d48276 100644 --- a/symposion/templates/schedule/_slot_edit.html +++ b/symposion/templates/schedule/_slot_edit.html @@ -1,5 +1,5 @@ {% load i18n bootstrap_tags %} -
    {% endblock %} diff --git a/symposion/templates/speakers/speaker_edit.html b/symposion/templates/speakers/speaker_edit.html index 27b00136..b5d7ef16 100644 --- a/symposion/templates/speakers/speaker_edit.html +++ b/symposion/templates/speakers/speaker_edit.html @@ -17,7 +17,7 @@ {% endblock %} diff --git a/symposion/templates/speakers/speaker_profile.html b/symposion/templates/speakers/speaker_profile.html index 98dfdcbb..25ad6539 100644 --- a/symposion/templates/speakers/speaker_profile.html +++ b/symposion/templates/speakers/speaker_profile.html @@ -17,14 +17,14 @@
    {% if speaker.user == request.user or request.user.is_staff %} - Edit + Edit {% endif %}

    {{ speaker.name }}

    {{ speaker.biography|safe }}
    - +

    Presentations

    {% for presentation in presentations %} -

    {{ presentation.title }}

    +

    {{ presentation.title }}

    {% if presentation.slot %}

    {{ presentation.slot.day.date|date:"l" }} diff --git a/symposion/templates/sponsorship/add.html b/symposion/templates/sponsorship/add.html index 9c807f9a..4364c76b 100644 --- a/symposion/templates/sponsorship/add.html +++ b/symposion/templates/sponsorship/add.html @@ -9,13 +9,13 @@ {% block body_class %}sponsorships{% endblock %} {% block body %} -

    + {% csrf_token %} {% trans "Add a Sponsor" %} {{ form|as_bootstrap }}
    diff --git a/symposion/templates/sponsorship/apply.html b/symposion/templates/sponsorship/apply.html index 38f25c56..1468d10b 100644 --- a/symposion/templates/sponsorship/apply.html +++ b/symposion/templates/sponsorship/apply.html @@ -18,9 +18,9 @@ {{ form|as_bootstrap }}
    - Cancel + Cancel

    - By submitting this sponsor application you are agreeing to the terms and conditions. + By submitting this sponsor application you are agreeing to the terms and conditions.

    diff --git a/symposion/templates/sponsorship/detail.html b/symposion/templates/sponsorship/detail.html index 6e44ad85..d322f8b0 100644 --- a/symposion/templates/sponsorship/detail.html +++ b/symposion/templates/sponsorship/detail.html @@ -33,8 +33,8 @@ -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/symposion/templates/sponsorship/list.html b/symposion/templates/sponsorship/list.html index da32b975..0847f92b 100644 --- a/symposion/templates/sponsorship/list.html +++ b/symposion/templates/sponsorship/list.html @@ -12,7 +12,7 @@

    {% trans "About Our Sponsors" %}

    - Learn how to become a sponsor + Learn how to become a sponsor {% sponsor_levels as levels %} {% for level in levels %} diff --git a/symposion/templates/teams/team_detail.html b/symposion/templates/teams/team_detail.html index 8ef56a3a..eb70ed40 100644 --- a/symposion/templates/teams/team_detail.html +++ b/symposion/templates/teams/team_detail.html @@ -9,33 +9,33 @@
    {% if can_join %} -
    + {% csrf_token %}
    {% endif %} - + {% if can_leave %} -
    + {% csrf_token %}
    {% endif %} - + {% if can_apply %} -
    + {% csrf_token %}
    {% endif %}
    - +

    {{ team.name }}{% if state %} {{ state }}{% endif %}

    - + {% if team.description %}

    {{ team.description }}

    {% endif %} - + {% if state == "invited" %}

    You have been invited to join this team. Click join to the right to accept.

    {% endif %} - + {% if user.is_staff or state == "manager" %} {% if team.managers %}

    Managers

    @@ -44,7 +44,7 @@ {{ membership.user.email }}{% if user == membership.user %} you{% endif %} -
    {% csrf_token %}
    +
    {% csrf_token %}
    {% endfor %} @@ -57,7 +57,7 @@ {{ membership.user.email }}{% if user == membership.user %} you{% endif %} -
    {% csrf_token %}
    +
    {% csrf_token %}
    {% endfor %} @@ -70,8 +70,8 @@ {{ membership.user.email }} -
    {% csrf_token %}
    -
    {% csrf_token %}
    +
    {% csrf_token %}
    +
    {% csrf_token %}
    {% endfor %} From fb26371362751584eb8352d06a840b914d24e3fc Mon Sep 17 00:00:00 2001 From: Volodymyr Hotsyk Date: Sat, 5 Jul 2014 16:49:01 +0300 Subject: [PATCH 554/751] Updated year in licence --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 3bf04a2e..d352bdf0 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2010-2013, Eldarion, Inc. and contributors +Copyright (c) 2010-2014, Eldarion, Inc. and contributors All rights reserved. Redistribution and use in source and binary forms, with or without modification, From 108407f1fdd8c79e78dcb5fd214ab9b3c069cf9a Mon Sep 17 00:00:00 2001 From: Patrick Altman Date: Thu, 10 Jul 2014 14:18:00 -0600 Subject: [PATCH 555/751] Update year in copyright --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 3bf04a2e..d352bdf0 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2010-2013, Eldarion, Inc. and contributors +Copyright (c) 2010-2014, Eldarion, Inc. and contributors All rights reserved. Redistribution and use in source and binary forms, with or without modification, From 47621e54afc9e4e7dd77ee93f31f4795deb0bffe Mon Sep 17 00:00:00 2001 From: Patrick Altman Date: Sat, 12 Jul 2014 21:31:18 -0400 Subject: [PATCH 556/751] Create .travis.yml --- .travis.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..d55e6ec0 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,11 @@ +language: python + +python: + - 2.7 + +install: + - pip install flake8 + - pip install -e . + +script: + - flake8 --max-line-length=100 --max-complexity=10 --statistics --benchmark symposion From 96596dc5ddb2781cd4370edd9730ba891906020e Mon Sep 17 00:00:00 2001 From: Patrick Altman Date: Wed, 16 Jul 2014 20:33:55 -0400 Subject: [PATCH 557/751] Update and rename README.md to README.rst --- README.md | 14 -------------- README.rst | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 14 deletions(-) delete mode 100644 README.md create mode 100644 README.rst diff --git a/README.md b/README.md deleted file mode 100644 index cad1f6fa..00000000 --- a/README.md +++ /dev/null @@ -1,14 +0,0 @@ -# Symposion - -A conference management solution from Eldarion. - -Built with the generous support of the Python Software Foundation. - -See http://eldarion.com/symposion/ for commercial support, customization and hosting - -## Quickstart - -If you're interested in running symposion locally, we have built a [basic -Django startproject template that includes Symposion][1]. - -[1]: https://github.com/pinax/pinax-project-symposion diff --git a/README.rst b/README.rst new file mode 100644 index 00000000..c0a3abd4 --- /dev/null +++ b/README.rst @@ -0,0 +1,33 @@ +Symposion +--------- + +.. image:: https://img.shields.io/travis/pinax/symposion.svg + :target: https://travis-ci.org/pinax/symposion + +.. image:: https://img.shields.io/coveralls/pinax/symposion.svg + :target: https://coveralls.io/r/pinax/symposion + +.. image:: https://img.shields.io/pypi/dm/symposion.svg + :target: https://pypi.python.org/pypi/symposion/ + +.. image:: https://img.shields.io/pypi/v/symposion.svg + :target: https://pypi.python.org/pypi/symposion/ + +.. image:: https://img.shields.io/badge/license-MIT-blue.svg + :target: https://pypi.python.org/pypi/symposion/ + + + +A conference management solution from Eldarion. + +Built with the generous support of the Python Software Foundation. + +See http://eldarion.com/symposion/ for commercial support, customization and hosting + +Quickstart +========== + +If you're interested in running symposion locally, we have built a [basic +Django startproject template that includes Symposion][1]. + +[1]: https://github.com/pinax/pinax-project-symposion From 36ab6d599ffca01b83d5a93a2b5b7fa6b5bd9d70 Mon Sep 17 00:00:00 2001 From: Carlos Henrique Romano Date: Wed, 30 Jul 2014 15:19:26 -0300 Subject: [PATCH 558/751] Fix flake8 warnings --- symposion/boxes/authorization.py | 4 +- symposion/boxes/forms.py | 2 +- symposion/boxes/models.py | 10 +- symposion/boxes/templatetags/boxes_tags.py | 13 +- symposion/boxes/urls.py | 3 +- symposion/boxes/views.py | 11 +- symposion/cms/admin.py | 1 + symposion/cms/forms.py | 4 +- symposion/cms/managers.py | 3 +- symposion/cms/models.py | 23 +- symposion/cms/urls.py | 1 + symposion/cms/views.py | 28 +-- symposion/conf.py | 4 +- symposion/conference/admin.py | 4 +- symposion/conference/models.py | 22 +- symposion/conference/urls.py | 3 +- symposion/conference/views.py | 4 +- symposion/forms.py | 7 +- symposion/markdown_parser.py | 5 +- symposion/proposals/actions.py | 11 +- symposion/proposals/forms.py | 8 +- symposion/proposals/managers.py | 11 +- symposion/proposals/models.py | 74 +++--- .../proposals/templatetags/proposal_tags.py | 13 +- symposion/proposals/urls.py | 3 +- symposion/proposals/views.py | 71 +++--- symposion/reviews/context_processors.py | 2 - symposion/reviews/forms.py | 12 +- .../management/commands/assign_reviewers.py | 5 - .../management/commands/calculate_results.py | 4 +- .../commands/create_review_permissions.py | 4 +- .../management/commands/promoteproposals.py | 7 +- symposion/reviews/models.py | 96 ++++---- symposion/reviews/templatetags/review_tags.py | 2 +- symposion/reviews/tests.py | 42 ++-- symposion/reviews/urls.py | 5 +- symposion/reviews/utils.py | 8 +- symposion/reviews/views.py | 212 +++++++++--------- symposion/schedule/forms.py | 6 +- symposion/schedule/models.py | 56 ++--- symposion/schedule/timetable.py | 14 +- symposion/schedule/urls.py | 1 + symposion/schedule/views.py | 45 ++-- symposion/speakers/admin.py | 5 +- symposion/speakers/fixture_gen.py | 6 +- symposion/speakers/forms.py | 2 +- .../commands/export_speaker_data.py | 6 +- symposion/speakers/models.py | 23 +- symposion/speakers/urls.py | 1 + symposion/speakers/views.py | 24 +- symposion/sponsorship/admin.py | 11 +- symposion/sponsorship/forms.py | 16 +- .../commands/reset_sponsor_benefits.py | 16 +- symposion/sponsorship/models.py | 85 +++---- .../templatetags/sponsorship_tags.py | 20 +- symposion/sponsorship/urls.py | 1 + symposion/sponsorship/views.py | 25 ++- symposion/teams/admin.py | 3 +- symposion/teams/backends.py | 4 +- symposion/teams/forms.py | 29 +-- symposion/teams/models.py | 21 +- symposion/teams/templatetags/teams_tags.py | 6 +- symposion/teams/urls.py | 3 +- symposion/teams/views.py | 14 +- symposion/utils/mail.py | 10 +- symposion/views.py | 4 +- 66 files changed, 614 insertions(+), 585 deletions(-) diff --git a/symposion/boxes/authorization.py b/symposion/boxes/authorization.py index 011e215d..e898b0cf 100644 --- a/symposion/boxes/authorization.py +++ b/symposion/boxes/authorization.py @@ -13,8 +13,8 @@ def default_can_edit(request, *args, **kwargs): def load_can_edit(): import_path = getattr(settings, "BOXES_CAN_EDIT_CALLABLE", None) - + if import_path is None: return default_can_edit - + return load_path_attr(import_path) diff --git a/symposion/boxes/forms.py b/symposion/boxes/forms.py index b5ad3b32..7f881acc 100644 --- a/symposion/boxes/forms.py +++ b/symposion/boxes/forms.py @@ -4,7 +4,7 @@ from symposion.boxes.models import Box class BoxForm(forms.ModelForm): - + class Meta: model = Box fields = ["content"] diff --git a/symposion/boxes/models.py b/symposion/boxes/models.py index c83cadcb..f12e5c95 100644 --- a/symposion/boxes/models.py +++ b/symposion/boxes/models.py @@ -1,5 +1,3 @@ -import datetime - from django.db import models from django.contrib.auth.models import User @@ -10,16 +8,16 @@ from markitup.fields import MarkupField class Box(models.Model): - + label = models.CharField(max_length=100, db_index=True) content = MarkupField(blank=True) - + created_by = models.ForeignKey(User, related_name="boxes") last_updated_by = models.ForeignKey(User, related_name="updated_boxes") - + def __unicode__(self): return self.label - + class Meta: verbose_name_plural = "boxes" diff --git a/symposion/boxes/templatetags/boxes_tags.py b/symposion/boxes/templatetags/boxes_tags.py index e300fd3b..53765e56 100644 --- a/symposion/boxes/templatetags/boxes_tags.py +++ b/symposion/boxes/templatetags/boxes_tags.py @@ -1,10 +1,5 @@ from django import template -from django.core.exceptions import ImproperlyConfigured from django.core.urlresolvers import reverse -from django.utils.safestring import mark_safe -from django.utils.encoding import smart_str -from django.utils.translation import ugettext_lazy as _ -from django.template.defaulttags import kwarg_re from symposion.boxes.models import Box from symposion.boxes.forms import BoxForm @@ -16,22 +11,22 @@ register = template.Library() @register.inclusion_tag("boxes/box.html", takes_context=True) def box(context, label, show_edit=True, *args, **kwargs): - + request = context["request"] can_edit = load_can_edit()(request, *args, **kwargs) - + try: box = Box.objects.get(label=label) except Box.DoesNotExist: box = None - + if can_edit and show_edit: form = BoxForm(instance=box, prefix=label) form_action = reverse("box_edit", args=[label]) else: form = None form_action = None - + return { "request": request, "label": label, diff --git a/symposion/boxes/urls.py b/symposion/boxes/urls.py index dc57fe6b..c0cf3fb1 100644 --- a/symposion/boxes/urls.py +++ b/symposion/boxes/urls.py @@ -1,6 +1,7 @@ +# flake8: noqa from django.conf.urls.defaults import url, patterns urlpatterns = patterns("symposion.boxes.views", url(r"^([-\w]+)/edit/$", "box_edit", name="box_edit"), -) \ No newline at end of file +) diff --git a/symposion/boxes/views.py b/symposion/boxes/views.py index b314b07e..1e93675d 100644 --- a/symposion/boxes/views.py +++ b/symposion/boxes/views.py @@ -7,7 +7,8 @@ from symposion.boxes.forms import BoxForm from symposion.boxes.models import Box -# @@@ problem with this is that the box_edit.html and box_create.html won't have domain objects in context +# @@@ problem with this is that the box_edit.html and box_create.html won't have domain objects in +# context def get_auth_vars(request): auth_vars = {} if request.method == "POST": @@ -20,17 +21,17 @@ def get_auth_vars(request): @require_POST def box_edit(request, label): - + if not load_can_edit()(request, **get_auth_vars(request)): return HttpResponseForbidden() - + next = request.GET.get("next") - + try: box = Box.objects.get(label=label) except Box.DoesNotExist: box = None - + form = BoxForm(request.POST, instance=box, prefix=label) if form.is_valid(): diff --git a/symposion/cms/admin.py b/symposion/cms/admin.py index c45e4cc1..83b4c176 100644 --- a/symposion/cms/admin.py +++ b/symposion/cms/admin.py @@ -4,6 +4,7 @@ import reversion from .models import Page + class PageAdmin(reversion.VersionAdmin): pass diff --git a/symposion/cms/forms.py b/symposion/cms/forms.py index 149ba34a..5eecca31 100644 --- a/symposion/cms/forms.py +++ b/symposion/cms/forms.py @@ -6,7 +6,7 @@ from .models import Page class PageForm(forms.ModelForm): - + class Meta: model = Page fields = ["title", "body", "path"] @@ -17,5 +17,5 @@ class PageForm(forms.ModelForm): class FileUploadForm(forms.Form): - + file = forms.FileField() diff --git a/symposion/cms/managers.py b/symposion/cms/managers.py index c8381ebd..fcf4a4e1 100644 --- a/symposion/cms/managers.py +++ b/symposion/cms/managers.py @@ -2,8 +2,9 @@ from datetime import datetime from django.db import models + class PublishedPageManager(models.Manager): - + def get_query_set(self): qs = super(PublishedPageManager, self).get_query_set() return qs.filter(publish_date__lte=datetime.now()) diff --git a/symposion/cms/models.py b/symposion/cms/models.py index 4acc174a..25c1caea 100644 --- a/symposion/cms/models.py +++ b/symposion/cms/models.py @@ -18,12 +18,12 @@ from .managers import PublishedPageManager class Page(models.Model): - + STATUS_CHOICES = ( (1, _("Draft")), (2, _("Public")), ) - + title = models.CharField(max_length=100) path = models.CharField(max_length=100, unique=True) body = MarkupField() @@ -32,28 +32,29 @@ class Page(models.Model): created = models.DateTimeField(editable=False, default=datetime.datetime.now) updated = models.DateTimeField(editable=False, default=datetime.datetime.now) tags = TaggableManager(blank=True) - + published = PublishedPageManager() - + def __unicode__(self): return self.title - + @models.permalink def get_absolute_url(self): return ("cms_page", [self.path]) - + @property def is_community(self): return self.path.lower().startswith("community/") - + def save(self, *args, **kwargs): self.updated = datetime.datetime.now() super(Page, self).save(*args, **kwargs) - + def clean_fields(self, exclude=None): super(Page, self).clean_fields(exclude) if not re.match(settings.SYMPOSION_PAGE_REGEX, self.path): - raise ValidationError({"path": [_("Path can only contain letters, numbers and hyphens and end with /")]}) + raise ValidationError( + {"path": [_("Path can only contain letters, numbers and hyphens and end with /")]}) reversion.register(Page) @@ -64,9 +65,9 @@ def generate_filename(instance, filename): class File(models.Model): - + file = models.FileField(upload_to=generate_filename) created = models.DateTimeField(default=datetime.datetime.now) - + def download_url(self): return reverse("file_download", args=[self.pk, os.path.basename(self.file.name).lower()]) diff --git a/symposion/cms/urls.py b/symposion/cms/urls.py index 3297ce53..be16e2cd 100644 --- a/symposion/cms/urls.py +++ b/symposion/cms/urls.py @@ -1,3 +1,4 @@ +# flake8: noqa from django.conf.urls.defaults import url, patterns PAGE_RE = r"(([\w-]{1,})(/[\w-]{1,})*)/" diff --git a/symposion/cms/views.py b/symposion/cms/views.py index 4a4ee610..2f58381a 100644 --- a/symposion/cms/views.py +++ b/symposion/cms/views.py @@ -23,20 +23,20 @@ def can_upload(user): def page(request, path): - + try: page = Page.published.get(path=path) except Page.DoesNotExist: page = None - + editable = can_edit(page, request.user) - + if page is None: if editable: return redirect("cms_page_edit", path=path) else: raise Http404 - + return render(request, "cms/page_detail.html", { "page": page, "editable": editable, @@ -45,15 +45,15 @@ def page(request, path): @login_required def page_edit(request, path): - + try: page = Page.published.get(path=path) except Page.DoesNotExist: page = None - + if not can_edit(page, request.user): raise Http404 - + if request.method == "POST": form = PageForm(request.POST, instance=page) if form.is_valid(): @@ -65,7 +65,7 @@ def page_edit(request, path): print form.errors else: form = PageForm(instance=page, initial={"path": path}) - + return render(request, "cms/page_edit.html", { "path": path, "form": form @@ -75,7 +75,7 @@ def page_edit(request, path): def file_index(request): if not can_upload(request.user): raise Http404 - + ctx = { "files": File.objects.all(), } @@ -85,7 +85,7 @@ def file_index(request): def file_create(request): if not can_upload(request.user): raise Http404 - + if request.method == "POST": form = FileUploadForm(request.POST, request.FILES) if form.is_valid(): @@ -97,7 +97,7 @@ def file_create(request): return redirect("file_index") else: form = FileUploadForm() - + ctx = { "form": form, } @@ -106,7 +106,7 @@ def file_create(request): def file_download(request, pk, *args): file = get_object_or_404(File, pk=pk) - + if getattr(settings, "USE_X_ACCEL_REDIRECT", False): response = HttpResponse() response["X-Accel-Redirect"] = file.file.url @@ -115,14 +115,14 @@ def file_download(request, pk, *args): del response["content-type"] else: response = static.serve(request, file.file.name, document_root=settings.MEDIA_ROOT) - + return response def file_delete(request, pk): if not can_upload(request.user): raise Http404 - + file = get_object_or_404(File, pk=pk) if request.method == "POST": file.delete() diff --git a/symposion/conf.py b/symposion/conf.py index e2fb8072..dd2a6d50 100644 --- a/symposion/conf.py +++ b/symposion/conf.py @@ -1,8 +1,6 @@ -from django.conf import settings - from appconf import AppConf class SymposionAppConf(AppConf): - + VOTE_THRESHOLD = 3 diff --git a/symposion/conference/admin.py b/symposion/conference/admin.py index 7f0b55c2..c8e57441 100644 --- a/symposion/conference/admin.py +++ b/symposion/conference/admin.py @@ -6,6 +6,6 @@ from symposion.conference.models import Conference, Section admin.site.register(Conference, list_display=("title", "start_date", "end_date")) admin.site.register( Section, - prepopulated_fields = {"slug": ("name",)}, - list_display = ("name", "conference", "start_date", "end_date") + prepopulated_fields={"slug": ("name",)}, + list_display=("name", "conference", "start_date", "end_date") ) diff --git a/symposion/conference/models.py b/symposion/conference/models.py index 4bb6b8ce..354403b6 100644 --- a/symposion/conference/models.py +++ b/symposion/conference/models.py @@ -11,24 +11,24 @@ class Conference(models.Model): """ the full conference for a specific year, e.g. US PyCon 2012. """ - + title = models.CharField(_("title"), max_length=100) - + # when the conference runs start_date = models.DateField(_("start date"), null=True, blank=True) end_date = models.DateField(_("end date"), null=True, blank=True) - + # timezone the conference is in timezone = TimeZoneField(_("timezone"), blank=True) - + def __unicode__(self): return self.title - + def save(self, *args, **kwargs): super(Conference, self).save(*args, **kwargs) if self.id in CONFERENCE_CACHE: del CONFERENCE_CACHE[self.id] - + def delete(self): pk = self.pk super(Conference, self).delete() @@ -36,7 +36,7 @@ class Conference(models.Model): del CONFERENCE_CACHE[pk] except KeyError: pass - + class Meta(object): verbose_name = _("conference") verbose_name_plural = _("conferences") @@ -48,19 +48,19 @@ class Section(models.Model): "Talks", "Expo", "Sprints", that may have its own review and scheduling process. """ - + conference = models.ForeignKey(Conference, verbose_name=_("conference")) - + name = models.CharField(_("name"), max_length=100) slug = models.SlugField() # when the section runs start_date = models.DateField(_("start date"), null=True, blank=True) end_date = models.DateField(_("end date"), null=True, blank=True) - + def __unicode__(self): return "%s %s" % (self.conference, self.name) - + class Meta(object): verbose_name = _("section") verbose_name_plural = _("sections") diff --git a/symposion/conference/urls.py b/symposion/conference/urls.py index 6bd44c16..f2cdd52b 100644 --- a/symposion/conference/urls.py +++ b/symposion/conference/urls.py @@ -1,4 +1,5 @@ -from django.conf.urls.defaults import * +# flake8: noqa +from django.conf.urls.defaults import patterns, url urlpatterns = patterns("symposion.conference.views", diff --git a/symposion/conference/views.py b/symposion/conference/views.py index 7edf644a..3be21e31 100644 --- a/symposion/conference/views.py +++ b/symposion/conference/views.py @@ -7,10 +7,10 @@ from django.contrib.auth.models import User @login_required def user_list(request): - + if not request.user.is_staff: raise Http404() - + return render(request, "conference/user_list.html", { "users": User.objects.all(), }) diff --git a/symposion/forms.py b/symposion/forms.py index 0a623606..7799e06a 100644 --- a/symposion/forms.py +++ b/symposion/forms.py @@ -4,7 +4,7 @@ import account.forms class SignupForm(account.forms.SignupForm): - + first_name = forms.CharField() last_name = forms.CharField() email_confirm = forms.EmailField(label="Confirm Email") @@ -20,11 +20,12 @@ class SignupForm(account.forms.SignupForm): "password", "password_confirm" ] - + def clean_email_confirm(self): email = self.cleaned_data.get("email") email_confirm = self.cleaned_data["email_confirm"] if email: if email != email_confirm: - raise forms.ValidationError("Email address must match previously typed email address") + raise forms.ValidationError( + "Email address must match previously typed email address") return email_confirm diff --git a/symposion/markdown_parser.py b/symposion/markdown_parser.py index 07216c2a..178708f6 100644 --- a/symposion/markdown_parser.py +++ b/symposion/markdown_parser.py @@ -1,14 +1,13 @@ -import html5lib from html5lib import html5parser, sanitizer import markdown def parse(text): - + # First run through the Markdown parser text = markdown.markdown(text, extensions=["extra"], safe_mode=False) - + # Sanitize using html5lib bits = [] parser = html5parser.HTMLParser(tokenizer=sanitizer.HTMLSanitizer) diff --git a/symposion/proposals/actions.py b/symposion/proposals/actions.py index e46e2524..3dcfb168 100644 --- a/symposion/proposals/actions.py +++ b/symposion/proposals/actions.py @@ -3,9 +3,8 @@ import csv from django.http import HttpResponse -def export_as_csv_action( - description="Export selected objects as CSV file", - fields=None, exclude=None, header=True): +def export_as_csv_action(description="Export selected objects as CSV file", + fields=None, exclude=None, header=True): """ This function returns an export csv action 'fields' and 'exclude' work like in Django ModelForm @@ -24,12 +23,14 @@ def export_as_csv_action( excludeset = set(exclude) field_names = field_names - excludeset response = HttpResponse(mimetype="text/csv") - response["Content-Disposition"] = "attachment; filename=%s.csv" % unicode(opts).replace(".", "_") + response["Content-Disposition"] = \ + "attachment; filename=%s.csv" % unicode(opts).replace(".", "_") writer = csv.writer(response) if header: writer.writerow(list(field_names)) for obj in queryset: - writer.writerow([unicode(getattr(obj, field)).encode("utf-8", "replace") for field in field_names]) + writer.writerow( + [unicode(getattr(obj, field)).encode("utf-8", "replace") for field in field_names]) return response export_as_csv.short_description = description return export_as_csv diff --git a/symposion/proposals/forms.py b/symposion/proposals/forms.py index 3f3fe115..91c7e004 100644 --- a/symposion/proposals/forms.py +++ b/symposion/proposals/forms.py @@ -9,15 +9,15 @@ from symposion.proposals.models import SupportingDocument class AddSpeakerForm(forms.Form): - + email = forms.EmailField( label="Email address of new speaker (use their email address, not yours)" ) - + def __init__(self, *args, **kwargs): self.proposal = kwargs.pop("proposal") super(AddSpeakerForm, self).__init__(*args, **kwargs) - + def clean_email(self): value = self.cleaned_data["email"] exists = self.proposal.additional_speakers.filter( @@ -32,7 +32,7 @@ class AddSpeakerForm(forms.Form): class SupportingDocumentCreateForm(forms.ModelForm): - + class Meta: model = SupportingDocument fields = [ diff --git a/symposion/proposals/managers.py b/symposion/proposals/managers.py index b908c7b6..e6ca5245 100644 --- a/symposion/proposals/managers.py +++ b/symposion/proposals/managers.py @@ -3,15 +3,14 @@ from django.db.models.query import QuerySet class CachingM2MQuerySet(QuerySet): - + def __init__(self, *args, **kwargs): super(CachingM2MQuerySet, self).__init__(*args, **kwargs) self.cached_m2m_field = kwargs["m2m_field"] - + def iterator(self): parent_iter = super(CachingM2MQuerySet, self).iterator() - m2m_model = getattr(self.model, self.cached_m2m_field).through - + for obj in parent_iter: if obj.id in cached_objects: setattr(obj, "_cached_m2m_%s" % self.cached_m2m_field) @@ -21,7 +20,3 @@ class CachingM2MQuerySet(QuerySet): class ProposalManager(models.Manager): def cache_m2m(self, m2m_field): return CachingM2MQuerySet(self.model, using=self._db, m2m_field=m2m_field) - AdditionalSpeaker = queryset.model.additional_speakers.through - additional_speakers = collections.defaultdict(set) - for additional_speaker in AdditionalSpeaker._default_manager.filter(proposal__in=queryset).select_related("speaker__user"): - additional_speakers[additional_speaker.proposal_id].add(additional_speaker.speaker) \ No newline at end of file diff --git a/symposion/proposals/models.py b/symposion/proposals/models.py index e70af550..c12c5bcc 100644 --- a/symposion/proposals/models.py +++ b/symposion/proposals/models.py @@ -21,20 +21,20 @@ from symposion.conference.models import Section class ProposalSection(models.Model): """ configuration of proposal submissions for a specific Section. - + a section is available for proposals iff: * it is after start (if there is one) and * it is before end (if there is one) and * closed is NULL or False """ - + section = models.OneToOneField(Section) - + start = models.DateTimeField(null=True, blank=True) end = models.DateTimeField(null=True, blank=True) closed = models.NullBooleanField() published = models.NullBooleanField() - + @classmethod def available(cls): now = datetime.datetime.now() @@ -43,7 +43,7 @@ class ProposalSection(models.Model): Q(end__gt=now) | Q(end=None), Q(closed=False) | Q(closed=None), ) - + def is_available(self): if self.closed: return False @@ -53,7 +53,7 @@ class ProposalSection(models.Model): if self.end and self.end < now: return False return True - + def __unicode__(self): return self.section.name @@ -61,68 +61,77 @@ class ProposalSection(models.Model): class ProposalKind(models.Model): """ e.g. talk vs panel vs tutorial vs poster - + Note that if you have different deadlines, reviewers, etc. you'll want to distinguish the section as well as the kind. """ - + section = models.ForeignKey(Section, related_name="proposal_kinds") - + name = models.CharField(_("Name"), max_length=100) slug = models.SlugField() - + def __unicode__(self): return self.name class ProposalBase(models.Model): - + objects = InheritanceManager() - + kind = models.ForeignKey(ProposalKind) - + title = models.CharField(max_length=100) description = models.TextField( _("Brief Description"), max_length=400, # @@@ need to enforce 400 in UI - help_text="If your proposal is accepted this will be made public and printed in the program. Should be one paragraph, maximum 400 characters." + help_text=_("If your proposal is accepted this will be made public and printed in the " + "program. Should be one paragraph, maximum 400 characters.") ) abstract = MarkupField( _("Detailed Abstract"), - help_text=_("Detailed outline. Will be made public if your proposal is accepted. Edit using Markdown.") + help_text=_("Detailed outline. Will be made public if your proposal is accepted. Edit " + "using Markdown.") ) additional_notes = MarkupField( blank=True, - help_text=_("Anything else you'd like the program committee to know when making their selection: your past experience, etc. This is not made public. Edit using Markdown.") + help_text=_("Anything else you'd like the program committee to know when making their " + "selection: your past experience, etc. This is not made public. Edit using " + "Markdown.") ) submitted = models.DateTimeField( default=datetime.datetime.now, editable=False, ) speaker = models.ForeignKey("speakers.Speaker", related_name="proposals") - additional_speakers = models.ManyToManyField("speakers.Speaker", through="AdditionalSpeaker", blank=True) + additional_speakers = models.ManyToManyField("speakers.Speaker", through="AdditionalSpeaker", + blank=True) cancelled = models.BooleanField(default=False) - + def can_edit(self): return True - + @property def section(self): return self.kind.section - + @property def speaker_email(self): return self.speaker.email - + @property def number(self): return str(self.pk).zfill(3) - + def speakers(self): yield self.speaker - for speaker in self.additional_speakers.exclude(additionalspeaker__status=AdditionalSpeaker.SPEAKING_STATUS_DECLINED): + speakers = self.additional_speakers.exclude( + additionalspeaker__status=AdditionalSpeaker.SPEAKING_STATUS_DECLINED) + for speaker in speakers: yield speaker - + def notification_email_context(self): return { "title": self.title, @@ -135,21 +144,21 @@ reversion.register(ProposalBase) class AdditionalSpeaker(models.Model): - + SPEAKING_STATUS_PENDING = 1 SPEAKING_STATUS_ACCEPTED = 2 SPEAKING_STATUS_DECLINED = 3 - + SPEAKING_STATUS = [ (SPEAKING_STATUS_PENDING, _("Pending")), (SPEAKING_STATUS_ACCEPTED, _("Accepted")), (SPEAKING_STATUS_DECLINED, _("Declined")), ] - + speaker = models.ForeignKey("speakers.Speaker") proposalbase = models.ForeignKey(ProposalBase) status = models.IntegerField(choices=SPEAKING_STATUS, default=SPEAKING_STATUS_PENDING) - + class Meta: db_table = "proposals_proposalbase_additional_speakers" unique_together = ("speaker", "proposalbase") @@ -162,14 +171,15 @@ def uuid_filename(instance, filename): class SupportingDocument(models.Model): - + proposal = models.ForeignKey(ProposalBase, related_name="supporting_documents") - + uploaded_by = models.ForeignKey(User) created_at = models.DateTimeField(default=datetime.datetime.now) - + file = models.FileField(upload_to=uuid_filename) description = models.CharField(max_length=140) def download_url(self): - return reverse("proposal_document_download", args=[self.pk, os.path.basename(self.file.name).lower()]) + return reverse("proposal_document_download", + args=[self.pk, os.path.basename(self.file.name).lower()]) diff --git a/symposion/proposals/templatetags/proposal_tags.py b/symposion/proposals/templatetags/proposal_tags.py index 2f728847..f9581d35 100644 --- a/symposion/proposals/templatetags/proposal_tags.py +++ b/symposion/proposals/templatetags/proposal_tags.py @@ -7,7 +7,7 @@ register = template.Library() class AssociatedProposalsNode(template.Node): - + @classmethod def handle_token(cls, parser, token): bits = token.split_contents() @@ -15,10 +15,10 @@ class AssociatedProposalsNode(template.Node): return cls(bits[2]) else: raise template.TemplateSyntaxError("%r takes 'as var'" % bits[0]) - + def __init__(self, context_var): self.context_var = context_var - + def render(self, context): request = context["request"] if request.user.speaker_profile: @@ -32,7 +32,7 @@ class AssociatedProposalsNode(template.Node): class PendingProposalsNode(template.Node): - + @classmethod def handle_token(cls, parser, token): bits = token.split_contents() @@ -40,10 +40,10 @@ class PendingProposalsNode(template.Node): return cls(bits[2]) else: raise template.TemplateSyntaxError("%r takes 'as var'" % bits[0]) - + def __init__(self, context_var): self.context_var = context_var - + def render(self, context): request = context["request"] if request.user.speaker_profile: @@ -70,4 +70,3 @@ def associated_proposals(parser, token): {% associated_proposals as associated_proposals %} """ return AssociatedProposalsNode.handle_token(parser, token) - diff --git a/symposion/proposals/urls.py b/symposion/proposals/urls.py index 85e2bb1c..c2efdcb5 100644 --- a/symposion/proposals/urls.py +++ b/symposion/proposals/urls.py @@ -1,3 +1,4 @@ +# flake8: noqa from django.conf.urls.defaults import * @@ -11,7 +12,7 @@ urlpatterns = patterns("symposion.proposals.views", url(r"^(\d+)/leave/$", "proposal_leave", name="proposal_leave"), url(r"^(\d+)/join/$", "proposal_pending_join", name="proposal_pending_join"), url(r"^(\d+)/decline/$", "proposal_pending_decline", name="proposal_pending_decline"), - + url(r"^(\d+)/document/create/$", "document_create", name="proposal_document_create"), url(r"^document/(\d+)/delete/$", "document_delete", name="proposal_document_delete"), url(r"^document/(\d+)/([^/]+)$", "document_download", name="proposal_document_download"), diff --git a/symposion/proposals/views.py b/symposion/proposals/views.py index cf532c2b..dea71b20 100644 --- a/symposion/proposals/views.py +++ b/symposion/proposals/views.py @@ -37,21 +37,21 @@ def proposal_submit(request): request.user.speaker_profile except ObjectDoesNotExist: return redirect("dashboard") - + kinds = [] for proposal_section in ProposalSection.available(): for kind in proposal_section.section.proposal_kinds.all(): kinds.append(kind) - + return render(request, "proposals/proposal_submit.html", { "kinds": kinds, }) def proposal_submit_kind(request, kind_slug): - + kind = get_object_or_404(ProposalKind, slug=kind_slug) - + if not request.user.is_authenticated(): return redirect("home") # @@@ unauth'd speaker info page? else: @@ -59,12 +59,12 @@ def proposal_submit_kind(request, kind_slug): speaker_profile = request.user.speaker_profile except ObjectDoesNotExist: return redirect("dashboard") - + if not kind.section.proposalsection.is_available(): return redirect("proposal_submit") - + form_class = get_form(settings.PROPOSAL_FORMS[kind_slug]) - + if request.method == "POST": form = form_class(request.POST) if form.is_valid(): @@ -79,7 +79,7 @@ def proposal_submit_kind(request, kind_slug): return redirect("dashboard") else: form = form_class() - + return render(request, "proposals/proposal_submit_kind.html", { "kind": kind, "form": form, @@ -91,17 +91,17 @@ def proposal_speaker_manage(request, pk): queryset = ProposalBase.objects.select_related("speaker") proposal = get_object_or_404(queryset, pk=pk) proposal = ProposalBase.objects.get_subclass(pk=proposal.pk) - + if proposal.speaker != request.user.speaker_profile: raise Http404() - + if request.method == "POST": add_speaker_form = AddSpeakerForm(request.POST, proposal=proposal) if add_speaker_form.is_valid(): message_ctx = { "proposal": proposal, } - + def create_speaker_token(email_address): # create token and look for an existing speaker to prevent # duplicate tokens and confusing the pending speaker @@ -135,13 +135,13 @@ def proposal_speaker_manage(request, pk): # fire off email to user to create profile send_email( [email_address], "speaker_no_profile", - context = message_ctx + context=message_ctx ) else: # fire off email to user letting them they are loved. send_email( [email_address], "speaker_addition", - context = message_ctx + context=message_ctx ) else: speaker, token = create_speaker_token(email_address) @@ -150,9 +150,10 @@ def proposal_speaker_manage(request, pk): # account and speaker profile send_email( [email_address], "speaker_invite", - context = message_ctx + context=message_ctx ) - invitation, created = AdditionalSpeaker.objects.get_or_create(proposalbase=proposal.proposalbase_ptr, speaker=speaker) + invitation, created = AdditionalSpeaker.objects.get_or_create( + proposalbase=proposal.proposalbase_ptr, speaker=speaker) messages.success(request, "Speaker invited to proposal.") return redirect("proposal_speaker_manage", proposal.pk) else: @@ -173,14 +174,14 @@ def proposal_edit(request, pk): if request.user != proposal.speaker.user: raise Http404() - + if not proposal.can_edit(): ctx = { "title": "Proposal editing closed", "body": "Proposal editing is closed for this session type." } return render(request, "proposals/proposal_error.html", ctx) - + form_class = get_form(settings.PROPOSAL_FORMS[proposal.kind.slug]) if request.method == "POST": @@ -206,7 +207,7 @@ def proposal_edit(request, pk): return redirect("proposal_detail", proposal.pk) else: form = form_class(instance=proposal) - + return render(request, "proposals/proposal_edit.html", { "proposal": proposal, "form": form, @@ -218,22 +219,22 @@ def proposal_detail(request, pk): queryset = ProposalBase.objects.select_related("speaker", "speaker__user") proposal = get_object_or_404(queryset, pk=pk) proposal = ProposalBase.objects.get_subclass(pk=proposal.pk) - + if request.user not in [p.user for p in proposal.speakers()]: raise Http404() - + if "symposion.reviews" in settings.INSTALLED_APPS: from symposion.reviews.forms import SpeakerCommentForm message_form = SpeakerCommentForm() if request.method == "POST": message_form = SpeakerCommentForm(request.POST) if message_form.is_valid(): - + message = message_form.save(commit=False) message.user = request.user message.proposal = proposal message.save() - + ProposalMessage = SpeakerCommentForm.Meta.model reviewers = User.objects.filter( id__in=ProposalMessage.objects.filter( @@ -242,7 +243,7 @@ def proposal_detail(request, pk): user=request.user ).distinct().values_list("user", flat=True) ) - + for reviewer in reviewers: ctx = { "proposal": proposal, @@ -253,13 +254,13 @@ def proposal_detail(request, pk): [reviewer.email], "proposal_new_message", context=ctx ) - + return redirect(request.path) else: message_form = SpeakerCommentForm() else: message_form = None - + return render(request, "proposals/proposal_detail.html", { "proposal": proposal, "message_form": message_form @@ -271,7 +272,7 @@ def proposal_cancel(request, pk): queryset = ProposalBase.objects.select_related("speaker") proposal = get_object_or_404(queryset, pk=pk) proposal = ProposalBase.objects.get_subclass(pk=proposal.pk) - + if proposal.speaker.user != request.user: return HttpResponseForbidden() @@ -281,7 +282,7 @@ def proposal_cancel(request, pk): # @@@ fire off email to submitter and other speakers messages.success(request, "%s has been cancelled" % proposal.title) return redirect("dashboard") - + return render(request, "proposals/proposal_cancel.html", { "proposal": proposal, }) @@ -311,7 +312,8 @@ def proposal_leave(request, pk): @login_required def proposal_pending_join(request, pk): proposal = get_object_or_404(ProposalBase, pk=pk) - speaking = get_object_or_404(AdditionalSpeaker, speaker=request.user.speaker_profile, proposalbase=proposal) + speaking = get_object_or_404(AdditionalSpeaker, speaker=request.user.speaker_profile, + proposalbase=proposal) if speaking.status == AdditionalSpeaker.SPEAKING_STATUS_PENDING: speaking.status = AdditionalSpeaker.SPEAKING_STATUS_ACCEPTED speaking.save() @@ -324,7 +326,8 @@ def proposal_pending_join(request, pk): @login_required def proposal_pending_decline(request, pk): proposal = get_object_or_404(ProposalBase, pk=pk) - speaking = get_object_or_404(AdditionalSpeaker, speaker=request.user.speaker_profile, proposalbase=proposal) + speaking = get_object_or_404(AdditionalSpeaker, speaker=request.user.speaker_profile, + proposalbase=proposal) if speaking.status == AdditionalSpeaker.SPEAKING_STATUS_PENDING: speaking.status = AdditionalSpeaker.SPEAKING_STATUS_DECLINED speaking.save() @@ -339,10 +342,10 @@ def document_create(request, proposal_pk): queryset = ProposalBase.objects.select_related("speaker") proposal = get_object_or_404(queryset, pk=proposal_pk) proposal = ProposalBase.objects.get_subclass(pk=proposal.pk) - + if proposal.cancelled: return HttpResponseForbidden() - + if request.method == "POST": form = SupportingDocumentCreateForm(request.POST, request.FILES) if form.is_valid(): @@ -353,7 +356,7 @@ def document_create(request, proposal_pk): return redirect("proposal_detail", proposal.pk) else: form = SupportingDocumentCreateForm() - + return render(request, "proposals/document_create.html", { "proposal": proposal, "form": form, @@ -378,8 +381,8 @@ def document_download(request, pk, *args): def document_delete(request, pk): document = get_object_or_404(SupportingDocument, pk=pk, uploaded_by=request.user) proposal_pk = document.proposal.pk - + if request.method == "POST": document.delete() - + return redirect("proposal_detail", proposal_pk) diff --git a/symposion/reviews/context_processors.py b/symposion/reviews/context_processors.py index 6e70715b..02850f15 100644 --- a/symposion/reviews/context_processors.py +++ b/symposion/reviews/context_processors.py @@ -1,5 +1,3 @@ -from django.contrib.contenttypes.models import ContentType - from symposion.proposals.models import ProposalSection diff --git a/symposion/reviews/forms.py b/symposion/reviews/forms.py index b9eee672..a705333c 100644 --- a/symposion/reviews/forms.py +++ b/symposion/reviews/forms.py @@ -9,13 +9,13 @@ class ReviewForm(forms.ModelForm): class Meta: model = Review fields = ["vote", "comment"] - widgets = { "comment": MarkItUpWidget() } - + widgets = {"comment": MarkItUpWidget()} + def __init__(self, *args, **kwargs): super(ReviewForm, self).__init__(*args, **kwargs) self.fields["vote"] = forms.ChoiceField( - widget = forms.RadioSelect(), - choices = VOTES.CHOICES + widget=forms.RadioSelect(), + choices=VOTES.CHOICES ) @@ -23,14 +23,14 @@ class ReviewCommentForm(forms.ModelForm): class Meta: model = Comment fields = ["text"] - widgets = { "text": MarkItUpWidget() } + widgets = {"text": MarkItUpWidget()} class SpeakerCommentForm(forms.ModelForm): class Meta: model = ProposalMessage fields = ["message"] - widgets = { "message": MarkItUpWidget() } + widgets = {"message": MarkItUpWidget()} class BulkPresentationForm(forms.Form): diff --git a/symposion/reviews/management/commands/assign_reviewers.py b/symposion/reviews/management/commands/assign_reviewers.py index 334eab30..20594f4c 100644 --- a/symposion/reviews/management/commands/assign_reviewers.py +++ b/symposion/reviews/management/commands/assign_reviewers.py @@ -1,8 +1,3 @@ -import csv -import os -import random - -from django.contrib.auth import models from django.core.management.base import BaseCommand from symposion.reviews.models import ReviewAssignment diff --git a/symposion/reviews/management/commands/calculate_results.py b/symposion/reviews/management/commands/calculate_results.py index 45185a22..6a06ce8a 100644 --- a/symposion/reviews/management/commands/calculate_results.py +++ b/symposion/reviews/management/commands/calculate_results.py @@ -1,11 +1,9 @@ from django.core.management.base import BaseCommand -from django.contrib.auth.models import Group - from symposion.reviews.models import ProposalResult class Command(BaseCommand): - + def handle(self, *args, **options): ProposalResult.full_calculate() diff --git a/symposion/reviews/management/commands/create_review_permissions.py b/symposion/reviews/management/commands/create_review_permissions.py index cde2c233..67dfc96b 100644 --- a/symposion/reviews/management/commands/create_review_permissions.py +++ b/symposion/reviews/management/commands/create_review_permissions.py @@ -7,14 +7,14 @@ from symposion.proposals.models import ProposalSection class Command(BaseCommand): - + def handle(self, *args, **options): ct, created = ContentType.objects.get_or_create( model="", app_label="reviews", defaults={"name": "reviews"} ) - + for ps in ProposalSection.objects.all(): for action in ["review", "manage"]: perm, created = Permission.objects.get_or_create( diff --git a/symposion/reviews/management/commands/promoteproposals.py b/symposion/reviews/management/commands/promoteproposals.py index 72c6faab..e62c6903 100644 --- a/symposion/reviews/management/commands/promoteproposals.py +++ b/symposion/reviews/management/commands/promoteproposals.py @@ -5,11 +5,12 @@ from symposion.reviews.models import ProposalResult, promote_proposal class Command(BaseCommand): - + def handle(self, *args, **options): accepted_proposals = ProposalResult.objects.filter(status="accepted") accepted_proposals = accepted_proposals.order_by("proposal") - + for result in accepted_proposals: promote_proposal(result.proposal) - connections["default"].cursor().execute("SELECT setval('schedule_session_id_seq', (SELECT max(id) FROM schedule_session))") + connections["default"].cursor().execute( + "SELECT setval('schedule_session_id_seq', (SELECT max(id) FROM schedule_session))") diff --git a/symposion/reviews/models.py b/symposion/reviews/models.py index 64d6db8d..b601e0c2 100644 --- a/symposion/reviews/models.py +++ b/symposion/reviews/models.py @@ -15,11 +15,11 @@ from symposion.schedule.models import Presentation class ProposalScoreExpression(object): - + def as_sql(self, qn, connection=None): sql = "((3 * plus_one + plus_zero) - (minus_zero + 3 * minus_one))" return sql, [] - + def prepare_database_save(self, unused): return self @@ -29,7 +29,7 @@ class Votes(object): PLUS_ZERO = "+0" MINUS_ZERO = u"−0" MINUS_ONE = u"−1" - + CHOICES = [ (PLUS_ONE, u"+1 — Good proposal and I will argue for it to be accepted."), (PLUS_ZERO, u"+0 — OK proposal, but I will not argue for it to be accepted."), @@ -45,21 +45,21 @@ class ReviewAssignment(models.Model): AUTO_ASSIGNED_LATER = 2 NUM_REVIEWERS = 3 - + ORIGIN_CHOICES = [ (AUTO_ASSIGNED_INITIAL, "auto-assigned, initial"), (OPT_IN, "opted-in"), (AUTO_ASSIGNED_LATER, "auto-assigned, later"), ] - + proposal = models.ForeignKey("proposals.ProposalBase") user = models.ForeignKey(User) - + origin = models.IntegerField(choices=ORIGIN_CHOICES) - + assigned_at = models.DateTimeField(default=datetime.now) opted_out = models.BooleanField() - + @classmethod def create_assignments(cls, proposal, origin=AUTO_ASSIGNED_INITIAL): speakers = [proposal.speaker] + list(proposal.additional_speakers.all()) @@ -94,7 +94,7 @@ class ReviewAssignment(models.Model): class ProposalMessage(models.Model): proposal = models.ForeignKey("proposals.ProposalBase", related_name="messages") user = models.ForeignKey(User) - + message = MarkupField() submitted_at = models.DateTimeField(default=datetime.now, editable=False) @@ -104,24 +104,24 @@ class ProposalMessage(models.Model): class Review(models.Model): VOTES = VOTES - + proposal = models.ForeignKey("proposals.ProposalBase", related_name="reviews") user = models.ForeignKey(User) - + # No way to encode "-0" vs. "+0" into an IntegerField, and I don't feel # like some complicated encoding system. vote = models.CharField(max_length=2, blank=True, choices=VOTES.CHOICES) comment = MarkupField() submitted_at = models.DateTimeField(default=datetime.now, editable=False) - + def save(self, **kwargs): if self.vote: vote, created = LatestVote.objects.get_or_create( - proposal = self.proposal, - user = self.user, - defaults = dict( - vote = self.vote, - submitted_at = self.submitted_at, + proposal=self.proposal, + user=self.user, + defaults=dict( + vote=self.vote, + submitted_at=self.submitted_at, ) ) if not created: @@ -130,7 +130,7 @@ class Review(models.Model): else: self.proposal.result.update_vote(self.vote) super(Review, self).save(**kwargs) - + def delete(self): model = self.__class__ user_reviews = model._default_manager.filter( @@ -152,7 +152,8 @@ class Review(models.Model): if self == latest: # self is the latest review; revert the latest vote to the # previous vote - previous = user_reviews.filter(submitted_at__lt=self.submitted_at).order_by("-submitted_at")[0] + previous = user_reviews.filter(submitted_at__lt=self.submitted_at)\ + .order_by("-submitted_at")[0] self.proposal.result.update_vote(self.vote, previous=previous.vote, removal=True) lv = LatestVote.objects.filter(proposal=self.proposal, user=self.user) lv.update( @@ -166,7 +167,7 @@ class Review(models.Model): self.proposal.result.save() # in all cases we need to delete the review; let's do it! super(Review, self).delete() - + def css_class(self): return { self.VOTES.PLUS_ONE: "plus-one", @@ -174,7 +175,7 @@ class Review(models.Model): self.VOTES.MINUS_ZERO: "minus-zero", self.VOTES.MINUS_ONE: "minus-one", }[self.vote] - + @property def section(self): return self.proposal.kind.section.slug @@ -182,18 +183,18 @@ class Review(models.Model): class LatestVote(models.Model): VOTES = VOTES - + proposal = models.ForeignKey("proposals.ProposalBase", related_name="votes") user = models.ForeignKey(User) - + # No way to encode "-0" vs. "+0" into an IntegerField, and I don't feel # like some complicated encoding system. vote = models.CharField(max_length=2, choices=VOTES.CHOICES) submitted_at = models.DateTimeField(default=datetime.now, editable=False) - + class Meta: unique_together = [("proposal", "user")] - + def css_class(self): return { self.VOTES.PLUS_ONE: "plus-one", @@ -223,7 +224,7 @@ class ProposalResult(models.Model): ("undecided", "undecided"), ("standby", "standby"), ], default="undecided") - + @classmethod def full_calculate(cls): for proposal in ProposalBase.objects.all(): @@ -231,24 +232,24 @@ class ProposalResult(models.Model): result.comment_count = Review.objects.filter(proposal=proposal).count() result.vote_count = LatestVote.objects.filter(proposal=proposal).count() result.plus_one = LatestVote.objects.filter( - proposal = proposal, - vote = VOTES.PLUS_ONE + proposal=proposal, + vote=VOTES.PLUS_ONE ).count() result.plus_zero = LatestVote.objects.filter( - proposal = proposal, - vote = VOTES.PLUS_ZERO + proposal=proposal, + vote=VOTES.PLUS_ZERO ).count() result.minus_zero = LatestVote.objects.filter( - proposal = proposal, - vote = VOTES.MINUS_ZERO + proposal=proposal, + vote=VOTES.MINUS_ZERO ).count() result.minus_one = LatestVote.objects.filter( - proposal = proposal, - vote = VOTES.MINUS_ONE + proposal=proposal, + vote=VOTES.MINUS_ONE ).count() result.save() cls._default_manager.filter(pk=result.pk).update(score=ProposalScoreExpression()) - + def update_vote(self, vote, previous=None, removal=False): mapping = { VOTES.PLUS_ONE: "plus_one", @@ -283,7 +284,7 @@ class Comment(models.Model): proposal = models.ForeignKey("proposals.ProposalBase", related_name="comments") commenter = models.ForeignKey(User) text = MarkupField() - + # Or perhaps more accurately, can the user see this comment. public = models.BooleanField(choices=[ (True, "public"), @@ -293,7 +294,7 @@ class Comment(models.Model): class NotificationTemplate(models.Model): - + label = models.CharField(max_length=100) from_address = models.EmailField() subject = models.CharField(max_length=100) @@ -301,15 +302,16 @@ class NotificationTemplate(models.Model): class ResultNotification(models.Model): - + proposal = models.ForeignKey("proposals.ProposalBase", related_name="notifications") - template = models.ForeignKey(NotificationTemplate, null=True, blank=True, on_delete=models.SET_NULL) + template = models.ForeignKey(NotificationTemplate, null=True, blank=True, + on_delete=models.SET_NULL) timestamp = models.DateTimeField(default=datetime.now) to_address = models.EmailField() from_address = models.EmailField() subject = models.CharField(max_length=100) body = models.TextField() - + @property def email_args(self): return (self.subject, self.body, self.from_address, [self.to_address]) @@ -321,18 +323,18 @@ def promote_proposal(proposal): presentation = proposal.presentation else: presentation = Presentation( - title = proposal.title, - description = proposal.description, - abstract = proposal.abstract, - speaker = proposal.speaker, - section = proposal.section, - proposal_base = proposal, + title=proposal.title, + description=proposal.description, + abstract=proposal.abstract, + speaker=proposal.speaker, + section=proposal.section, + proposal_base=proposal, ) presentation.save() for speaker in proposal.additional_speakers.all(): presentation.additional_speakers.add(speaker) presentation.save() - + return presentation diff --git a/symposion/reviews/templatetags/review_tags.py b/symposion/reviews/templatetags/review_tags.py index cfb6437e..c646ca96 100644 --- a/symposion/reviews/templatetags/review_tags.py +++ b/symposion/reviews/templatetags/review_tags.py @@ -1,6 +1,6 @@ from django import template -from symposion.reviews.models import Review, ReviewAssignment +from symposion.reviews.models import ReviewAssignment register = template.Library() diff --git a/symposion/reviews/tests.py b/symposion/reviews/tests.py index e5fe7caf..07e3cc99 100644 --- a/symposion/reviews/tests.py +++ b/symposion/reviews/tests.py @@ -15,44 +15,44 @@ class login(object): success, "login with username=%r, password=%r failed" % (user, password) ) - + def __enter__(self): pass - + def __exit__(self, *args): self.testcase.client.logout() class ReviewTests(TestCase): fixtures = ["proposals"] - + def get(self, url_name, *args, **kwargs): return self.client.get(reverse(url_name, args=args, kwargs=kwargs)) - + def post(self, url_name, *args, **kwargs): data = kwargs.pop("data") return self.client.post(reverse(url_name, args=args, kwargs=kwargs), data) - + def login(self, user, password): return login(self, user, password) - + def test_detail_perms(self): guidos_proposal = Proposal.objects.all()[0] response = self.get("review_detail", pk=guidos_proposal.pk) - + # Not logged in self.assertEqual(response.status_code, 302) - + with self.login("guido", "pythonisawesome"): response = self.get("review_detail", pk=guidos_proposal.pk) # Guido can see his own proposal. self.assertEqual(response.status_code, 200) - + with self.login("matz", "pythonsucks"): response = self.get("review_detail", pk=guidos_proposal.pk) # Matz can't see guido's proposal self.assertEqual(response.status_code, 302) - + larry = User.objects.get(username="larryw") # Larry is a trustworthy guy, he's a reviewer. larry.groups.add(Group.objects.get(name="reviewers")) @@ -60,10 +60,10 @@ class ReviewTests(TestCase): response = self.get("review_detail", pk=guidos_proposal.pk) # Reviewers can see a review detail page. self.assertEqual(response.status_code, 200) - + def test_reviewing(self): guidos_proposal = Proposal.objects.all()[0] - + with self.login("guido", "pythonisawesome"): response = self.post("review_review", pk=guidos_proposal.pk, data={ "vote": "+1", @@ -72,7 +72,7 @@ class ReviewTests(TestCase): self.assertEqual(response.status_code, 302) # ... no vote recorded self.assertEqual(guidos_proposal.reviews.count(), 0) - + larry = User.objects.get(username="larryw") # Larry is a trustworthy guy, he's a reviewer. larry.groups.add(Group.objects.get(name="reviewers")) @@ -90,7 +90,7 @@ class ReviewTests(TestCase): self.assertEqual(guidos_proposal.comments.count(), 1) comment = guidos_proposal.comments.get() self.assertFalse(comment.public) - + response = self.post("review_review", pk=guidos_proposal.pk, data={ "vote": "+1", "text": "Actually Perl is dead, we really need a talk on the future", @@ -100,21 +100,21 @@ class ReviewTests(TestCase): assignment = ReviewAssignment.objects.get() self.assertEqual(assignment.review, Review.objects.order_by("-id")[0]) self.assertEqual(guidos_proposal.comments.count(), 2) - + # Larry's a big fan... response = self.post("review_review", pk=guidos_proposal.pk, data={ "vote": "+20", }) self.assertEqual(guidos_proposal.reviews.count(), 2) - + def test_speaker_commenting(self): guidos_proposal = Proposal.objects.all()[0] - + with self.login("guido", "pythonisawesome"): response = self.get("review_comment", pk=guidos_proposal.pk) # Guido can comment on his proposal. self.assertEqual(response.status_code, 200) - + response = self.post("review_comment", pk=guidos_proposal.pk, data={ "text": "FYI I can do this as a 30-minute or 45-minute talk.", }) @@ -122,7 +122,7 @@ class ReviewTests(TestCase): self.assertEqual(guidos_proposal.comments.count(), 1) comment = guidos_proposal.comments.get() self.assertTrue(comment.public) - + larry = User.objects.get(username="larryw") # Larry is a trustworthy guy, he's a reviewer. larry.groups.add(Group.objects.get(name="reviewers")) @@ -130,13 +130,13 @@ class ReviewTests(TestCase): response = self.get("review_comment", pk=guidos_proposal.pk) # Larry can comment, since he's a reviewer self.assertEqual(response.status_code, 200) - + response = self.post("review_comment", pk=guidos_proposal.pk, data={ "text": "Thanks for the heads-up Guido." }) self.assertEqual(response.status_code, 302) self.assertEqual(guidos_proposal.comments.count(), 2) - + with self.login("matz", "pythonsucks"): response = self.get("review_comment", pk=guidos_proposal.pk) # Matz can't comment. diff --git a/symposion/reviews/urls.py b/symposion/reviews/urls.py index 564bd35e..3cdc6f73 100644 --- a/symposion/reviews/urls.py +++ b/symposion/reviews/urls.py @@ -1,3 +1,4 @@ +# flake8: noqa from django.conf.urls.defaults import patterns, url @@ -14,9 +15,9 @@ urlpatterns = patterns("symposion.reviews.views", url(r"^section/(?P[\w\-]+)/notification/(?P\w+)/$", "result_notification", name="result_notification"), url(r"^section/(?P[\w\-]+)/notification/(?P\w+)/prepare/$", "result_notification_prepare", name="result_notification_prepare"), url(r"^section/(?P[\w\-]+)/notification/(?P\w+)/send/$", "result_notification_send", name="result_notification_send"), - + url(r"^review/(?P\d+)/$", "review_detail", name="review_detail"), - + url(r"^(?P\d+)/delete/$", "review_delete", name="review_delete"), url(r"^assignments/$", "review_assignments", name="review_assignments"), url(r"^assignment/(?P\d+)/opt-out/$", "review_assignment_opt_out", name="review_assignment_opt_out"), diff --git a/symposion/reviews/utils.py b/symposion/reviews/utils.py index b91e8166..80e87e49 100644 --- a/symposion/reviews/utils.py +++ b/symposion/reviews/utils.py @@ -2,16 +2,16 @@ def has_permission(user, proposal, speaker=False, reviewer=False): """ Returns whether or not ther user has permission to review this proposal, with the specified requirements. - - If ``speaker`` is ``True`` then the user can be one of the speakers for the + + If ``speaker`` is ``True`` then the user can be one of the speakers for the proposal. If ``reviewer`` is ``True`` the speaker can be a part of the reviewer group. """ if user.is_superuser: return True if speaker: - if (user == proposal.speaker.user or - proposal.additional_speakers.filter(user=user).exists()): + if user == proposal.speaker.user or \ + proposal.additional_speakers.filter(user=user).exists(): return True if reviewer: if user.groups.filter(name="reviewers").exists(): diff --git a/symposion/reviews/views.py b/symposion/reviews/views.py index 880e9e8b..2be745e5 100644 --- a/symposion/reviews/views.py +++ b/symposion/reviews/views.py @@ -1,5 +1,3 @@ -import re - from django.core.mail import send_mass_mail from django.db.models import Q from django.http import HttpResponseBadRequest, HttpResponseNotAllowed @@ -27,18 +25,18 @@ def access_not_permitted(request): def proposals_generator(request, queryset, user_pk=None, check_speaker=True): - + for obj in queryset: # @@@ this sucks; we can do better if check_speaker: if request.user in [s.user for s in obj.speakers()]: continue - + try: obj.result except ProposalResult.DoesNotExist: ProposalResult.objects.get_or_create(proposal=obj) - + obj.comment_count = obj.result.comment_count obj.total_votes = obj.result.vote_count obj.plus_one = obj.result.plus_one @@ -46,37 +44,38 @@ def proposals_generator(request, queryset, user_pk=None, check_speaker=True): obj.minus_zero = obj.result.minus_zero obj.minus_one = obj.result.minus_one lookup_params = dict(proposal=obj) - + if user_pk: lookup_params["user__pk"] = user_pk else: lookup_params["user"] = request.user - + try: obj.user_vote = LatestVote.objects.get(**lookup_params).vote obj.user_vote_css = LatestVote.objects.get(**lookup_params).css_class() except LatestVote.DoesNotExist: obj.user_vote = None obj.user_vote_css = "no-vote" - + yield obj -# Returns a list of all proposals, proposals reviewed by the user, or the proposals the user has yet to review -# depending on the link user clicks in dashboard +# Returns a list of all proposals, proposals reviewed by the user, or the proposals the user has +# yet to review depending on the link user clicks in dashboard @login_required def review_section(request, section_slug, assigned=False, reviewed="all"): - + if not request.user.has_perm("reviews.can_review_%s" % section_slug): return access_not_permitted(request) - + section = get_object_or_404(ProposalSection, section__slug=section_slug) queryset = ProposalBase.objects.filter(kind__section=section) - + if assigned: - assignments = ReviewAssignment.objects.filter(user=request.user).values_list("proposal__id") + assignments = ReviewAssignment.objects.filter(user=request.user)\ + .values_list("proposal__id") queryset = queryset.filter(id__in=assignments) - + # passing reviewed in from reviews.urls and out to review_list for # appropriate template header rendering if reviewed == "all": @@ -88,35 +87,36 @@ def review_section(request, section_slug, assigned=False, reviewed="all"): else: queryset = queryset.exclude(reviews__user=request.user).exclude(speaker=request.user) reviewed = "user_not_reviewed" - + proposals = proposals_generator(request, queryset) - + ctx = { "proposals": proposals, "section": section, "reviewed": reviewed, } - + return render(request, "reviews/review_list.html", ctx) + @login_required def review_list(request, section_slug, user_pk): - + # if they're not a reviewer admin and they aren't the person whose # review list is being asked for, don't let them in if not request.user.has_perm("reviews.can_manage_%s" % section_slug): if not request.user.pk == user_pk: return access_not_permitted(request) - + queryset = ProposalBase.objects.select_related("speaker__user", "result") reviewed = LatestVote.objects.filter(user__pk=user_pk).values_list("proposal", flat=True) queryset = queryset.filter(pk__in=reviewed) proposals = queryset.order_by("submitted") - + admin = request.user.has_perm("reviews.can_manage_%s" % section_slug) - + proposals = proposals_generator(request, proposals, user_pk=user_pk, check_speaker=not admin) - + ctx = { "proposals": proposals, } @@ -125,41 +125,41 @@ def review_list(request, section_slug, user_pk): @login_required def review_admin(request, section_slug): - + if not request.user.has_perm("reviews.can_manage_%s" % section_slug): return access_not_permitted(request) - + def reviewers(): already_seen = set() - + for team in Team.objects.filter(permissions__codename="can_review_%s" % section_slug): for membership in team.memberships.filter(Q(state="member") | Q(state="manager")): user = membership.user if user.pk in already_seen: continue already_seen.add(user.pk) - + user.comment_count = Review.objects.filter(user=user).count() user.total_votes = LatestVote.objects.filter(user=user).count() user.plus_one = LatestVote.objects.filter( - user = user, - vote = LatestVote.VOTES.PLUS_ONE + user=user, + vote=LatestVote.VOTES.PLUS_ONE ).count() user.plus_zero = LatestVote.objects.filter( - user = user, - vote = LatestVote.VOTES.PLUS_ZERO + user=user, + vote=LatestVote.VOTES.PLUS_ZERO ).count() user.minus_zero = LatestVote.objects.filter( - user = user, - vote = LatestVote.VOTES.MINUS_ZERO + user=user, + vote=LatestVote.VOTES.MINUS_ZERO ).count() user.minus_one = LatestVote.objects.filter( - user = user, - vote = LatestVote.VOTES.MINUS_ONE + user=user, + vote=LatestVote.VOTES.MINUS_ONE ).count() - + yield user - + ctx = { "section_slug": section_slug, "reviewers": reviewers(), @@ -169,50 +169,50 @@ def review_admin(request, section_slug): @login_required def review_detail(request, pk): - + proposals = ProposalBase.objects.select_related("result").select_subclasses() proposal = get_object_or_404(proposals, pk=pk) - + if not request.user.has_perm("reviews.can_review_%s" % proposal.kind.section.slug): return access_not_permitted(request) - + speakers = [s.user for s in proposal.speakers()] - + if not request.user.is_superuser and request.user in speakers: return access_not_permitted(request) - + admin = request.user.is_staff - + try: latest_vote = LatestVote.objects.get(proposal=proposal, user=request.user) except LatestVote.DoesNotExist: latest_vote = None - + if request.method == "POST": if request.user in speakers: return access_not_permitted(request) - + if "vote_submit" in request.POST: review_form = ReviewForm(request.POST) if review_form.is_valid(): - + review = review_form.save(commit=False) review.user = request.user review.proposal = proposal review.save() - + return redirect(request.path) else: message_form = SpeakerCommentForm() elif "message_submit" in request.POST: message_form = SpeakerCommentForm(request.POST) if message_form.is_valid(): - + message = message_form.save(commit=False) message.user = request.user message.proposal = proposal message.save() - + for speaker in speakers: if speaker and speaker.email: ctx = { @@ -222,9 +222,9 @@ def review_detail(request, pk): } send_email( [speaker.email], "proposal_new_message", - context = ctx + context=ctx ) - + return redirect(request.path) else: initial = {} @@ -237,7 +237,7 @@ def review_detail(request, pk): elif "result_submit" in request.POST: if admin: result = request.POST["result_submit"] - + if result == "accept": proposal.result.status = "accepted" proposal.result.save() @@ -250,7 +250,7 @@ def review_detail(request, pk): elif result == "standby": proposal.result.status = "standby" proposal.result.save() - + return redirect(request.path) else: initial = {} @@ -261,17 +261,17 @@ def review_detail(request, pk): else: review_form = ReviewForm(initial=initial) message_form = SpeakerCommentForm() - + proposal.comment_count = proposal.result.comment_count proposal.total_votes = proposal.result.vote_count proposal.plus_one = proposal.result.plus_one proposal.plus_zero = proposal.result.plus_zero proposal.minus_zero = proposal.result.minus_zero proposal.minus_one = proposal.result.minus_one - + reviews = Review.objects.filter(proposal=proposal).order_by("-submitted_at") messages = proposal.messages.order_by("submitted_at") - + return render(request, "reviews/review_detail.html", { "proposal": proposal, "latest_vote": latest_vote, @@ -287,53 +287,64 @@ def review_detail(request, pk): def review_delete(request, pk): review = get_object_or_404(Review, pk=pk) section_slug = review.section.slug - + if not request.user.has_perm("reviews.can_manage_%s" % section_slug): return access_not_permitted(request) - + review = get_object_or_404(Review, pk=pk) review.delete() - + return redirect("review_detail", pk=review.proposal.pk) @login_required def review_status(request, section_slug=None, key=None): - + if not request.user.has_perm("reviews.can_review_%s" % section_slug): return access_not_permitted(request) - + VOTE_THRESHOLD = settings.SYMPOSION_VOTE_THRESHOLD - + ctx = { "section_slug": section_slug, "vote_threshold": VOTE_THRESHOLD, } - + queryset = ProposalBase.objects.select_related("speaker__user", "result").select_subclasses() if section_slug: queryset = queryset.filter(kind__section__slug=section_slug) - + proposals = { - # proposals with at least VOTE_THRESHOLD reviews and at least one +1 and no -1s, sorted by the 'score' - "positive": queryset.filter(result__vote_count__gte=VOTE_THRESHOLD, result__plus_one__gt=0, result__minus_one=0).order_by("-result__score"), - # proposals with at least VOTE_THRESHOLD reviews and at least one -1 and no +1s, reverse sorted by the 'score' - "negative": queryset.filter(result__vote_count__gte=VOTE_THRESHOLD, result__minus_one__gt=0, result__plus_one=0).order_by("result__score"), - # proposals with at least VOTE_THRESHOLD reviews and neither a +1 or a -1, sorted by total votes (lowest first) - "indifferent": queryset.filter(result__vote_count__gte=VOTE_THRESHOLD, result__minus_one=0, result__plus_one=0).order_by("result__vote_count"), - # proposals with at least VOTE_THRESHOLD reviews and both a +1 and -1, sorted by total votes (highest first) - "controversial": queryset.filter(result__vote_count__gte=VOTE_THRESHOLD, result__plus_one__gt=0, result__minus_one__gt=0).order_by("-result__vote_count"), + # proposals with at least VOTE_THRESHOLD reviews and at least one +1 and no -1s, sorted by + # the 'score' + "positive": queryset.filter(result__vote_count__gte=VOTE_THRESHOLD, result__plus_one__gt=0, + result__minus_one=0).order_by("-result__score"), + # proposals with at least VOTE_THRESHOLD reviews and at least one -1 and no +1s, reverse + # sorted by the 'score' + "negative": queryset.filter(result__vote_count__gte=VOTE_THRESHOLD, result__minus_one__gt=0, + result__plus_one=0).order_by("result__score"), + # proposals with at least VOTE_THRESHOLD reviews and neither a +1 or a -1, sorted by total + # votes (lowest first) + "indifferent": queryset.filter(result__vote_count__gte=VOTE_THRESHOLD, result__minus_one=0, + result__plus_one=0).order_by("result__vote_count"), + # proposals with at least VOTE_THRESHOLD reviews and both a +1 and -1, sorted by total + # votes (highest first) + "controversial": queryset.filter(result__vote_count__gte=VOTE_THRESHOLD, + result__plus_one__gt=0, result__minus_one__gt=0) + .order_by("-result__vote_count"), # proposals with fewer than VOTE_THRESHOLD reviews - "too_few": queryset.filter(result__vote_count__lt=VOTE_THRESHOLD).order_by("result__vote_count"), + "too_few": queryset.filter(result__vote_count__lt=VOTE_THRESHOLD) + .order_by("result__vote_count"), } - + admin = request.user.has_perm("reviews.can_manage_%s" % section_slug) - + for status in proposals: if key and key != status: continue - proposals[status] = list(proposals_generator(request, proposals[status], check_speaker=not admin)) - + proposals[status] = list(proposals_generator(request, proposals[status], + check_speaker=not admin)) + if key: ctx.update({ "key": key, @@ -341,7 +352,7 @@ def review_status(request, section_slug=None, key=None): }) else: ctx["proposals"] = proposals - + return render(request, "reviews/review_stats.html", ctx) @@ -361,14 +372,13 @@ def review_assignments(request): @login_required @require_POST def review_assignment_opt_out(request, pk): - review_assignment = get_object_or_404(ReviewAssignment, - pk=pk, - user=request.user - ) + review_assignment = get_object_or_404( + ReviewAssignment, pk=pk, user=request.user) if not review_assignment.opted_out: review_assignment.opted_out = True review_assignment.save() - ReviewAssignment.create_assignments(review_assignment.proposal, origin=ReviewAssignment.AUTO_ASSIGNED_LATER) + ReviewAssignment.create_assignments( + review_assignment.proposal, origin=ReviewAssignment.AUTO_ASSIGNED_LATER) return redirect("review_assignments") @@ -387,7 +397,7 @@ def review_bulk_accept(request, section_slug): return redirect("review_section", section_slug=section_slug) else: form = BulkPresentationForm() - + return render(request, "reviews/review_bulk_accept.html", { "form": form, }) @@ -397,10 +407,12 @@ def review_bulk_accept(request, section_slug): def result_notification(request, section_slug, status): if not request.user.has_perm("reviews.can_manage_%s" % section_slug): return access_not_permitted(request) - - proposals = ProposalBase.objects.filter(kind__section__slug=section_slug, result__status=status).select_related("speaker__user", "result").select_subclasses() + + proposals = ProposalBase.objects.filter(kind__section__slug=section_slug, + result__status=status)\ + .select_related("speaker__user", "result").select_subclasses() notification_templates = NotificationTemplate.objects.all() - + ctx = { "section_slug": section_slug, "status": status, @@ -414,10 +426,10 @@ def result_notification(request, section_slug, status): def result_notification_prepare(request, section_slug, status): if request.method != "POST": return HttpResponseNotAllowed(["POST"]) - + if not request.user.has_perm("reviews.can_manage_%s" % section_slug): return access_not_permitted(request) - + proposal_pks = [] try: for pk in request.POST.getlist("_selected_action"): @@ -431,13 +443,13 @@ def result_notification_prepare(request, section_slug, status): proposals = proposals.filter(pk__in=proposal_pks) proposals = proposals.select_related("speaker__user", "result") proposals = proposals.select_subclasses() - + notification_template_pk = request.POST.get("notification_template", "") if notification_template_pk: notification_template = NotificationTemplate.objects.get(pk=notification_template_pk) else: notification_template = None - + ctx = { "section_slug": section_slug, "status": status, @@ -452,18 +464,18 @@ def result_notification_prepare(request, section_slug, status): def result_notification_send(request, section_slug, status): if request.method != "POST": return HttpResponseNotAllowed(["POST"]) - + if not request.user.has_perm("reviews.can_manage_%s" % section_slug): return access_not_permitted(request) - + if not all([k in request.POST for k in ["proposal_pks", "from_address", "subject", "body"]]): return HttpResponseBadRequest() - + try: proposal_pks = [int(pk) for pk in request.POST["proposal_pks"].split(",")] except ValueError: return HttpResponseBadRequest() - + proposals = ProposalBase.objects.filter( kind__section__slug=section_slug, result__status=status, @@ -471,15 +483,15 @@ def result_notification_send(request, section_slug, status): proposals = proposals.filter(pk__in=proposal_pks) proposals = proposals.select_related("speaker__user", "result") proposals = proposals.select_subclasses() - + notification_template_pk = request.POST.get("notification_template", "") if notification_template_pk: notification_template = NotificationTemplate.objects.get(pk=notification_template_pk) else: notification_template = None - + emails = [] - + for proposal in proposals: rn = ResultNotification() rn.proposal = proposal @@ -494,7 +506,7 @@ def result_notification_send(request, section_slug, status): ) rn.save() emails.append(rn.email_args) - + send_mass_mail(emails) - + return redirect("result_notification", section_slug=section_slug, status=status) diff --git a/symposion/schedule/forms.py b/symposion/schedule/forms.py index 63316743..5a0f97c1 100644 --- a/symposion/schedule/forms.py +++ b/symposion/schedule/forms.py @@ -7,7 +7,7 @@ from symposion.schedule.models import Presentation class SlotEditForm(forms.Form): - + def __init__(self, *args, **kwargs): self.slot = kwargs.pop("slot") super(SlotEditForm, self).__init__(*args, **kwargs) @@ -16,7 +16,7 @@ class SlotEditForm(forms.Form): self.fields["presentation"] = self.build_presentation_field() else: self.fields["content_override"] = self.build_content_override_field() - + def build_presentation_field(self): kwargs = {} queryset = Presentation.objects.all() @@ -31,7 +31,7 @@ class SlotEditForm(forms.Form): kwargs["required"] = True kwargs["queryset"] = queryset return forms.ModelChoiceField(**kwargs) - + def build_content_override_field(self): kwargs = { "label": "Content", diff --git a/symposion/schedule/models.py b/symposion/schedule/models.py index dc070917..0d7f642e 100644 --- a/symposion/schedule/models.py +++ b/symposion/schedule/models.py @@ -2,44 +2,43 @@ from django.core.exceptions import ObjectDoesNotExist from django.db import models from markitup.fields import MarkupField -from model_utils.managers import InheritanceManager from symposion.proposals.models import ProposalBase from symposion.conference.models import Section class Schedule(models.Model): - + section = models.OneToOneField(Section) published = models.BooleanField(default=True) hidden = models.BooleanField("Hide schedule from overall conference view", default=False) - + def __unicode__(self): return "%s Schedule" % self.section - + class Meta: ordering = ["section"] class Day(models.Model): - + schedule = models.ForeignKey(Schedule) date = models.DateField() - + def __unicode__(self): return "%s" % self.date - + class Meta: unique_together = [("schedule", "date")] ordering = ["date"] class Room(models.Model): - + schedule = models.ForeignKey(Schedule) name = models.CharField(max_length=65) order = models.PositiveIntegerField() - + def __unicode__(self): return self.name @@ -49,22 +48,22 @@ class SlotKind(models.Model): A slot kind represents what kind a slot is. For example, a slot can be a break, lunch, or X-minute talk. """ - + schedule = models.ForeignKey(Schedule) label = models.CharField(max_length=50) - + def __unicode__(self): return self.label class Slot(models.Model): - + day = models.ForeignKey(Day) kind = models.ForeignKey(SlotKind) start = models.TimeField() end = models.TimeField() content_override = MarkupField(blank=True) - + def assign(self, content): """ Assign the given content to this slot and if a previous slot content @@ -73,7 +72,7 @@ class Slot(models.Model): self.unassign() content.slot = self content.save() - + def unassign(self): """ Unassign the associated content with this slot. @@ -81,7 +80,7 @@ class Slot(models.Model): if self.content and self.content.slot_id: self.content.slot = None self.content.save() - + @property def content(self): """ @@ -92,14 +91,14 @@ class Slot(models.Model): return self.content_ptr except ObjectDoesNotExist: return None - + @property def rooms(self): return Room.objects.filter(pk__in=self.slotroom_set.values("room")) - + def __unicode__(self): return "%s %s (%s - %s)" % (self.day, self.kind, self.start, self.end) - + class Meta: ordering = ["day", "start", "end"] @@ -108,48 +107,49 @@ class SlotRoom(models.Model): """ Links a slot with a room. """ - + slot = models.ForeignKey(Slot) room = models.ForeignKey(Room) - + def __unicode__(self): return "%s %s" % (self.room, self.slot) - + class Meta: unique_together = [("slot", "room")] ordering = ["slot", "room__order"] class Presentation(models.Model): - + slot = models.OneToOneField(Slot, null=True, blank=True, related_name="content_ptr") title = models.CharField(max_length=100) description = MarkupField() abstract = MarkupField() speaker = models.ForeignKey("speakers.Speaker", related_name="presentations") - additional_speakers = models.ManyToManyField("speakers.Speaker", related_name="copresentations", blank=True) + additional_speakers = models.ManyToManyField("speakers.Speaker", related_name="copresentations", + blank=True) cancelled = models.BooleanField(default=False) proposal_base = models.OneToOneField(ProposalBase, related_name="presentation") section = models.ForeignKey(Section, related_name="presentations") - + @property def number(self): return self.proposal.number - + @property def proposal(self): if self.proposal_base_id is None: return None return ProposalBase.objects.get_subclass(pk=self.proposal_base_id) - + def speakers(self): yield self.speaker for speaker in self.additional_speakers.all(): if speaker.user: yield speaker - + def __unicode__(self): return "#%s %s (%s)" % (self.number, self.title, self.speaker) - + class Meta: ordering = ["slot"] diff --git a/symposion/schedule/timetable.py b/symposion/schedule/timetable.py index 2aba1de3..1756db6c 100644 --- a/symposion/schedule/timetable.py +++ b/symposion/schedule/timetable.py @@ -1,5 +1,4 @@ import itertools -import operator from django.db.models import Count, Min @@ -7,22 +6,23 @@ from symposion.schedule.models import Room, Slot, SlotRoom class TimeTable(object): - + def __init__(self, day): self.day = day - + def slots_qs(self): qs = Slot.objects.all() qs = qs.filter(day=self.day) return qs - + def rooms(self): qs = Room.objects.all() qs = qs.filter(schedule=self.day.schedule) - qs = qs.filter(pk__in=SlotRoom.objects.filter(slot__in=self.slots_qs().values("pk")).values("room")) + qs = qs.filter( + pk__in=SlotRoom.objects.filter(slot__in=self.slots_qs().values("pk")).values("room")) qs = qs.order_by("order") return qs - + def __iter__(self): times = sorted(set(itertools.chain(*self.slots_qs().values_list("start", "end")))) slots = Slot.objects.filter(pk__in=self.slots_qs().values("pk")) @@ -38,7 +38,7 @@ class TimeTable(object): row["slots"].append(slot) if row["slots"] or next_time is None: yield row - + @staticmethod def rowspan(times, start, end): return times.index(end) - times.index(start) diff --git a/symposion/schedule/urls.py b/symposion/schedule/urls.py index 58890827..59e9b55f 100644 --- a/symposion/schedule/urls.py +++ b/symposion/schedule/urls.py @@ -1,3 +1,4 @@ +# flake8: noqa from django.conf.urls.defaults import url, patterns diff --git a/symposion/schedule/views.py b/symposion/schedule/views.py index 35f9e432..98771b59 100644 --- a/symposion/schedule/views.py +++ b/symposion/schedule/views.py @@ -1,4 +1,3 @@ -from django.core.exceptions import ObjectDoesNotExist from django.http import Http404, HttpResponse from django.shortcuts import render, get_object_or_404, redirect from django.template import loader, Context @@ -12,7 +11,7 @@ from symposion.schedule.timetable import TimeTable def fetch_schedule(slug): qs = Schedule.objects.all() - + if slug is None: if qs.count() > 1: raise Http404() @@ -21,14 +20,14 @@ def fetch_schedule(slug): raise Http404() else: schedule = get_object_or_404(qs, section__slug=slug) - + return schedule def schedule_conference(request): - + schedules = Schedule.objects.filter(published=True, hidden=False) - + sections = [] for schedule in schedules: days_qs = Day.objects.filter(schedule=schedule) @@ -37,7 +36,7 @@ def schedule_conference(request): "schedule": schedule, "days": days, }) - + ctx = { "sections": sections, } @@ -45,14 +44,14 @@ def schedule_conference(request): def schedule_detail(request, slug=None): - + schedule = fetch_schedule(slug) if not schedule.published and not request.user.is_staff: raise Http404() - + days_qs = Day.objects.filter(schedule=schedule) days = [TimeTable(day) for day in days_qs] - + ctx = { "schedule": schedule, "days": days, @@ -62,10 +61,10 @@ def schedule_detail(request, slug=None): def schedule_list(request, slug=None): schedule = fetch_schedule(slug) - + presentations = Presentation.objects.filter(section=schedule.section) presentations = presentations.exclude(cancelled=True) - + ctx = { "schedule": schedule, "presentations": presentations, @@ -75,32 +74,32 @@ def schedule_list(request, slug=None): def schedule_list_csv(request, slug=None): schedule = fetch_schedule(slug) - + presentations = Presentation.objects.filter(section=schedule.section) presentations = presentations.exclude(cancelled=True).order_by("id") - + response = HttpResponse(mimetype="text/csv") if slug: file_slug = slug else: file_slug = "presentations" response["Content-Disposition"] = 'attachment; filename="%s.csv"' % file_slug - + response.write(loader.get_template("schedule/schedule_list.csv").render(Context({ "presentations": presentations, - + }))) return response @login_required def schedule_edit(request, slug=None): - + if not request.user.is_staff: raise Http404() - + schedule = fetch_schedule(slug) - + days_qs = Day.objects.filter(schedule=schedule) days = [TimeTable(day) for day in days_qs] ctx = { @@ -112,12 +111,12 @@ def schedule_edit(request, slug=None): @login_required def schedule_slot_edit(request, slug, slot_pk): - + if not request.user.is_staff: raise Http404() - + slot = get_object_or_404(Slot, day__schedule__section__slug=slug, pk=slot_pk) - + if request.method == "POST": form = SlotEditForm(request.POST, slot=slot) if form.is_valid(): @@ -145,13 +144,13 @@ def schedule_slot_edit(request, slug, slot_pk): def schedule_presentation_detail(request, pk): - + presentation = get_object_or_404(Presentation, pk=pk) if presentation.slot: schedule = presentation.slot.day.schedule else: schedule = None - + ctx = { "presentation": presentation, "schedule": schedule, diff --git a/symposion/speakers/admin.py b/symposion/speakers/admin.py index 8162ac3f..479c9626 100644 --- a/symposion/speakers/admin.py +++ b/symposion/speakers/admin.py @@ -4,6 +4,5 @@ from symposion.speakers.models import Speaker admin.site.register(Speaker, - list_display = ["name", "email", "created"], - search_fields = ["name"], -) \ No newline at end of file + list_display=["name", "email", "created"], + search_fields=["name"]) diff --git a/symposion/speakers/fixture_gen.py b/symposion/speakers/fixture_gen.py index fc38463f..fdecb6b7 100644 --- a/symposion/speakers/fixture_gen.py +++ b/symposion/speakers/fixture_gen.py @@ -10,7 +10,7 @@ def speakers(): guido = User.objects.create_user("guido", "guido@python.org", "pythonisawesome") matz = User.objects.create_user("matz", "matz@ruby.org", "pythonsucks") larry = User.objects.create_user("larryw", "larry@perl.org", "linenoisehere") - + Speaker.objects.create( user=guido, name="Guido van Rossum", @@ -19,8 +19,8 @@ def speakers(): Speaker.objects.create( user=matz, name="Yukihiro Matsumoto", - biography="I wrote Ruby, and named it after the rare gem Ruby, a pun " - "on Perl/pearl.", + biography=("I wrote Ruby, and named it after the rare gem Ruby, a pun " + "on Perl/pearl."), ) Speaker.objects.create( user=larry, diff --git a/symposion/speakers/forms.py b/symposion/speakers/forms.py index 334dcea2..14ecb9a9 100644 --- a/symposion/speakers/forms.py +++ b/symposion/speakers/forms.py @@ -6,7 +6,7 @@ from symposion.speakers.models import Speaker class SpeakerForm(forms.ModelForm): - + class Meta: model = Speaker fields = [ diff --git a/symposion/speakers/management/commands/export_speaker_data.py b/symposion/speakers/management/commands/export_speaker_data.py index 5f9f565d..00e82588 100644 --- a/symposion/speakers/management/commands/export_speaker_data.py +++ b/symposion/speakers/management/commands/export_speaker_data.py @@ -1,17 +1,17 @@ import csv import os -from django.core.management.base import BaseCommand, CommandError +from django.core.management.base import BaseCommand from symposion.speakers.models import Speaker class Command(BaseCommand): - + def handle(self, *args, **options): csv_file = csv.writer(open(os.path.join(os.getcwd(), "speakers.csv"), "wb")) csv_file.writerow(["Name", "Bio"]) - + for speaker in Speaker.objects.all(): csv_file.writerow([ speaker.name.encode("utf-8"), diff --git a/symposion/speakers/models.py b/symposion/speakers/models.py index c86c9aa2..b5705bcc 100644 --- a/symposion/speakers/models.py +++ b/symposion/speakers/models.py @@ -9,44 +9,47 @@ from markitup.fields import MarkupField class Speaker(models.Model): - + SESSION_COUNT_CHOICES = [ (1, "One"), (2, "Two") ] - + user = models.OneToOneField(User, null=True, related_name="speaker_profile") - name = models.CharField(max_length=100, help_text="As you would like it to appear in the conference program.") - biography = MarkupField(blank=True, help_text="A little bit about you. Edit using Markdown.") + name = models.CharField(max_length=100, help_text=("As you would like it to appear in the " + "conference program.")) + biography = MarkupField(blank=True, help_text=("A little bit about you. Edit using " + "" + "Markdown.")) photo = models.ImageField(upload_to="speaker_photos", blank=True) annotation = models.TextField() # staff only invite_email = models.CharField(max_length=200, unique=True, null=True, db_index=True) invite_token = models.CharField(max_length=40, db_index=True) created = models.DateTimeField( - default = datetime.datetime.now, - editable = False + default=datetime.datetime.now, + editable=False ) class Meta: ordering = ['name'] - + def __unicode__(self): if self.user: return self.name else: return "?" - def get_absolute_url(self): return reverse("speaker_edit") - + @property def email(self): if self.user is not None: return self.user.email else: return self.invite_email - + @property def all_presentations(self): presentations = [] diff --git a/symposion/speakers/urls.py b/symposion/speakers/urls.py index fa7055cf..0d1c7b13 100644 --- a/symposion/speakers/urls.py +++ b/symposion/speakers/urls.py @@ -1,3 +1,4 @@ +# flake8: noqa from django.conf.urls.defaults import * diff --git a/symposion/speakers/views.py b/symposion/speakers/views.py index 17eb1370..fbd4c57c 100644 --- a/symposion/speakers/views.py +++ b/symposion/speakers/views.py @@ -17,7 +17,7 @@ def speaker_create(request): return redirect(request.user.speaker_profile) except ObjectDoesNotExist: pass - + if request.method == "POST": try: speaker = Speaker.objects.get(invite_email=request.user.email) @@ -26,7 +26,7 @@ def speaker_create(request): speaker = None found = False form = SpeakerForm(request.POST, request.FILES, instance=speaker) - + if form.is_valid(): speaker = form.save(commit=False) speaker.user = request.user @@ -37,7 +37,7 @@ def speaker_create(request): return redirect("dashboard") else: form = SpeakerForm(initial={"name": request.user.get_full_name()}) - + return render(request, "speakers/speaker_create.html", { "form": form, }) @@ -48,15 +48,15 @@ def speaker_create_staff(request, pk): user = get_object_or_404(User, pk=pk) if not request.user.is_staff: raise Http404 - + try: return redirect(user.speaker_profile) except ObjectDoesNotExist: pass - + if request.method == "POST": form = SpeakerForm(request.POST, request.FILES) - + if form.is_valid(): speaker = form.save(commit=False) speaker.user = user @@ -65,7 +65,7 @@ def speaker_create_staff(request, pk): return redirect("user_list") else: form = SpeakerForm(initial={"name": user.get_full_name()}) - + return render(request, "speakers/speaker_create.html", { "form": form, }) @@ -88,8 +88,8 @@ def speaker_create_token(request, token): ).update( speaker=existing_speaker ) - messages.info(request, "You have been associated with all pending " - "talk proposals") + messages.info(request, ("You have been associated with all pending " + "talk proposals")) return redirect("dashboard") else: if not request.user.is_authenticated(): @@ -109,7 +109,7 @@ def speaker_edit(request, pk=None): speaker = get_object_or_404(Speaker, pk=pk) else: raise Http404() - + if request.method == "POST": form = SpeakerForm(request.POST, request.FILES, instance=speaker) if form.is_valid(): @@ -118,7 +118,7 @@ def speaker_edit(request, pk=None): return redirect("dashboard") else: form = SpeakerForm(instance=speaker) - + return render(request, "speakers/speaker_edit.html", { "form": form, }) @@ -129,7 +129,7 @@ def speaker_profile(request, pk): presentations = speaker.all_presentations if not presentations and not request.user.is_staff: raise Http404() - + return render(request, "speakers/speaker_profile.html", { "speaker": speaker, "presentations": presentations, diff --git a/symposion/sponsorship/admin.py b/symposion/sponsorship/admin.py index ad39a626..b64c1a13 100644 --- a/symposion/sponsorship/admin.py +++ b/symposion/sponsorship/admin.py @@ -1,6 +1,7 @@ from django.contrib import admin -from symposion.sponsorship.models import SponsorLevel, Sponsor, Benefit, BenefitLevel, SponsorBenefit +from symposion.sponsorship.models import SponsorLevel, Sponsor, Benefit, BenefitLevel, \ + SponsorBenefit class BenefitLevelInline(admin.TabularInline): @@ -24,7 +25,7 @@ class SponsorBenefitInline(admin.StackedInline): class SponsorAdmin(admin.ModelAdmin): - + save_on_top = True fieldsets = [ (None, { @@ -43,7 +44,7 @@ class SponsorAdmin(admin.ModelAdmin): ] inlines = [SponsorBenefitInline] list_display = ["name", "external_url", "level", "active"] - + def get_form(self, *args, **kwargs): # @@@ kinda ugly but using choices= on NullBooleanField is broken form = super(SponsorAdmin, self).get_form(*args, **kwargs) @@ -56,13 +57,13 @@ class SponsorAdmin(admin.ModelAdmin): class BenefitAdmin(admin.ModelAdmin): - + list_display = ["name", "type", "description"] inlines = [BenefitLevelInline] class SponsorLevelAdmin(admin.ModelAdmin): - + inlines = [BenefitLevelInline] diff --git a/symposion/sponsorship/forms.py b/symposion/sponsorship/forms.py index 9584c7fb..33b52f42 100644 --- a/symposion/sponsorship/forms.py +++ b/symposion/sponsorship/forms.py @@ -16,7 +16,7 @@ class SponsorApplicationForm(forms.ModelForm): } }) super(SponsorApplicationForm, self).__init__(*args, **kwargs) - + class Meta: model = Sponsor fields = [ @@ -26,7 +26,7 @@ class SponsorApplicationForm(forms.ModelForm): "contact_email", "level" ] - + def save(self, commit=True): obj = super(SponsorApplicationForm, self).save(commit=False) obj.applicant = self.user @@ -47,26 +47,26 @@ class SponsorDetailsForm(forms.ModelForm): class SponsorBenefitsInlineFormSet(BaseInlineFormSet): - + def _construct_form(self, i, **kwargs): form = super(SponsorBenefitsInlineFormSet, self)._construct_form(i, **kwargs) - + # only include the relevant data fields for this benefit type fields = form.instance.data_fields() form.fields = dict((k, v) for (k, v) in form.fields.items() if k in fields + ["id"]) - + for field in fields: # don't need a label, the form template will label it with the benefit name form.fields[field].label = "" - + # provide word limit as help_text if form.instance.benefit.type == "text" and form.instance.max_words: form.fields[field].help_text = u"maximum %s words" % form.instance.max_words - + # use admin file widget that shows currently uploaded file if field == "upload": form.fields[field].widget = AdminFileWidget() - + return form diff --git a/symposion/sponsorship/management/commands/reset_sponsor_benefits.py b/symposion/sponsorship/management/commands/reset_sponsor_benefits.py index 2c515a2e..3c17efab 100644 --- a/symposion/sponsorship/management/commands/reset_sponsor_benefits.py +++ b/symposion/sponsorship/management/commands/reset_sponsor_benefits.py @@ -1,15 +1,13 @@ from django.core.management.base import BaseCommand -from django.contrib.auth.models import Group - -from symposion.sponsorship.models import Sponsor, SponsorBenefit +from symposion.sponsorship.models import Sponsor, SponsorBenefit, SponsorLevel class Command(BaseCommand): - + def handle(self, *args, **options): for sponsor in Sponsor.objects.all(): - level = None + level = None try: level = sponsor.level except SponsorLevel.DoesNotExist: @@ -19,17 +17,17 @@ class Command(BaseCommand): # Create all needed benefits if they don't exist already sponsor_benefit, created = SponsorBenefit.objects.get_or_create( sponsor=sponsor, benefit=benefit_level.benefit) - + if created: print "created", sponsor_benefit, "for", sponsor - + # and set to default limits for this level. sponsor_benefit.max_words = benefit_level.max_words sponsor_benefit.other_limits = benefit_level.other_limits - + # and set to active sponsor_benefit.active = True - + # @@@ We don't call sponsor_benefit.clean here. This means # that if the sponsorship level for a sponsor is adjusted # downwards, an existing too-long text entry can remain, diff --git a/symposion/sponsorship/models.py b/symposion/sponsorship/models.py index 24a12d31..e29ac3d1 100644 --- a/symposion/sponsorship/models.py +++ b/symposion/sponsorship/models.py @@ -14,29 +14,30 @@ from symposion.sponsorship.managers import SponsorManager class SponsorLevel(models.Model): - + conference = models.ForeignKey(Conference, verbose_name=_("conference")) name = models.CharField(_("name"), max_length=100) order = models.IntegerField(_("order"), default=0) cost = models.PositiveIntegerField(_("cost")) description = models.TextField(_("description"), blank=True, help_text=_("This is private.")) - + class Meta: ordering = ["conference", "order"] verbose_name = _("sponsor level") verbose_name_plural = _("sponsor levels") - + def __unicode__(self): return self.name - + def sponsors(self): return self.sponsor_set.filter(active=True).order_by("added") class Sponsor(models.Model): - - applicant = models.ForeignKey(User, related_name="sponsorships", verbose_name=_("applicant"), null=True) - + + applicant = models.ForeignKey(User, related_name="sponsorships", verbose_name=_("applicant"), + null=True) + name = models.CharField(_("Sponsor Name"), max_length=100) external_url = models.URLField(_("external URL")) annotation = models.TextField(_("annotation"), blank=True) @@ -45,34 +46,36 @@ class Sponsor(models.Model): level = models.ForeignKey(SponsorLevel, verbose_name=_("level")) added = models.DateTimeField(_("added"), default=datetime.datetime.now) active = models.BooleanField(_("active"), default=False) - + # Denormalization (this assumes only one logo) - sponsor_logo = models.ForeignKey("SponsorBenefit", related_name="+", null=True, blank=True, editable=False) - + sponsor_logo = models.ForeignKey("SponsorBenefit", related_name="+", null=True, blank=True, + editable=False) + objects = SponsorManager() - + def __unicode__(self): return self.name - + class Meta: verbose_name = _("sponsor") verbose_name_plural = _("sponsors") - + def get_absolute_url(self): if self.active: return reverse("sponsor_detail", kwargs={"pk": self.pk}) return reverse("sponsor_list") - + @property def website_logo(self): if self.sponsor_logo is None: - benefits = self.sponsor_benefits.filter(benefit__type="weblogo", upload__isnull=False)[:1] + benefits = self.sponsor_benefits.filter( + benefit__type="weblogo", upload__isnull=False)[:1] if benefits.count(): if benefits[0].upload: self.sponsor_logo = benefits[0] self.save() return self.sponsor_logo.upload - + @property def listing_text(self): if not hasattr(self, "_listing_text"): @@ -82,46 +85,47 @@ class Sponsor(models.Model): if benefits.count(): self._listing_text = benefits[0].text return self._listing_text - + def reset_benefits(self): """ Reset all benefits for this sponsor to the defaults for their sponsorship level. """ level = None - + try: level = self.level except SponsorLevel.DoesNotExist: pass - + allowed_benefits = [] if level: for benefit_level in level.benefit_levels.all(): # Create all needed benefits if they don't exist already sponsor_benefit, created = SponsorBenefit.objects.get_or_create( sponsor=self, benefit=benefit_level.benefit) - + # and set to default limits for this level. sponsor_benefit.max_words = benefit_level.max_words sponsor_benefit.other_limits = benefit_level.other_limits - + # and set to active sponsor_benefit.active = True - + # @@@ We don't call sponsor_benefit.clean here. This means # that if the sponsorship level for a sponsor is adjusted # downwards, an existing too-long text entry can remain, # and won't raise a validation error until it's next # edited. sponsor_benefit.save() - + allowed_benefits.append(sponsor_benefit.pk) - + # Any remaining sponsor benefits that don't normally belong to # this level are set to inactive - self.sponsor_benefits.exclude(pk__in=allowed_benefits).update(active=False, max_words=None, other_limits="") - + self.sponsor_benefits.exclude(pk__in=allowed_benefits)\ + .update(active=False, max_words=None, other_limits="") + def send_coordinator_emails(self): pass # @@@ should this just be done centrally? @@ -147,59 +151,60 @@ BENEFIT_TYPE_CHOICES = [ class Benefit(models.Model): - + name = models.CharField(_("name"), max_length=100) description = models.TextField(_("description"), blank=True) - type = models.CharField(_("type"), choices=BENEFIT_TYPE_CHOICES, max_length=10, default="simple") - + type = models.CharField(_("type"), choices=BENEFIT_TYPE_CHOICES, max_length=10, + default="simple") + def __unicode__(self): return self.name class BenefitLevel(models.Model): - + benefit = models.ForeignKey(Benefit, related_name="benefit_levels", verbose_name=_("benefit")) level = models.ForeignKey(SponsorLevel, related_name="benefit_levels", verbose_name=_("level")) - + # default limits for this benefit at given level max_words = models.PositiveIntegerField(_("max words"), blank=True, null=True) other_limits = models.CharField(_("other limits"), max_length=200, blank=True) - + class Meta: ordering = ["level"] - + def __unicode__(self): return u"%s - %s" % (self.level, self.benefit) class SponsorBenefit(models.Model): - + sponsor = models.ForeignKey(Sponsor, related_name="sponsor_benefits", verbose_name=_("sponsor")) benefit = models.ForeignKey(Benefit, related_name="sponsor_benefits", verbose_name=_("benefit")) active = models.BooleanField(default=True) - + # Limits: will initially be set to defaults from corresponding BenefitLevel max_words = models.PositiveIntegerField(_("max words"), blank=True, null=True) other_limits = models.CharField(_("other limits"), max_length=200, blank=True) - + # Data: zero or one of these fields will be used, depending on the # type of the Benefit (text, file, or simple) text = models.TextField(_("text"), blank=True) upload = models.FileField(_("file"), blank=True, upload_to="sponsor_files") - + class Meta: ordering = ["-active"] - + def __unicode__(self): return u"%s - %s" % (self.sponsor, self.benefit) - + def clean(self): num_words = len(self.text.split()) if self.max_words and num_words > self.max_words: raise ValidationError( "Sponsorship level only allows for %s words, you provided %d." % ( self.max_words, num_words)) - + def data_fields(self): """ Return list of data field names which should be editable for diff --git a/symposion/sponsorship/templatetags/sponsorship_tags.py b/symposion/sponsorship/templatetags/sponsorship_tags.py index cd1f33ba..d88243c4 100644 --- a/symposion/sponsorship/templatetags/sponsorship_tags.py +++ b/symposion/sponsorship/templatetags/sponsorship_tags.py @@ -8,7 +8,7 @@ register = template.Library() class SponsorsNode(template.Node): - + @classmethod def handle_token(cls, parser, token): bits = token.split_contents() @@ -18,27 +18,30 @@ class SponsorsNode(template.Node): return cls(bits[3], bits[1]) else: raise template.TemplateSyntaxError("%r takes 'as var' or 'level as var'" % bits[0]) - + def __init__(self, context_var, level=None): if level: self.level = template.Variable(level) else: self.level = None self.context_var = context_var - + def render(self, context): conference = current_conference() if self.level: level = self.level.resolve(context) - queryset = Sponsor.objects.filter(level__conference = conference, level__name__iexact = level, active = True).order_by("added") + queryset = Sponsor.objects.filter( + level__conference=conference, level__name__iexact=level, active=True)\ + .order_by("added") else: - queryset = Sponsor.objects.filter(level__conference = conference, active = True).order_by("level__order", "added") + queryset = Sponsor.objects.filter(level__conference=conference, active=True)\ + .order_by("level__order", "added") context[self.context_var] = queryset return u"" class SponsorLevelNode(template.Node): - + @classmethod def handle_token(cls, parser, token): bits = token.split_contents() @@ -46,10 +49,10 @@ class SponsorLevelNode(template.Node): return cls(bits[2]) else: raise template.TemplateSyntaxError("%r takes 'as var'" % bits[0]) - + def __init__(self, context_var): self.context_var = context_var - + def render(self, context): conference = current_conference() context[self.context_var] = SponsorLevel.objects.filter(conference=conference) @@ -72,4 +75,3 @@ def sponsor_levels(parser, token): {% sponsor_levels as levels %} """ return SponsorLevelNode.handle_token(parser, token) - \ No newline at end of file diff --git a/symposion/sponsorship/urls.py b/symposion/sponsorship/urls.py index e5d32bb0..e6ba5fa3 100644 --- a/symposion/sponsorship/urls.py +++ b/symposion/sponsorship/urls.py @@ -1,3 +1,4 @@ +# flake8: noqa from django.conf.urls.defaults import patterns, url from django.views.generic.simple import direct_to_template diff --git a/symposion/sponsorship/views.py b/symposion/sponsorship/views.py index 22b4f243..c7728aed 100644 --- a/symposion/sponsorship/views.py +++ b/symposion/sponsorship/views.py @@ -5,7 +5,8 @@ from django.template import RequestContext from django.contrib import messages from django.contrib.auth.decorators import login_required -from symposion.sponsorship.forms import SponsorApplicationForm, SponsorDetailsForm, SponsorBenefitsFormSet +from symposion.sponsorship.forms import SponsorApplicationForm, SponsorDetailsForm, \ + SponsorBenefitsFormSet from symposion.sponsorship.models import Sponsor, SponsorBenefit @@ -18,7 +19,7 @@ def sponsor_apply(request): return redirect("sponsor_detail", pk=sponsor.pk) else: form = SponsorApplicationForm(user=request.user) - + return render_to_response("sponsorship/apply.html", { "form": form, }, context_instance=RequestContext(request)) @@ -28,7 +29,7 @@ def sponsor_apply(request): def sponsor_add(request): if not request.user.is_staff: raise Http404() - + if request.method == "POST": form = SponsorApplicationForm(request.POST, user=request.user) if form.is_valid(): @@ -38,7 +39,7 @@ def sponsor_add(request): return redirect("sponsor_detail", pk=sponsor.pk) else: form = SponsorApplicationForm(user=request.user) - + return render_to_response("sponsorship/add.html", { "form": form, }, context_instance=RequestContext(request)) @@ -47,31 +48,31 @@ def sponsor_add(request): @login_required def sponsor_detail(request, pk): sponsor = get_object_or_404(Sponsor, pk=pk) - + if sponsor.applicant != request.user: return redirect("sponsor_list") - + formset_kwargs = { "instance": sponsor, "queryset": SponsorBenefit.objects.filter(active=True) } - + if request.method == "POST": - + form = SponsorDetailsForm(request.POST, instance=sponsor) formset = SponsorBenefitsFormSet(request.POST, request.FILES, **formset_kwargs) - + if form.is_valid() and formset.is_valid(): form.save() formset.save() - + messages.success(request, "Sponsorship details have been updated") - + return redirect("dashboard") else: form = SponsorDetailsForm(instance=sponsor) formset = SponsorBenefitsFormSet(**formset_kwargs) - + return render_to_response("sponsorship/detail.html", { "sponsor": sponsor, "form": form, diff --git a/symposion/teams/admin.py b/symposion/teams/admin.py index 14b9ec25..5175e302 100644 --- a/symposion/teams/admin.py +++ b/symposion/teams/admin.py @@ -5,8 +5,7 @@ import reversion from symposion.teams.models import Team, Membership admin.site.register(Team, - prepopulated_fields={"slug": ("name",)}, -) + prepopulated_fields={"slug": ("name",)}) class MembershipAdmin(reversion.VersionAdmin): diff --git a/symposion/teams/backends.py b/symposion/teams/backends.py index 9b4ddf7e..23b001b6 100644 --- a/symposion/teams/backends.py +++ b/symposion/teams/backends.py @@ -4,10 +4,10 @@ from .models import Team class TeamPermissionsBackend(object): - + def authenticate(self, username=None, password=None): return None - + def get_team_permissions(self, user_obj, obj=None): """ Returns a set of permission strings that this user has through his/her diff --git a/symposion/teams/forms.py b/symposion/teams/forms.py index 6004ae21..a9080626 100644 --- a/symposion/teams/forms.py +++ b/symposion/teams/forms.py @@ -9,40 +9,43 @@ from symposion.teams.models import Membership class TeamInvitationForm(forms.Form): - - email = forms.EmailField(help_text="email address must be that of an account on this conference site") - + + email = forms.EmailField(help_text=("email address must be that of an account on this " + "conference site")) + def __init__(self, *args, **kwargs): self.team = kwargs.pop("team") super(TeamInvitationForm, self).__init__(*args, **kwargs) - + def clean(self): cleaned_data = super(TeamInvitationForm, self).clean() email = cleaned_data.get("email") - + if email is None: raise forms.ValidationError("valid email address required") - + try: user = User.objects.get(email=email) except User.DoesNotExist: # eventually we can invite them but for now assume they are # already on the site - raise forms.ValidationError(mark_safe("no account with email address %s found on this conference site" % escape(email))) - + raise forms.ValidationError( + mark_safe("no account with email address %s found on this conference " + "site" % escape(email))) + state = self.team.get_state_for_user(user) - + if state in ["member", "manager"]: raise forms.ValidationError("user already in team") - + if state in ["invited"]: raise forms.ValidationError("user already invited to team") - + self.user = user self.state = state - + return cleaned_data - + def invite(self): if self.state is None: Membership.objects.create(team=self.team, user=self.user, state="invited") diff --git a/symposion/teams/models.py b/symposion/teams/models.py index 7f00f055..8c344cc4 100644 --- a/symposion/teams/models.py +++ b/symposion/teams/models.py @@ -20,19 +20,20 @@ class Team(models.Model): name = models.CharField(max_length=100) description = models.TextField(blank=True) access = models.CharField(max_length=20, choices=TEAM_ACCESS_CHOICES) - + # member permissions permissions = models.ManyToManyField(Permission, blank=True, related_name="member_teams") - + # manager permissions - manager_permissions = models.ManyToManyField(Permission, blank=True, related_name="manager_teams") - + manager_permissions = models.ManyToManyField(Permission, blank=True, + related_name="manager_teams") + created = models.DateTimeField(default=datetime.datetime.now, editable=False) - + @models.permalink def get_absolute_url(self): return ("team_detail", [self.slug]) - + def __unicode__(self): return self.name @@ -41,16 +42,16 @@ class Team(models.Model): return self.memberships.get(user=user).state except Membership.DoesNotExist: return None - + def applicants(self): return self.memberships.filter(state="applied") - + def invitees(self): return self.memberships.filter(state="invited") - + def members(self): return self.memberships.filter(state="member") - + def managers(self): return self.memberships.filter(state="manager") diff --git a/symposion/teams/templatetags/teams_tags.py b/symposion/teams/templatetags/teams_tags.py index 7c9cd5b5..b82a0eba 100644 --- a/symposion/teams/templatetags/teams_tags.py +++ b/symposion/teams/templatetags/teams_tags.py @@ -6,7 +6,7 @@ register = template.Library() class AvailableTeamsNode(template.Node): - + @classmethod def handle_token(cls, parser, token): bits = token.split_contents() @@ -14,10 +14,10 @@ class AvailableTeamsNode(template.Node): return cls(bits[2]) else: raise template.TemplateSyntaxError("%r takes 'as var'" % bits[0]) - + def __init__(self, context_var): self.context_var = context_var - + def render(self, context): request = context["request"] teams = [] diff --git a/symposion/teams/urls.py b/symposion/teams/urls.py index 01145f41..d70a600d 100644 --- a/symposion/teams/urls.py +++ b/symposion/teams/urls.py @@ -1,3 +1,4 @@ +# flake8: noqa from django.conf.urls.defaults import * @@ -7,7 +8,7 @@ urlpatterns = patterns("symposion.teams.views", url(r"^(?P[\w\-]+)/join/$", "team_join", name="team_join"), url(r"^(?P[\w\-]+)/leave/$", "team_leave", name="team_leave"), url(r"^(?P[\w\-]+)/apply/$", "team_apply", name="team_apply"), - + # membership specific url(r"^promote/(?P\d+)/$", "team_promote", name="team_promote"), url(r"^demote/(?P\d+)/$", "team_demote", name="team_demote"), diff --git a/symposion/teams/views.py b/symposion/teams/views.py index e885f9b4..f45a0cba 100644 --- a/symposion/teams/views.py +++ b/symposion/teams/views.py @@ -10,7 +10,7 @@ from symposion.teams.forms import TeamInvitationForm from symposion.teams.models import Team, Membership -## perm checks +# perm checks # # @@@ these can be moved @@ -50,7 +50,7 @@ def can_invite(team, user): return False -## views +# views @login_required @@ -59,7 +59,7 @@ def team_detail(request, slug): state = team.get_state_for_user(request.user) if team.access == "invitation" and state is None and not request.user.is_staff: raise Http404() - + if can_invite(team, request.user): if request.method == "POST": form = TeamInvitationForm(request.POST, team=team) @@ -72,7 +72,7 @@ def team_detail(request, slug): form = TeamInvitationForm(team=team) else: form = None - + return render(request, "teams/team_detail.html", { "team": team, "state": state, @@ -89,7 +89,7 @@ def team_join(request, slug): state = team.get_state_for_user(request.user) if team.access == "invitation" and state is None and not request.user.is_staff: raise Http404() - + if can_join(team, request.user) and request.method == "POST": membership, created = Membership.objects.get_or_create(team=team, user=request.user) membership.state = "member" @@ -106,7 +106,7 @@ def team_leave(request, slug): state = team.get_state_for_user(request.user) if team.access == "invitation" and state is None and not request.user.is_staff: raise Http404() - + if can_leave(team, request.user) and request.method == "POST": membership = Membership.objects.get(team=team, user=request.user) membership.delete() @@ -122,7 +122,7 @@ def team_apply(request, slug): state = team.get_state_for_user(request.user) if team.access == "invitation" and state is None and not request.user.is_staff: raise Http404() - + if can_apply(team, request.user) and request.method == "POST": membership, created = Membership.objects.get_or_create(team=team, user=request.user) membership.state = "applied" diff --git a/symposion/utils/mail.py b/symposion/utils/mail.py index dc856871..59cdf09c 100644 --- a/symposion/utils/mail.py +++ b/symposion/utils/mail.py @@ -7,9 +7,9 @@ from django.contrib.sites.models import Site def send_email(to, kind, **kwargs): - + current_site = Site.objects.get_current() - + ctx = { "current_site": current_site, "STATIC_URL": settings.STATIC_URL, @@ -19,12 +19,12 @@ def send_email(to, kind, **kwargs): current_site.name, render_to_string("emails/%s/subject.txt" % kind, ctx).strip() ) - + message_html = render_to_string("emails/%s/message.html" % kind, ctx) message_plaintext = strip_tags(message_html) - + from_email = settings.DEFAULT_FROM_EMAIL - + email = EmailMultiAlternatives(subject, message_plaintext, from_email, to) email.attach_alternative(message_html, "text/html") email.send() diff --git a/symposion/views.py b/symposion/views.py index 669e2847..e1bfab7e 100644 --- a/symposion/views.py +++ b/symposion/views.py @@ -12,12 +12,12 @@ import symposion.forms class SignupView(account.views.SignupView): - + form_class = symposion.forms.SignupForm form_kwargs = { "prefix": "signup", } - + def create_user(self, form, commit=True): user_kwargs = { "first_name": form.cleaned_data["first_name"], From 0486af4bba7414ca62f9dd5c7da52b2bb1029517 Mon Sep 17 00:00:00 2001 From: Carlos Henrique Romano Date: Wed, 30 Jul 2014 15:57:45 -0300 Subject: [PATCH 559/751] Remove unused code --- symposion/proposals/managers.py | 22 ---------------------- 1 file changed, 22 deletions(-) delete mode 100644 symposion/proposals/managers.py diff --git a/symposion/proposals/managers.py b/symposion/proposals/managers.py deleted file mode 100644 index e6ca5245..00000000 --- a/symposion/proposals/managers.py +++ /dev/null @@ -1,22 +0,0 @@ -from django.db import models -from django.db.models.query import QuerySet - - -class CachingM2MQuerySet(QuerySet): - - def __init__(self, *args, **kwargs): - super(CachingM2MQuerySet, self).__init__(*args, **kwargs) - self.cached_m2m_field = kwargs["m2m_field"] - - def iterator(self): - parent_iter = super(CachingM2MQuerySet, self).iterator() - - for obj in parent_iter: - if obj.id in cached_objects: - setattr(obj, "_cached_m2m_%s" % self.cached_m2m_field) - yield obj - - -class ProposalManager(models.Manager): - def cache_m2m(self, m2m_field): - return CachingM2MQuerySet(self.model, using=self._db, m2m_field=m2m_field) From 08939f1f3eed6ae1b659c62c6c11c43bbf184a3a Mon Sep 17 00:00:00 2001 From: Carlos Henrique Romano Date: Wed, 30 Jul 2014 15:57:45 -0300 Subject: [PATCH 560/751] Remove unused code --- symposion/proposals/managers.py | 22 ---------------------- 1 file changed, 22 deletions(-) delete mode 100644 symposion/proposals/managers.py diff --git a/symposion/proposals/managers.py b/symposion/proposals/managers.py deleted file mode 100644 index e6ca5245..00000000 --- a/symposion/proposals/managers.py +++ /dev/null @@ -1,22 +0,0 @@ -from django.db import models -from django.db.models.query import QuerySet - - -class CachingM2MQuerySet(QuerySet): - - def __init__(self, *args, **kwargs): - super(CachingM2MQuerySet, self).__init__(*args, **kwargs) - self.cached_m2m_field = kwargs["m2m_field"] - - def iterator(self): - parent_iter = super(CachingM2MQuerySet, self).iterator() - - for obj in parent_iter: - if obj.id in cached_objects: - setattr(obj, "_cached_m2m_%s" % self.cached_m2m_field) - yield obj - - -class ProposalManager(models.Manager): - def cache_m2m(self, m2m_field): - return CachingM2MQuerySet(self.model, using=self._db, m2m_field=m2m_field) From f557337433d53db405d4bc850cf87b76393d1da3 Mon Sep 17 00:00:00 2001 From: Carlos Henrique Romano Date: Thu, 31 Jul 2014 08:57:45 -0300 Subject: [PATCH 561/751] Increase code complexity in travis configuration Also mark the view reported as too complex with a FIXME, so we can refactor it when possible. --- .travis.yml | 2 +- symposion/reviews/views.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index d55e6ec0..44782e61 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,4 +8,4 @@ install: - pip install -e . script: - - flake8 --max-line-length=100 --max-complexity=10 --statistics --benchmark symposion + - flake8 --max-line-length=100 --max-complexity=24 --statistics --benchmark symposion diff --git a/symposion/reviews/views.py b/symposion/reviews/views.py index 2be745e5..28be7101 100644 --- a/symposion/reviews/views.py +++ b/symposion/reviews/views.py @@ -167,6 +167,7 @@ def review_admin(request, section_slug): return render(request, "reviews/review_admin.html", ctx) +# FIXME: This view is too complex according to flake8 @login_required def review_detail(request, pk): From a6cd048f43958484d5eb6994320fb2fb2c28972b Mon Sep 17 00:00:00 2001 From: Brian Rosner Date: Thu, 31 Jul 2014 14:54:10 -0600 Subject: [PATCH 562/751] added source English PO --- .tx/config | 7 + symposion/locale/en/LC_MESSAGES/django.mo | Bin 0 -> 378 bytes symposion/locale/en/LC_MESSAGES/django.po | 537 ++++++++++++++++++++++ 3 files changed, 544 insertions(+) create mode 100644 .tx/config create mode 100644 symposion/locale/en/LC_MESSAGES/django.mo create mode 100644 symposion/locale/en/LC_MESSAGES/django.po diff --git a/.tx/config b/.tx/config new file mode 100644 index 00000000..d8ebca05 --- /dev/null +++ b/.tx/config @@ -0,0 +1,7 @@ +[main] +host = https://www.transifex.com + +[symposion.djangopo] +file_filter = symposion/locale//LC_MESSAGES/django.po +source_lang = en +source_file = symposion/locale/en/LC_MESSAGES/django.po diff --git a/symposion/locale/en/LC_MESSAGES/django.mo b/symposion/locale/en/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..9cdd2446c8c8e855d0aca03d0c9c5c9e7ec79747 GIT binary patch literal 378 zcmYL^u};G<6h(_*%E-*%fel_sprXYTRFk+R!cM9*tpuBCO$aJ><+unwgx}*^c##mj z(vb(BXW#d8c<|l9>>)?UF|v=GB5el92rsYjY@Pp1oqf|w59taP+BLN`luTLtz$Tn5 zcri=!EGYEGIuEj`>J{8=s|HF-NifYzh@Dm*hagd2Q*s_gJqY`75s|1zdwmEm!w^L% z^he!3{^vb%R)EYQVdOriGCg_9Gad-lcqpAR&02ZuC>hU%AQ_XK47p^f7_lTN_-uBI zs;RcB(okykPKjXIWZqUD3Nc#OUF)iygH>fJ`EI|c8wRm8UK{-GYlFSL{`6;?wW`fE hS-vXQXn!*=$1vD=ZLJsWfSaXV)uy6k@Lc<#^9x=KW=#M9 literal 0 HcmV?d00001 diff --git a/symposion/locale/en/LC_MESSAGES/django.po b/symposion/locale/en/LC_MESSAGES/django.po new file mode 100644 index 00000000..0f07ab78 --- /dev/null +++ b/symposion/locale/en/LC_MESSAGES/django.po @@ -0,0 +1,537 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2014-07-31 14:47-0600\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: cms/models.py:23 +msgid "Draft" +msgstr "" + +#: cms/models.py:24 +msgid "Public" +msgstr "" + +#: cms/models.py:57 +msgid "Path can only contain letters, numbers and hyphens and end with /" +msgstr "" + +#: conference/models.py:15 +msgid "title" +msgstr "" + +#: conference/models.py:18 conference/models.py:58 +msgid "start date" +msgstr "" + +#: conference/models.py:19 conference/models.py:59 +msgid "end date" +msgstr "" + +#: conference/models.py:22 +msgid "timezone" +msgstr "" + +#: conference/models.py:41 conference/models.py:52 sponsorship/models.py:18 +msgid "conference" +msgstr "" + +#: conference/models.py:42 +msgid "conferences" +msgstr "" + +#: conference/models.py:54 sponsorship/models.py:19 sponsorship/models.py:155 +msgid "name" +msgstr "" + +#: conference/models.py:65 +msgid "section" +msgstr "" + +#: conference/models.py:66 +msgid "sections" +msgstr "" + +#: proposals/models.py:71 templates/conference/user_list.html:60 +msgid "Name" +msgstr "" + +#: proposals/models.py:86 +msgid "Brief Description" +msgstr "" + +#: proposals/models.py:88 +msgid "" +"If your proposal is accepted this will be made public and printed in the " +"program. Should be one paragraph, maximum 400 characters." +msgstr "" + +#: proposals/models.py:92 +msgid "Detailed Abstract" +msgstr "" + +#: proposals/models.py:93 +msgid "" +"Detailed outline. Will be made public if your proposal is accepted. Edit " +"using Markdown." +msgstr "" + +#: proposals/models.py:99 +msgid "" +"Anything else you'd like the program committee to know when making their " +"selection: your past experience, etc. This is not made public. Edit using Markdown." +msgstr "" + +#: proposals/models.py:153 +msgid "Pending" +msgstr "" + +#: proposals/models.py:154 templates/proposals/_pending_proposal_row.html:16 +msgid "Accepted" +msgstr "" + +#: proposals/models.py:155 +msgid "Declined" +msgstr "" + +#: sponsorship/models.py:20 +msgid "order" +msgstr "" + +#: sponsorship/models.py:21 +msgid "cost" +msgstr "" + +#: sponsorship/models.py:22 sponsorship/models.py:156 +msgid "description" +msgstr "" + +#: sponsorship/models.py:22 +msgid "This is private." +msgstr "" + +#: sponsorship/models.py:26 +msgid "sponsor level" +msgstr "" + +#: sponsorship/models.py:27 +msgid "sponsor levels" +msgstr "" + +#: sponsorship/models.py:38 +msgid "applicant" +msgstr "" + +#: sponsorship/models.py:41 +msgid "Sponsor Name" +msgstr "" + +#: sponsorship/models.py:42 +msgid "external URL" +msgstr "" + +#: sponsorship/models.py:43 +msgid "annotation" +msgstr "" + +#: sponsorship/models.py:44 +msgid "Contact Name" +msgstr "" + +#: sponsorship/models.py:45 +msgid "Contact Email" +msgstr "" + +#: sponsorship/models.py:46 sponsorship/models.py:167 +msgid "level" +msgstr "" + +#: sponsorship/models.py:47 +msgid "added" +msgstr "" + +#: sponsorship/models.py:48 +msgid "active" +msgstr "" + +#: sponsorship/models.py:60 sponsorship/models.py:182 +msgid "sponsor" +msgstr "" + +#: sponsorship/models.py:61 +msgid "sponsors" +msgstr "" + +#: sponsorship/models.py:157 +msgid "type" +msgstr "" + +#: sponsorship/models.py:166 sponsorship/models.py:183 +msgid "benefit" +msgstr "" + +#: sponsorship/models.py:170 sponsorship/models.py:187 +msgid "max words" +msgstr "" + +#: sponsorship/models.py:171 sponsorship/models.py:188 +msgid "other limits" +msgstr "" + +#: sponsorship/models.py:192 +msgid "text" +msgstr "" + +#: sponsorship/models.py:193 +msgid "file" +msgstr "" + +#: templates/dashboard.html:16 +msgid "Speaking" +msgstr "" + +#: templates/dashboard.html:92 templates/sponsorship/detail.html:8 +msgid "Sponsorship" +msgstr "" + +#: templates/dashboard.html:132 templates/reviews/review_detail.html:75 +msgid "Reviews" +msgstr "" + +#: templates/dashboard.html:177 +msgid "Teams" +msgstr "" + +#: templates/boxes/box.html:9 +msgid "Editing content:" +msgstr "" + +#: templates/cms/page_edit.html:11 +msgid "Edit page at:" +msgstr "" + +#: templates/conference/user_list.html:59 +msgid "Email" +msgstr "" + +#: templates/conference/user_list.html:61 +msgid "Speaker Profile?" +msgstr "" + +#: templates/emails/teams_user_applied/message.html:3 +#, python-format +msgid "" +"\n" +"

    \n" +" User \"%(username)s\" has applied to join %(team_name)s on " +"%(site_name)s.\n" +"

    \n" +"\n" +"

    \n" +" To accept this application and see any other pending applications, " +"visit the following url:\n" +" http://%(site_url)s" +"%(team_url)s\n" +"

    \n" +msgstr "" + +#: templates/emails/teams_user_applied/subject.txt:1 +#, python-format +msgid "%(username)s has applied to to join \"%(team)s\"" +msgstr "" + +#: templates/emails/teams_user_invited/message.html:3 +#, python-format +msgid "" +"\n" +"

    \n" +" You have been invited to join %(team_name)s on " +"%(site_name)s.\n" +"

    \n" +"\n" +"

    \n" +" To accept this invitation, visit the following url:\n" +" http://%(site_url)s" +"%(team_url)s\n" +"

    \n" +msgstr "" + +#: templates/emails/teams_user_invited/subject.txt:1 +#, python-format +msgid "You have been invited to join \"%(team)s\"" +msgstr "" + +#: templates/proposals/_pending_proposal_row.html:12 +msgid "Cancelled" +msgstr "" + +#: templates/proposals/_pending_proposal_row.html:18 +msgid "Submitted" +msgstr "" + +#: templates/proposals/_pending_proposal_row.html:21 +msgid "Invited" +msgstr "" + +#: templates/proposals/_pending_proposal_row.html:30 +msgid "Choose Response" +msgstr "" + +#: templates/proposals/_pending_proposal_row.html:35 +msgid "Accept invitation" +msgstr "" + +#: templates/proposals/_pending_proposal_row.html:37 +msgid "Decline invitation" +msgstr "" + +#: templates/proposals/_proposal_fields.html:4 +msgid "Submitted by" +msgstr "" + +#: templates/proposals/_proposal_fields.html:7 +msgid "Track" +msgstr "" + +#: templates/proposals/_proposal_fields.html:10 +msgid "Audience Level" +msgstr "" + +#: templates/proposals/_proposal_fields.html:14 +msgid "Additional Speakers" +msgstr "" + +#: templates/proposals/_proposal_fields.html:21 +msgid "Invitation Sent" +msgstr "" + +#: templates/proposals/_proposal_fields.html:28 +msgid "Description" +msgstr "" + +#: templates/proposals/_proposal_fields.html:31 +msgid "Abstract" +msgstr "" + +#: templates/proposals/_proposal_fields.html:34 +msgid "Notes" +msgstr "" + +#: templates/proposals/_proposal_fields.html:37 +msgid "Speaker Bio" +msgstr "" + +#: templates/proposals/_proposal_fields.html:40 +msgid "Documents" +msgstr "" + +#: templates/proposals/proposal_cancel.html:7 +msgid "Cancel Proposal" +msgstr "" + +#: templates/proposals/proposal_cancel.html:16 +msgid "No, keep it for now" +msgstr "" + +#: templates/proposals/proposal_detail.html:14 +msgid "Edit this proposal" +msgstr "" + +#: templates/proposals/proposal_detail.html:17 +msgid "Cancel this proposal" +msgstr "" + +#: templates/proposals/proposal_detail.html:21 +msgid "Remove me from this proposal" +msgstr "" + +#: templates/proposals/proposal_detail.html:33 +#: templates/reviews/review_detail.html:74 +msgid "Proposal Details" +msgstr "" + +#: templates/proposals/proposal_detail.html:35 +#: templates/proposals/proposal_detail.html:47 +msgid "Supporting Documents" +msgstr "" + +#: templates/proposals/proposal_detail.html:38 +msgid "Reviewer Feedback" +msgstr "" + +#: templates/proposals/proposal_detail.html:57 +msgid "delete" +msgstr "" + +#: templates/proposals/proposal_detail.html:64 +msgid "No supporting documents attached to this proposal." +msgstr "" + +#: templates/proposals/proposal_detail.html:66 +msgid "Add Document" +msgstr "" + +#: templates/proposals/proposal_detail.html:73 +msgid "Conversation with Reviewers" +msgstr "" + +#: templates/proposals/proposal_detail.html:83 +msgid "Leave a Message" +msgstr "" + +#: templates/proposals/proposal_detail.html:85 +msgid "You can leave a message for the reviewers here." +msgstr "" + +#: templates/proposals/proposal_detail.html:94 +msgid "Submit" +msgstr "" + +#: templates/proposals/proposal_speaker_manage.html:7 +msgid "Proposal:" +msgstr "" + +#: templates/proposals/proposal_speaker_manage.html:10 +msgid "Edit proposal" +msgstr "" + +#: templates/proposals/proposal_speaker_manage.html:14 +msgid "Current Speakers" +msgstr "" + +#: templates/proposals/proposal_speaker_manage.html:20 +msgid "pending invitation" +msgstr "" + +#: templates/proposals/proposal_speaker_manage.html:24 +msgid "Add another speaker" +msgstr "" + +#: templates/proposals/proposal_submit.html:6 +msgid "Submit A Proposal" +msgstr "" + +#: templates/reviews/_review_table.html:6 +#: templates/reviews/result_notification.html:45 +msgid "Speaker / Title" +msgstr "" + +#: templates/reviews/_review_table.html:7 +#: templates/reviews/result_notification.html:46 +msgid "Category" +msgstr "" + +#: templates/reviews/_review_table.html:9 +msgid "+1" +msgstr "" + +#: templates/reviews/_review_table.html:10 +msgid "+0" +msgstr "" + +#: templates/reviews/_review_table.html:11 +msgid "-0" +msgstr "" + +#: templates/reviews/_review_table.html:12 +msgid "-1" +msgstr "" + +#: templates/reviews/_review_table.html:13 +msgid "Your Rating" +msgstr "" + +#: templates/reviews/base.html:64 +msgid "All Reviews" +msgstr "" + +#: templates/reviews/base.html:77 +msgid "Voting Status" +msgstr "" + +#: templates/reviews/result_notification.html:47 +msgid "Status" +msgstr "" + +#: templates/reviews/result_notification.html:48 +msgid "Notified?" +msgstr "" + +#: templates/reviews/review_detail.html:76 +msgid "Speaker Feedback" +msgstr "" + +#: templates/reviews/review_detail.html:84 +msgid "Current Results" +msgstr "" + +#: templates/reviews/review_detail.html:91 +msgid "Total Responses" +msgstr "" + +#: templates/reviews/review_detail.html:108 +msgid "Submit Review" +msgstr "" + +#: templates/reviews/review_detail.html:148 +msgid "Conversation with the submitter" +msgstr "" + +#: templates/reviews/review_detail.html:162 +msgid "Send a message" +msgstr "" + +#: templates/reviews/review_detail.html:164 +msgid "" +"\n" +" If you'd like to communicate with the submitter, " +"use the following form and he or she will be\n" +" notified and given the opportunity to respond.\n" +" " +msgstr "" + +#: templates/schedule/_slot_edit.html:5 +msgid "Edit Slot" +msgstr "" + +#: templates/speakers/speaker_create.html:7 +#: templates/speakers/speaker_create.html:14 +msgid "Create Speaker Profile" +msgstr "" + +#: templates/speakers/speaker_edit.html:7 +#: templates/speakers/speaker_edit.html:14 +msgid "Edit Speaker Profile" +msgstr "" + +#: templates/sponsorship/add.html:7 templates/sponsorship/add.html.py:14 +msgid "Add a Sponsor" +msgstr "" + +#: templates/sponsorship/apply.html:7 +msgid "Apply to be a Sponsor" +msgstr "" + +#: templates/sponsorship/apply.html:17 +msgid "Apply to Be a Sponsor" +msgstr "" + +#: templates/sponsorship/list.html:7 templates/sponsorship/list.html.py:14 +msgid "About Our Sponsors" +msgstr "" From 2f6930ae11c6dcffd55d2e36c347fbcedd8552fa Mon Sep 17 00:00:00 2001 From: David Ray Date: Mon, 11 Aug 2014 16:33:23 -0400 Subject: [PATCH 563/751] Fixes #12; 500 error when re/un assigning slot presentations --- symposion/schedule/models.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/symposion/schedule/models.py b/symposion/schedule/models.py index c4614437..5ffd63ce 100644 --- a/symposion/schedule/models.py +++ b/symposion/schedule/models.py @@ -77,9 +77,10 @@ class Slot(models.Model): """ Unassign the associated content with this slot. """ - if self.content and self.content.slot_id: - self.content.slot = None - self.content.save() + content = self.content + if content and content.slot_id: + content.slot = None + content.save() @property def content(self): From ee1f4d92a53175280f4ca83de837b4dc9e3b1081 Mon Sep 17 00:00:00 2001 From: Sheila Miguez Date: Sat, 27 Sep 2014 12:10:24 -0500 Subject: [PATCH 564/751] flake8 fixes for the schedule edit form. imports were missing. added a TODO for the atomic call, which is not supported in django 1.4 --- symposion/schedule/forms.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/symposion/schedule/forms.py b/symposion/schedule/forms.py index 161f0396..f9b863ac 100644 --- a/symposion/schedule/forms.py +++ b/symposion/schedule/forms.py @@ -1,9 +1,15 @@ +import csv +import time +from datetime import datetime + from django import forms +from django.contrib import messages +from django.db import IntegrityError from django.db.models import Q from markitup.widgets import MarkItUpWidget -from symposion.schedule.models import Presentation +from symposion.schedule.models import (Day, Presentation, Room, SlotKind, Slot, SlotRoom) class SlotEditForm(forms.Form): @@ -139,8 +145,10 @@ class ScheduleSectionForm(forms.Form): ) created_items.append(slot) try: - with transaction.atomic(): - SlotRoom.objects.create(slot=slot, room=room) + # @@@ TODO - upgrade Django, use atomic transactions + # with transaction.atomic(): + # SlotRoom.objects.create(slot=slot, room=room) + SlotRoom.objects.create(slot=slot, room=room) except IntegrityError: # delete all created objects and report error for x in created_items: From c4db94b7e5fd60e1d22ece1c3e1abb783f8db2f2 Mon Sep 17 00:00:00 2001 From: Taavi Burns Date: Sun, 29 Sep 2013 17:02:01 -0400 Subject: [PATCH 565/751] Adds a schedule_json view which provides a /schedule/conference.json endpoint, of the kind that Carl uses for producing conference videos. Also useful to feed into mobile schedule apps! It is expected that someone might have to customize this for their own installation (PyCon Canada definitely did, with modifications to some of the models). --- symposion/schedule/models.py | 25 ++++++++++++++++++ symposion/schedule/urls.py | 4 +++ symposion/schedule/views.py | 49 ++++++++++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+) diff --git a/symposion/schedule/models.py b/symposion/schedule/models.py index 0d7f642e..7f8d0e25 100644 --- a/symposion/schedule/models.py +++ b/symposion/schedule/models.py @@ -1,3 +1,5 @@ +import datetime + from django.core.exceptions import ObjectDoesNotExist from django.db import models @@ -92,6 +94,29 @@ class Slot(models.Model): except ObjectDoesNotExist: return None + @property + def start_datetime(self): + return datetime.datetime( + self.day.date.year, + self.day.date.month, + self.day.date.day, + self.start.hour, + self.start.minute) + + @property + def end_datetime(self): + return datetime.datetime( + self.day.date.year, + self.day.date.month, + self.day.date.day, + self.end.hour, + self.end.minute) + + @property + def length_in_minutes(self): + return int( + (self.end_datetime - self.start_datetime).total_seconds() / 60) + @property def rooms(self): return Room.objects.filter(pk__in=self.slotroom_set.values("room")) diff --git a/symposion/schedule/urls.py b/symposion/schedule/urls.py index 59e9b55f..c25a84ba 100644 --- a/symposion/schedule/urls.py +++ b/symposion/schedule/urls.py @@ -1,5 +1,8 @@ # flake8: noqa from django.conf.urls.defaults import url, patterns +from django.views.decorators.cache import cache_page + +from symposion.schedule.views import schedule_json urlpatterns = patterns("symposion.schedule.views", @@ -13,4 +16,5 @@ urlpatterns = patterns("symposion.schedule.views", url(r"^([\w\-]+)/list/$", "schedule_list", name="schedule_list"), url(r"^([\w\-]+)/presentations.csv$", "schedule_list_csv", name="schedule_list_csv"), url(r"^([\w\-]+)/edit/slot/(\d+)/", "schedule_slot_edit", name="schedule_slot_edit"), + url(r"^conference.json", cache_page(300)(schedule_json), name="schedule_json"), ) diff --git a/symposion/schedule/views.py b/symposion/schedule/views.py index 98771b59..a4d65b65 100644 --- a/symposion/schedule/views.py +++ b/symposion/schedule/views.py @@ -156,3 +156,52 @@ def schedule_presentation_detail(request, pk): "schedule": schedule, } return render(request, "schedule/presentation_detail.html", ctx) + + +def schedule_json(request): + everything = bool(request.GET.get('everything')) + slots = Slot.objects.all().order_by("start") + data = [] + for slot in slots: + if slot.content: + slot_data = { + "name": slot.content.title, + "room": ", ".join(room["name"] for room in slot.rooms.values()), + "start": slot.start_datetime.isoformat(), + "end": slot.end_datetime.isoformat(), + "duration": slot.length_in_minutes, + "authors": [s.name for s in slot.content.speakers()], + "released": slot.content.proposal.recording_release, + # You may wish to change this... + "license": "All Rights Reserved", + "contact": + [s.email for s in slot.content.speakers()] + if request.user.is_staff + else ["redacted"], + "abstract": slot.content.abstract.raw, + "description": slot.content.description.raw, + "conf_key": slot.content.pk, + "conf_url": "https://%s%s" % ( + Site.objects.get_current().domain, + reverse("schedule_presentation_detail", args=[slot.content.pk]) + ), + "kind": slot.kind.label, + "tags": "", + } + elif everything: + slot_data = { + "room": ", ".join(room["name"] for room in slot.rooms.values()), + "start": slot.start_datetime.isoformat(), + "end": slot.end_datetime.isoformat(), + "duration": slot.length_in_minutes, + "kind": slot.kind.label, + "title": slot.content_override.raw, + } + else: + continue + data.append(slot_data) + + return HttpResponse( + json.dumps({'schedule': data}), + content_type="application/json" + ) From 51709c6eaf398c50cbeaf5032758e1c1c0c39438 Mon Sep 17 00:00:00 2001 From: Sheila Miguez Date: Sat, 20 Sep 2014 20:20:04 -0500 Subject: [PATCH 566/751] adds a schedule json endpoint. based on @taavi's PR #45 with some changes from the @pyohio/pyohio repo --- requirements-test.txt | 11 +++++ symposion/schedule/tests/factories.py | 62 +++++++++++++++++++++++ symposion/schedule/tests/runtests.py | 68 ++++++++++++++++++++++++++ symposion/schedule/tests/test_views.py | 31 ++++++++++++ symposion/schedule/urls.py | 6 +-- symposion/schedule/views.py | 58 +++++++++++----------- 6 files changed, 201 insertions(+), 35 deletions(-) create mode 100644 requirements-test.txt create mode 100644 symposion/schedule/tests/factories.py create mode 100755 symposion/schedule/tests/runtests.py create mode 100644 symposion/schedule/tests/test_views.py diff --git a/requirements-test.txt b/requirements-test.txt new file mode 100644 index 00000000..2ebbf3b6 --- /dev/null +++ b/requirements-test.txt @@ -0,0 +1,11 @@ +Django==1.4.15 +Pillow==2.5.3 +django-discover-runner==1.0 +django-markitup==2.2.2 +django-model-utils==1.5.0 +django-nose==1.2 +django-reversion==1.8.0 +django-timezones==0.2 +factory-boy==2.4.1 +nose==1.3.4 +pytz==2014.7 diff --git a/symposion/schedule/tests/factories.py b/symposion/schedule/tests/factories.py new file mode 100644 index 00000000..f4304e9a --- /dev/null +++ b/symposion/schedule/tests/factories.py @@ -0,0 +1,62 @@ +import datetime +import random + +import factory +from factory import fuzzy + +from symposion.schedule.models import Schedule, Day, Slot, SlotKind +from symposion.conference.models import Section, Conference + + +class ConferenceFactory(factory.DjangoModelFactory): + title = fuzzy.FuzzyText() + start_date = fuzzy.FuzzyDate(datetime.date(2014, 1, 1)) + end_date = fuzzy.FuzzyDate(datetime.date(2014, 1, 1) + datetime.timedelta(days=random.randint(1,10))) + #timezone = TimeZoneField("UTC") + + class Meta: + model = Conference + + +class SectionFactory(factory.DjangoModelFactory): + conference = factory.SubFactory(ConferenceFactory) + name = fuzzy.FuzzyText() + slug = fuzzy.FuzzyText() + + class Meta: + model = Section + + +class ScheduleFactory(factory.DjangoModelFactory): + section = factory.SubFactory(SectionFactory) + published = True + hidden = False + + class Meta: + model = Schedule + + +class SlotKindFactory(factory.DjangoModelFactory): + schedule = factory.SubFactory(ScheduleFactory) + label = fuzzy.FuzzyText() + + class Meta: + model = SlotKind + + +class DayFactory(factory.DjangoModelFactory): + schedule = factory.SubFactory(ScheduleFactory) + date = fuzzy.FuzzyDate(datetime.date(2014, 1, 1)) + + class Meta: + model = Day + + +class SlotFactory(factory.DjangoModelFactory): + day = factory.SubFactory(DayFactory) + kind = factory.SubFactory(SlotKindFactory) + start = datetime.time(random.randint(0,23), random.randint(0,59)) + end = datetime.time(random.randint(0,23), random.randint(0,59)) + + class Meta: + model = Slot diff --git a/symposion/schedule/tests/runtests.py b/symposion/schedule/tests/runtests.py new file mode 100755 index 00000000..dcb46455 --- /dev/null +++ b/symposion/schedule/tests/runtests.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# see runtests.py in https://github.com/pydanny/cookiecutter-djangopackage + +import sys + +try: + from django.conf import settings + + settings.configure( + DEBUG=True, + USE_TZ=True, + DATABASES={ + "default": { + "ENGINE": "django.db.backends.sqlite3", + } + }, + ROOT_URLCONF="symposion.schedule.urls", + INSTALLED_APPS=[ + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sites", + + "markitup", + "reversion", + + "symposion", + "symposion.conference", + "symposion.speakers", + "symposion.schedule", + "symposion.proposals", + + ], + SITE_ID=1, + NOSE_ARGS=['-s'], + + MARKITUP_FILTER=('django.contrib.markup.templatetags.markup.textile', {}), + ) + + try: + import django + setup = django.setup + except AttributeError: + pass + else: + setup() + + from django_nose import NoseTestSuiteRunner +except ImportError: + raise ImportError("To fix this error, run: pip install -r requirements-test.txt") + + +def run_tests(*test_args): + if not test_args: + test_args = ['tests'] + + # Run tests + test_runner = NoseTestSuiteRunner(verbosity=1) + + failures = test_runner.run_tests(test_args) + + if failures: + sys.exit(failures) + + +if __name__ == '__main__': + run_tests("symposion.schedule.tests.test_views") diff --git a/symposion/schedule/tests/test_views.py b/symposion/schedule/tests/test_views.py new file mode 100644 index 00000000..04e04efd --- /dev/null +++ b/symposion/schedule/tests/test_views.py @@ -0,0 +1,31 @@ +import json + +from django.test.client import Client +from django.test import TestCase + +from . import factories + + +class ScheduleViewTests(TestCase): + + def test_empty_json(self): + c = Client() + r = c.get('/conference.json') + assert r.status_code == 200 + + conference = json.loads(r.content) + assert 'schedule' in conference + assert len(conference['schedule']) == 0 + + + def test_populated_empty_presentations(self): + + factories.SlotFactory.create_batch(size=5) + + c = Client() + r = c.get('/conference.json') + assert r.status_code == 200 + + conference = json.loads(r.content) + assert 'schedule' in conference + assert len(conference['schedule']) == 5 diff --git a/symposion/schedule/urls.py b/symposion/schedule/urls.py index c25a84ba..94acd3e1 100644 --- a/symposion/schedule/urls.py +++ b/symposion/schedule/urls.py @@ -1,9 +1,5 @@ # flake8: noqa from django.conf.urls.defaults import url, patterns -from django.views.decorators.cache import cache_page - -from symposion.schedule.views import schedule_json - urlpatterns = patterns("symposion.schedule.views", url(r"^$", "schedule_conference", name="schedule_conference"), @@ -16,5 +12,5 @@ urlpatterns = patterns("symposion.schedule.views", url(r"^([\w\-]+)/list/$", "schedule_list", name="schedule_list"), url(r"^([\w\-]+)/presentations.csv$", "schedule_list_csv", name="schedule_list_csv"), url(r"^([\w\-]+)/edit/slot/(\d+)/", "schedule_slot_edit", name="schedule_slot_edit"), - url(r"^conference.json", cache_page(300)(schedule_json), name="schedule_json"), + url(r"^conference.json", "schedule_json", name="schedule_json"), ) diff --git a/symposion/schedule/views.py b/symposion/schedule/views.py index a4d65b65..aa043994 100644 --- a/symposion/schedule/views.py +++ b/symposion/schedule/views.py @@ -1,8 +1,13 @@ +from datetime import datetime +import json + +from django.core.urlresolvers import reverse from django.http import Http404, HttpResponse from django.shortcuts import render, get_object_or_404, redirect from django.template import loader, Context from django.contrib.auth.decorators import login_required +from django.contrib.sites.models import Site from symposion.schedule.forms import SlotEditForm from symposion.schedule.models import Schedule, Day, Slot, Presentation @@ -159,46 +164,39 @@ def schedule_presentation_detail(request, pk): def schedule_json(request): - everything = bool(request.GET.get('everything')) - slots = Slot.objects.all().order_by("start") + slots = Slot.objects.filter(day__schedule__published=True, day__schedule__hidden=False).order_by("start") + + protocol = request.META.get('HTTP_X_FORWARDED_PROTO', 'http') data = [] for slot in slots: - if slot.content: - slot_data = { + slot_data = { + "room": ", ".join(room["name"] for room in slot.rooms.values()), + "rooms": [room["name"] for room in slot.rooms.values()], + "start": datetime.combine(slot.day.date, slot.start).isoformat(), + "end": datetime.combine(slot.day.date, slot.end).isoformat(), + "duration": slot.length_in_minutes, + "kind": slot.kind.label, + "section": slot.day.schedule.section.slug, + } + if hasattr(slot.content, "proposal"): + slot_data.update({ "name": slot.content.title, - "room": ", ".join(room["name"] for room in slot.rooms.values()), - "start": slot.start_datetime.isoformat(), - "end": slot.end_datetime.isoformat(), - "duration": slot.length_in_minutes, "authors": [s.name for s in slot.content.speakers()], - "released": slot.content.proposal.recording_release, - # You may wish to change this... - "license": "All Rights Reserved", - "contact": - [s.email for s in slot.content.speakers()] - if request.user.is_staff - else ["redacted"], + "contact": [ + s.email for s in slot.content.speakers() + ] if request.user.is_staff else ["redacted"], "abstract": slot.content.abstract.raw, "description": slot.content.description.raw, - "conf_key": slot.content.pk, - "conf_url": "https://%s%s" % ( + "content_href": "%s://%s%s" % ( + protocol, Site.objects.get_current().domain, reverse("schedule_presentation_detail", args=[slot.content.pk]) ), - "kind": slot.kind.label, - "tags": "", - } - elif everything: - slot_data = { - "room": ", ".join(room["name"] for room in slot.rooms.values()), - "start": slot.start_datetime.isoformat(), - "end": slot.end_datetime.isoformat(), - "duration": slot.length_in_minutes, - "kind": slot.kind.label, - "title": slot.content_override.raw, - } + }) else: - continue + slot_data.update({ + "name": slot.content_override.raw if slot.content_override else "Slot", + }) data.append(slot_data) return HttpResponse( From 0ebcc2f1247ab9573afccab9436c39effaf063f4 Mon Sep 17 00:00:00 2001 From: Sheila Miguez Date: Sun, 21 Sep 2014 21:11:12 -0500 Subject: [PATCH 567/751] flake8 fixes --- symposion/schedule/models.py | 6 +++--- symposion/schedule/tests/factories.py | 9 +++++---- symposion/schedule/tests/test_views.py | 1 - symposion/schedule/views.py | 5 ++++- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/symposion/schedule/models.py b/symposion/schedule/models.py index 7f8d0e25..a47afb9d 100644 --- a/symposion/schedule/models.py +++ b/symposion/schedule/models.py @@ -102,7 +102,7 @@ class Slot(models.Model): self.day.date.day, self.start.hour, self.start.minute) - + @property def end_datetime(self): return datetime.datetime( @@ -111,12 +111,12 @@ class Slot(models.Model): self.day.date.day, self.end.hour, self.end.minute) - + @property def length_in_minutes(self): return int( (self.end_datetime - self.start_datetime).total_seconds() / 60) - + @property def rooms(self): return Room.objects.filter(pk__in=self.slotroom_set.values("room")) diff --git a/symposion/schedule/tests/factories.py b/symposion/schedule/tests/factories.py index f4304e9a..ccddb58c 100644 --- a/symposion/schedule/tests/factories.py +++ b/symposion/schedule/tests/factories.py @@ -11,8 +11,9 @@ from symposion.conference.models import Section, Conference class ConferenceFactory(factory.DjangoModelFactory): title = fuzzy.FuzzyText() start_date = fuzzy.FuzzyDate(datetime.date(2014, 1, 1)) - end_date = fuzzy.FuzzyDate(datetime.date(2014, 1, 1) + datetime.timedelta(days=random.randint(1,10))) - #timezone = TimeZoneField("UTC") + end_date = fuzzy.FuzzyDate(datetime.date(2014, 1, 1) + + datetime.timedelta(days=random.randint(1, 10))) + # timezone = TimeZoneField("UTC") class Meta: model = Conference @@ -55,8 +56,8 @@ class DayFactory(factory.DjangoModelFactory): class SlotFactory(factory.DjangoModelFactory): day = factory.SubFactory(DayFactory) kind = factory.SubFactory(SlotKindFactory) - start = datetime.time(random.randint(0,23), random.randint(0,59)) - end = datetime.time(random.randint(0,23), random.randint(0,59)) + start = datetime.time(random.randint(0, 23), random.randint(0, 59)) + end = datetime.time(random.randint(0, 23), random.randint(0, 59)) class Meta: model = Slot diff --git a/symposion/schedule/tests/test_views.py b/symposion/schedule/tests/test_views.py index 04e04efd..3bc2606b 100644 --- a/symposion/schedule/tests/test_views.py +++ b/symposion/schedule/tests/test_views.py @@ -17,7 +17,6 @@ class ScheduleViewTests(TestCase): assert 'schedule' in conference assert len(conference['schedule']) == 0 - def test_populated_empty_presentations(self): factories.SlotFactory.create_batch(size=5) diff --git a/symposion/schedule/views.py b/symposion/schedule/views.py index aa043994..a025834b 100644 --- a/symposion/schedule/views.py +++ b/symposion/schedule/views.py @@ -164,7 +164,10 @@ def schedule_presentation_detail(request, pk): def schedule_json(request): - slots = Slot.objects.filter(day__schedule__published=True, day__schedule__hidden=False).order_by("start") + slots = Slot.objects.filter( + day__schedule__published=True, + day__schedule__hidden=False + ).order_by("start") protocol = request.META.get('HTTP_X_FORWARDED_PROTO', 'http') data = [] From 2b91a7296ccf48fc33d0fbdbbbad8f60ad5c3d69 Mon Sep 17 00:00:00 2001 From: Sheila Miguez Date: Sun, 21 Sep 2014 21:25:18 -0500 Subject: [PATCH 568/751] added cancelled element to json, used taavis start/end props --- symposion/schedule/views.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/symposion/schedule/views.py b/symposion/schedule/views.py index a025834b..204254df 100644 --- a/symposion/schedule/views.py +++ b/symposion/schedule/views.py @@ -175,8 +175,8 @@ def schedule_json(request): slot_data = { "room": ", ".join(room["name"] for room in slot.rooms.values()), "rooms": [room["name"] for room in slot.rooms.values()], - "start": datetime.combine(slot.day.date, slot.start).isoformat(), - "end": datetime.combine(slot.day.date, slot.end).isoformat(), + "start": slot.start_datetime.isoformat(), + "end": slot.end_datetime.isoformat(), "duration": slot.length_in_minutes, "kind": slot.kind.label, "section": slot.day.schedule.section.slug, @@ -195,6 +195,7 @@ def schedule_json(request): Site.objects.get_current().domain, reverse("schedule_presentation_detail", args=[slot.content.pk]) ), + "cancelled": slot.content.cancelled, }) else: slot_data.update({ From 40a55c24c7c7ca52bdd0ff547793efa06672631c Mon Sep 17 00:00:00 2001 From: Sheila Miguez Date: Sun, 21 Sep 2014 21:26:19 -0500 Subject: [PATCH 569/751] flake8 fix --- symposion/schedule/views.py | 1 - 1 file changed, 1 deletion(-) diff --git a/symposion/schedule/views.py b/symposion/schedule/views.py index 204254df..b03aa28a 100644 --- a/symposion/schedule/views.py +++ b/symposion/schedule/views.py @@ -1,4 +1,3 @@ -from datetime import datetime import json from django.core.urlresolvers import reverse From 2c97ec7106623433e1f313fbf5ff61561b3485f4 Mon Sep 17 00:00:00 2001 From: Sheila Miguez Date: Sun, 28 Sep 2014 12:52:41 -0500 Subject: [PATCH 570/751] adds placeholder values for future model changes to ProposalBase" --- symposion/schedule/views.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/symposion/schedule/views.py b/symposion/schedule/views.py index b03aa28a..03fd17e8 100644 --- a/symposion/schedule/views.py +++ b/symposion/schedule/views.py @@ -179,6 +179,17 @@ def schedule_json(request): "duration": slot.length_in_minutes, "kind": slot.kind.label, "section": slot.day.schedule.section.slug, + "conf_key": slot.pk, + # TODO: models should be changed. + # these are model features from other conferences that have forked symposion + # these have been used almost everywhere and are good candidates for + # base proposals + "license": "CC BY", + "tags": "", + "released": True, + "contact": [], + + } if hasattr(slot.content, "proposal"): slot_data.update({ @@ -189,7 +200,7 @@ def schedule_json(request): ] if request.user.is_staff else ["redacted"], "abstract": slot.content.abstract.raw, "description": slot.content.description.raw, - "content_href": "%s://%s%s" % ( + "conf_url": "%s://%s%s" % ( protocol, Site.objects.get_current().domain, reverse("schedule_presentation_detail", args=[slot.content.pk]) From 5e4a6e440784c2213919cfd218bf2623081bd0e8 Mon Sep 17 00:00:00 2001 From: Patrick Altman Date: Mon, 15 Dec 2014 15:29:17 -0600 Subject: [PATCH 571/751] Use sha1 instead of sha256 for compatibility --- symposion/proposals/views.py | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/symposion/proposals/views.py b/symposion/proposals/views.py index 8fcbfd09..878d17ce 100644 --- a/symposion/proposals/views.py +++ b/symposion/proposals/views.py @@ -7,18 +7,10 @@ from django.core.urlresolvers import reverse from django.db.models import Q from django.http import Http404, HttpResponse, HttpResponseForbidden from django.shortcuts import render, redirect, get_object_or_404 -<<<<<<< HEAD - -try: - from hashlib import sha1 as sha_constructor -except ImportError: - from django.utils.hashcompat import sha_constructor - -======= -from hashlib import sha256 ->>>>>>> fb26371362751584eb8352d06a840b914d24e3fc from django.views import static +from hashlib import sha1 + from django.contrib import messages from django.contrib.auth.models import User from django.contrib.auth.decorators import login_required @@ -131,8 +123,8 @@ def proposal_speaker_manage(request, pk): Q(user=None, invite_email=email_address) ) except Speaker.DoesNotExist: - salt = sha256(str(random.random())).hexdigest()[:5] - token = sha256(salt + email_address).hexdigest() + salt = sha1(str(random.random())).hexdigest()[:5] + token = sha1(salt + email_address).hexdigest() pending = Speaker.objects.create( invite_email=email_address, invite_token=token, From 894e7b1b2e65b3b9d36ec981c39a3352826b75de Mon Sep 17 00:00:00 2001 From: Patrick Altman Date: Mon, 15 Dec 2014 16:20:05 -0600 Subject: [PATCH 572/751] Upgrade requirements --- requirements-test.txt | 11 ----------- requirements/base.txt | 23 ++++++++++++----------- 2 files changed, 12 insertions(+), 22 deletions(-) delete mode 100644 requirements-test.txt diff --git a/requirements-test.txt b/requirements-test.txt deleted file mode 100644 index 2ebbf3b6..00000000 --- a/requirements-test.txt +++ /dev/null @@ -1,11 +0,0 @@ -Django==1.4.15 -Pillow==2.5.3 -django-discover-runner==1.0 -django-markitup==2.2.2 -django-model-utils==1.5.0 -django-nose==1.2 -django-reversion==1.8.0 -django-timezones==0.2 -factory-boy==2.4.1 -nose==1.3.4 -pytz==2014.7 diff --git a/requirements/base.txt b/requirements/base.txt index b503a4ba..fd5fd582 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -1,13 +1,14 @@ -Django>=1.5,<=1.6 -django-appconf==0.5 +Django>=1.6,<=1.7.1 +django-appconf==0.6 django-forms-bootstrap>=2.0.3.post2 -django-markitup==2.1 -django-model-utils==2.0.2 -django-reversion==1.8 -django-sitetree==1.0.0 -django-taggit==0.11.2 +django-markitup==2.2.2 +django-model-utils==2.2 +django-reversion==1.8.5 +django-sitetree==1.2.1 +django-taggit==0.12.2 django-timezones==0.2 -django-user-accounts==1.0b13 -easy-thumbnails==1.4 -html5lib==0.95 -markdown==2.3.1 +django-user-accounts==1.0 +easy-thumbnails==2.2 +html5lib==0.999 +markdown==2.5.2 +pytz==2014.10 From aeb784b3cda5e0040a60fd1905896086d2d2d178 Mon Sep 17 00:00:00 2001 From: Patrick Altman Date: Mon, 15 Dec 2014 16:25:00 -0600 Subject: [PATCH 573/751] Add tox file and clean up a lint error --- .travis.yml | 2 +- symposion/boxes/utils.py | 2 +- tox.ini | 3 +++ 3 files changed, 5 insertions(+), 2 deletions(-) create mode 100644 tox.ini diff --git a/.travis.yml b/.travis.yml index 44782e61..a62fc3cf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,4 +8,4 @@ install: - pip install -e . script: - - flake8 --max-line-length=100 --max-complexity=24 --statistics --benchmark symposion + - flake8 symposion diff --git a/symposion/boxes/utils.py b/symposion/boxes/utils.py index 24b95c69..3d2b4c76 100644 --- a/symposion/boxes/utils.py +++ b/symposion/boxes/utils.py @@ -7,7 +7,7 @@ except ImportError: def load_path_attr(path): i = path.rfind(".") - module, attr = path[:i], path[i+1:] + module, attr = path[:i], path[i + 1:] try: mod = import_module(module) except ImportError, e: diff --git a/tox.ini b/tox.ini new file mode 100644 index 00000000..db9b7171 --- /dev/null +++ b/tox.ini @@ -0,0 +1,3 @@ +[flake8] +ignore = E501,E265 +exclude = migrations From 8d415ef1dde94979f0dd2bfdee267b8b451e4654 Mon Sep 17 00:00:00 2001 From: Sheila Miguez Date: Wed, 27 Aug 2014 16:14:41 -0500 Subject: [PATCH 574/751] updating docs, add content management section --- docs/content.rst | 40 ++++++++++++++++++++++++++++++++++++++++ docs/index.rst | 25 ++++++++++++++----------- 2 files changed, 54 insertions(+), 11 deletions(-) create mode 100644 docs/content.rst diff --git a/docs/content.rst b/docs/content.rst new file mode 100644 index 00000000..59c0b3ef --- /dev/null +++ b/docs/content.rst @@ -0,0 +1,40 @@ +Content Management +================== + +The content management system allows organizers to create pages and page +sections for a conference. You may want to have an entire page about a job +fair, or may only want to have an editable section at the top of a tutorial +schedule with some instructions for all of the tutorial attendees. + +CMS App +------- + +The ``cms`` app provides functionality for creating wiki pages. These pages can +be created using the django admin. The django admin form has controls for +specifying: + +* title +* markup content +* url path +* tags +* public or draft mode +* publication date + +Page content and title can also be edited directly at the url. The ``cms`` app +uses the `django-reversion `_ package, +thus content is version controlled. + +Boxes App +--------- + +The ``boxes`` app allows for sections of a page to be edited like a wiki. To use this in a template +use the ``boxes_tags`` and specify a box section of a page using the ``boxes`` tag: + +.. code-block:: django + + {% load boxes_tags %} + {% boxes "intro_section" %} + +This template will render an editable content box. When a staff user visits the +page, they will see an ``Edit this content`` button. The ``boxes`` app also uses the +``django-reversion`` package. diff --git a/docs/index.rst b/docs/index.rst index 732670ca..0b7abda1 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,27 +1,30 @@ Pinax Symposion =============== -Pinax Symposion is an open-source Django project for conference websites. +Pinax Symposion is an open-source conference management system written in +Django. Symposion includes support for content management, proposal +submission, reviews, scheduling and sponsor management. -It came out of development done by Eldarion for DjangoCon US and US PyCon -but has been independently used for a number of other conferences. - -We are in the process of cleaning things up and making them more generic. - -The project homepage is http://eldarion.com/symposion/ - -Apps: .. toctree:: :maxdepth: 2 - + + project conference + content + proposals sponsorship speakers - proposals schedule +About +----- + +Symposion came out of development done by Eldarion for DjangoCon US and US PyCon +but has been independently used for a number of other conferences. +The project homepage is http://eldarion.com/symposion/ + Indices and tables ================== From 450bf3f5e4d1247088899b24d1a24ea637a1d0fe Mon Sep 17 00:00:00 2001 From: Sheila Miguez Date: Tue, 16 Dec 2014 20:32:09 -0600 Subject: [PATCH 575/751] adds a starting page about the example project --- docs/project.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 docs/project.rst diff --git a/docs/project.rst b/docs/project.rst new file mode 100644 index 00000000..8a59b8db --- /dev/null +++ b/docs/project.rst @@ -0,0 +1,10 @@ +Project Template +================ + +The `pinax-project-symposion `_ repository +is a starter project demonstrating how to create a minimal symposion instance. + +TODO: + * initial data + * overriding templates + * deployment From 60e7095cacb3b02de4dff2e84c87322d6228f827 Mon Sep 17 00:00:00 2001 From: Patrick Altman Date: Thu, 18 Dec 2014 08:08:56 -0600 Subject: [PATCH 576/751] Add .egg-info to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index cd160223..2b2e626e 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ _build *.pyc dev.db site_media +*.egg-info \ No newline at end of file From 5d8b09231970a28b1c25546b8d18cc7a48dddd9b Mon Sep 17 00:00:00 2001 From: Patrick Altman Date: Thu, 18 Dec 2014 08:09:26 -0600 Subject: [PATCH 577/751] Just focus on Django 1.7+ --- requirements/base.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/base.txt b/requirements/base.txt index fd5fd582..f435a716 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -1,4 +1,4 @@ -Django>=1.6,<=1.7.1 +Django>=1.7.1 django-appconf==0.6 django-forms-bootstrap>=2.0.3.post2 django-markitup==2.2.2 From ba1df47d695b9349f876105f1887cbcba33f7d1a Mon Sep 17 00:00:00 2001 From: Patrick Altman Date: Thu, 18 Dec 2014 08:13:10 -0600 Subject: [PATCH 578/751] Remove templates These have all been moved to the starter project, pinax-project-symposion. The reasoning behind this is they are very specific to the theme (pinax-theme-bootstrap) that the project uses and not really all that reusable when packaged with this app. --- requirements/base.txt | 1 - symposion/templates/boxes/box.html | 32 --- symposion/templates/cms/file_create.html | 20 -- symposion/templates/cms/file_index.html | 30 --- symposion/templates/cms/page_detail.html | 27 --- symposion/templates/cms/page_edit.html | 22 -- symposion/templates/conference/user_list.html | 106 --------- symposion/templates/dashboard.html | 221 ------------------ .../emails/proposal_new_message/message.html | 11 - .../emails/proposal_new_message/subject.txt | 1 - .../emails/proposal_updated/message.html | 8 - .../emails/proposal_updated/subject.txt | 1 - .../emails/speaker_addition/message.html | 6 - .../emails/speaker_addition/subject.txt | 1 - .../emails/speaker_invite/message.html | 10 - .../emails/speaker_invite/subject.txt | 1 - .../emails/speaker_no_profile/message.html | 9 - .../emails/speaker_no_profile/subject.txt | 1 - .../emails/sponsor_signup/message.html | 9 - .../emails/sponsor_signup/subject.txt | 1 - .../emails/teams_user_applied/message.html | 12 - .../emails/teams_user_applied/subject.txt | 1 - .../emails/teams_user_invited/message.html | 12 - .../emails/teams_user_invited/subject.txt | 1 - .../proposals/_pending_proposal_row.html | 42 ---- .../templates/proposals/_proposal_fields.html | 60 ----- .../templates/proposals/_proposal_row.html | 32 --- symposion/templates/proposals/base.html | 5 - .../templates/proposals/document_create.html | 17 -- .../templates/proposals/proposal_cancel.html | 18 -- .../templates/proposals/proposal_detail.html | 120 ---------- .../templates/proposals/proposal_edit.html | 23 -- .../templates/proposals/proposal_leave.html | 11 - .../proposals/proposal_speaker_manage.html | 41 ---- .../templates/proposals/proposal_submit.html | 22 -- .../proposals/proposal_submit_kind.html | 27 --- .../_result_notification_prepare_help.html | 7 - .../templates/reviews/_review_table.html | 37 --- .../reviews/access_not_permitted.html | 10 - symposion/templates/reviews/base.html | 117 ---------- .../reviews/result_notification.html | 153 ------------ .../reviews/result_notification_prepare.html | 49 ---- symposion/templates/reviews/review_admin.html | 56 ----- .../templates/reviews/review_assignment.html | 32 --- .../templates/reviews/review_bulk_accept.html | 15 -- .../templates/reviews/review_comment.html | 2 - .../templates/reviews/review_detail.html | 198 ---------------- symposion/templates/reviews/review_list.html | 14 -- .../templates/reviews/review_review.html | 80 ------- symposion/templates/reviews/review_stats.html | 82 ------- symposion/templates/schedule/_edit_grid.html | 39 ---- symposion/templates/schedule/_grid.html | 41 ---- symposion/templates/schedule/_slot_edit.html | 14 -- .../schedule/presentation_detail.html | 35 --- .../schedule/schedule_conference.html | 38 --- .../templates/schedule/schedule_detail.html | 37 --- .../templates/schedule/schedule_edit.html | 63 ----- .../templates/schedule/schedule_list.html | 51 ---- symposion/templates/sitetree/breadcrumbs.html | 17 -- symposion/templates/sitetree/menu.html | 16 -- symposion/templates/sitetree/submenu.html | 8 - symposion/templates/sitetree/tree.html | 15 -- symposion/templates/speakers/base.html | 1 - .../templates/speakers/speaker_create.html | 23 -- .../templates/speakers/speaker_edit.html | 23 -- .../templates/speakers/speaker_profile.html | 41 ---- .../sponsorship/_horizontal_by_level.html | 11 - .../templates/sponsorship/_sponsor_link.html | 10 - .../sponsorship/_vertical_by_level.html | 13 -- symposion/templates/sponsorship/_wall.html | 7 - symposion/templates/sponsorship/add.html | 22 -- symposion/templates/sponsorship/apply.html | 28 --- symposion/templates/sponsorship/detail.html | 40 ---- symposion/templates/sponsorship/list.html | 44 ---- symposion/templates/teams/team_detail.html | 103 -------- 75 files changed, 2554 deletions(-) delete mode 100644 symposion/templates/boxes/box.html delete mode 100644 symposion/templates/cms/file_create.html delete mode 100644 symposion/templates/cms/file_index.html delete mode 100644 symposion/templates/cms/page_detail.html delete mode 100644 symposion/templates/cms/page_edit.html delete mode 100644 symposion/templates/conference/user_list.html delete mode 100644 symposion/templates/dashboard.html delete mode 100644 symposion/templates/emails/proposal_new_message/message.html delete mode 100644 symposion/templates/emails/proposal_new_message/subject.txt delete mode 100644 symposion/templates/emails/proposal_updated/message.html delete mode 100644 symposion/templates/emails/proposal_updated/subject.txt delete mode 100644 symposion/templates/emails/speaker_addition/message.html delete mode 100644 symposion/templates/emails/speaker_addition/subject.txt delete mode 100644 symposion/templates/emails/speaker_invite/message.html delete mode 100644 symposion/templates/emails/speaker_invite/subject.txt delete mode 100644 symposion/templates/emails/speaker_no_profile/message.html delete mode 100644 symposion/templates/emails/speaker_no_profile/subject.txt delete mode 100644 symposion/templates/emails/sponsor_signup/message.html delete mode 100644 symposion/templates/emails/sponsor_signup/subject.txt delete mode 100644 symposion/templates/emails/teams_user_applied/message.html delete mode 100644 symposion/templates/emails/teams_user_applied/subject.txt delete mode 100644 symposion/templates/emails/teams_user_invited/message.html delete mode 100644 symposion/templates/emails/teams_user_invited/subject.txt delete mode 100644 symposion/templates/proposals/_pending_proposal_row.html delete mode 100644 symposion/templates/proposals/_proposal_fields.html delete mode 100644 symposion/templates/proposals/_proposal_row.html delete mode 100644 symposion/templates/proposals/base.html delete mode 100644 symposion/templates/proposals/document_create.html delete mode 100644 symposion/templates/proposals/proposal_cancel.html delete mode 100644 symposion/templates/proposals/proposal_detail.html delete mode 100644 symposion/templates/proposals/proposal_edit.html delete mode 100644 symposion/templates/proposals/proposal_leave.html delete mode 100644 symposion/templates/proposals/proposal_speaker_manage.html delete mode 100644 symposion/templates/proposals/proposal_submit.html delete mode 100644 symposion/templates/proposals/proposal_submit_kind.html delete mode 100644 symposion/templates/reviews/_result_notification_prepare_help.html delete mode 100644 symposion/templates/reviews/_review_table.html delete mode 100644 symposion/templates/reviews/access_not_permitted.html delete mode 100644 symposion/templates/reviews/base.html delete mode 100644 symposion/templates/reviews/result_notification.html delete mode 100644 symposion/templates/reviews/result_notification_prepare.html delete mode 100644 symposion/templates/reviews/review_admin.html delete mode 100644 symposion/templates/reviews/review_assignment.html delete mode 100644 symposion/templates/reviews/review_bulk_accept.html delete mode 100644 symposion/templates/reviews/review_comment.html delete mode 100644 symposion/templates/reviews/review_detail.html delete mode 100644 symposion/templates/reviews/review_list.html delete mode 100644 symposion/templates/reviews/review_review.html delete mode 100644 symposion/templates/reviews/review_stats.html delete mode 100644 symposion/templates/schedule/_edit_grid.html delete mode 100644 symposion/templates/schedule/_grid.html delete mode 100644 symposion/templates/schedule/_slot_edit.html delete mode 100644 symposion/templates/schedule/presentation_detail.html delete mode 100644 symposion/templates/schedule/schedule_conference.html delete mode 100644 symposion/templates/schedule/schedule_detail.html delete mode 100644 symposion/templates/schedule/schedule_edit.html delete mode 100644 symposion/templates/schedule/schedule_list.html delete mode 100644 symposion/templates/sitetree/breadcrumbs.html delete mode 100644 symposion/templates/sitetree/menu.html delete mode 100644 symposion/templates/sitetree/submenu.html delete mode 100644 symposion/templates/sitetree/tree.html delete mode 100644 symposion/templates/speakers/base.html delete mode 100644 symposion/templates/speakers/speaker_create.html delete mode 100644 symposion/templates/speakers/speaker_edit.html delete mode 100644 symposion/templates/speakers/speaker_profile.html delete mode 100644 symposion/templates/sponsorship/_horizontal_by_level.html delete mode 100644 symposion/templates/sponsorship/_sponsor_link.html delete mode 100644 symposion/templates/sponsorship/_vertical_by_level.html delete mode 100644 symposion/templates/sponsorship/_wall.html delete mode 100644 symposion/templates/sponsorship/add.html delete mode 100644 symposion/templates/sponsorship/apply.html delete mode 100644 symposion/templates/sponsorship/detail.html delete mode 100644 symposion/templates/sponsorship/list.html delete mode 100644 symposion/templates/teams/team_detail.html diff --git a/requirements/base.txt b/requirements/base.txt index f435a716..8cd92478 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -1,6 +1,5 @@ Django>=1.7.1 django-appconf==0.6 -django-forms-bootstrap>=2.0.3.post2 django-markitup==2.2.2 django-model-utils==2.2 django-reversion==1.8.5 diff --git a/symposion/templates/boxes/box.html b/symposion/templates/boxes/box.html deleted file mode 100644 index f4a33538..00000000 --- a/symposion/templates/boxes/box.html +++ /dev/null @@ -1,32 +0,0 @@ -{% load markitup_tags %} -{% load i18n %} - -{% if form %} - -{% endif %} - -
    - {% if form %} - Edit this content - {% endif %} - {{ box.content|safe }} -
    diff --git a/symposion/templates/cms/file_create.html b/symposion/templates/cms/file_create.html deleted file mode 100644 index 07f449c9..00000000 --- a/symposion/templates/cms/file_create.html +++ /dev/null @@ -1,20 +0,0 @@ -{% extends "site_base.html" %} - -{% load bootstrap_tags %} - -{% block head_title %}Upload File{% endblock %} - -{% block body_outer %} -
    -
    -

    Upload File

    -
    - {% csrf_token %} - {{ form|as_bootstrap }} -
    - -
    -
    -
    -
    -{% endblock %} diff --git a/symposion/templates/cms/file_index.html b/symposion/templates/cms/file_index.html deleted file mode 100644 index e9f367f0..00000000 --- a/symposion/templates/cms/file_index.html +++ /dev/null @@ -1,30 +0,0 @@ -{% extends "site_base.html" %} - -{% block head_title %}Uploaded Files{% endblock %} - -{% block body_outer %} -
    -
    -

    Files

    - - {% for file in files %} -
    -
    - {% csrf_token %} - -
    -

    {{ file.file }}

    - Uploaded {{ file.created|date:"N j, Y" }} -
    - {% empty %} -

    No uploaded files.

    - {% endfor %} - -
    -
    -{% endblock %} diff --git a/symposion/templates/cms/page_detail.html b/symposion/templates/cms/page_detail.html deleted file mode 100644 index 6f35395b..00000000 --- a/symposion/templates/cms/page_detail.html +++ /dev/null @@ -1,27 +0,0 @@ -{% extends "site_base.html" %} - -{% load url from future %} - - -{% load sitetree %} -{% load i18n %} - -{% block body_class %}cms-page{% endblock %} - -{% block head_title %}{{ page.title }}{% endblock %} - -{% block breadcrumbs %}{% sitetree_breadcrumbs from "main" %}{% endblock %} - -{% block body %} - {% if editable %} - - {% endif %} -

    {{ page.title }}

    - -
    - {{ page.body }} -
    - -{% endblock %} \ No newline at end of file diff --git a/symposion/templates/cms/page_edit.html b/symposion/templates/cms/page_edit.html deleted file mode 100644 index e361ecdf..00000000 --- a/symposion/templates/cms/page_edit.html +++ /dev/null @@ -1,22 +0,0 @@ -{% extends "site_base.html" %} - -{% load sitetree %} -{% load i18n %} -{% load bootstrap_tags %} - -{% block body_class %}cms-page{% endblock %} - -{% block head_title %}Create Page{% endblock %} - -{% block page_title %}{% trans "Edit page at:" %} {{ path }}{% endblock %} -{% block breadcrumbs %}{% sitetree_breadcrumbs from "main" %}{% endblock %} - -{% block body %} -
    - {% csrf_token %} - {{ form|as_bootstrap }} -
    - -
    -
    -{% endblock %} \ No newline at end of file diff --git a/symposion/templates/conference/user_list.html b/symposion/templates/conference/user_list.html deleted file mode 100644 index 9886fde1..00000000 --- a/symposion/templates/conference/user_list.html +++ /dev/null @@ -1,106 +0,0 @@ -{% extends "site_base.html" %} - -{% load i18n %} -{% load sitetree %} - -{% block head_title %}User List{% endblock %} - -{% block extra_style %} - -{% endblock %} - -{% block body_outer %} -
    -
    -

    User List

    - - - - - - - - - {% for user in users %} - - - - - - {% endfor %} - -
    {% trans "Email" %}{% trans "Name" %}{% trans "Speaker Profile?" %}
    {{ user.email }}{{ user.get_full_name }} - {% if user.speaker_profile %} - {{ user.speaker_profile }} - {% else %} - create - {% endif %} -
    -
    -
    -{% endblock %} - -{% block extra_script %} - - - - -{% endblock %} diff --git a/symposion/templates/dashboard.html b/symposion/templates/dashboard.html deleted file mode 100644 index f4ef8775..00000000 --- a/symposion/templates/dashboard.html +++ /dev/null @@ -1,221 +0,0 @@ -{% extends "site_base.html" %} - -{% load i18n %} -{% load proposal_tags %} -{% load review_tags %} -{% load teams_tags %} - -{% block head_title %}Dashboard{% endblock %} - -{% block body_class %}auth{% endblock %} - -{% block body %} -
    -
    - -

    {% trans "Speaking" %}

    -
    - {% if not user.speaker_profile %} - - Create a speaker profile - - {% else %} - - Edit your speaker profile - - - Submit a new proposal - - {% endif %} -
    -
    - -
    - {% if not user.speaker_profile %} -

    To submit a proposal, you must first create a speaker profile.

    - {% else %} -

    Your Proposals

    - {% if user.speaker_profile.proposals.exists %} - - - - - - - - {% for proposal in user.speaker_profile.proposals.all %} - {% include "proposals/_proposal_row.html" %} - {% endfor %} -
    TitleSession typeStatusActions
    - {% else %} -

    No proposals submitted yet.

    - {% endif %} - - {% associated_proposals as associated_proposals %} - {% if associated_proposals %} -

    Proposals you have joined as an additional speaker

    - - - - - - - - {% for proposal in associated_proposals %} - {% include "proposals/_proposal_row.html" %} - {% endfor %} -
    TitleSession typeStatusActions
    - {% endif %} - - {% pending_proposals as pending_proposals %} - {% if pending_proposals %} -

    Proposals you have been invited to join

    - - - - - - - - {% for proposal in pending_proposals %} - {% include "proposals/_pending_proposal_row.html" %} - {% endfor %} -
    TitleSession typeStatusActions
    - {% endif %} - {% endif %} -
    -
    - -
    -
    - -

    {% trans "Sponsorship" %}

    -
    - {% if not user.sponsorships.exists %} - - Apply to be a sponsor - - {% endif %} -
    -
    - -
    - {% if not user.sponsorships.exists %} -

    If you or your organization would be interested in sponsorship opportunities, use our online form to apply to be a sponsor. - {% else %} -

    Your Sponsorship

    -
      - {% for sponsorship in user.sponsorships.all %} -
    • - {{ sponsorship.name }} - ({{ sponsorship.level }}) - {% if not sponsorship.active %} - awaiting approval - {% endif %} -
    • - {% endfor %} -
    - {% endif %} - {% if user.is_staff %} -

    - As staff, you can directly add a sponsor if the organization isn't - applying themselves. -

    - {% endif %} -
    -
    - - {% if review_sections %} -
    -
    - -

    {% trans "Reviews" %}

    -
    - -
    -

    Reviews by Section

    - - - - {% comment %} -

    My Assignments

    - - - - - - - - - - - - - - - -
    Proposal TitleScore
    Title Three-1
    Title Four+2
    - {% endcomment %} - -
    -
    - {% endif %} - - {% available_teams as available_teams %} - {% if user.memberships.exists or available_teams %} -
    -
    - -

    {% trans "Teams" %}

    -
    - -
    - {% if user.memberships.exists %} -

    Your Teams

    - - {% for membership in user.memberships.all %} - - - - - - {% endfor %} -
    - {{ membership.team.name }} - {% if membership.team.description %}
    {{ membership.team.description }}{% endif %} -
    - {{ membership.get_state_display }} - - {% if membership.state == "manager" or user.is_staff %} - {% if membership.team.applicants %}{{ membership.team.applicants.count }} applicant{{ membership.team.applicants.count|pluralize }}{% endif %} - {% endif %} -
    - {% endif %} - {% if available_teams %} -

    Available Teams

    - - {% for team in available_teams %} - - - - - - {% endfor %} -
    - {{ team }} - {% if team.description %}
    {{ team.description }}{% endif %} -
    - {{ team.get_access_display }} -
    - {% endif %} -
    -
    - {% endif %} -{% endblock %} diff --git a/symposion/templates/emails/proposal_new_message/message.html b/symposion/templates/emails/proposal_new_message/message.html deleted file mode 100644 index 42c55ac2..00000000 --- a/symposion/templates/emails/proposal_new_message/message.html +++ /dev/null @@ -1,11 +0,0 @@ -{% load account_tags %} -

    - {% user_display message.user %} has added a message on {{ proposal.title }}. -

    -
    - {{ message.message|safe }} -
    -

    - {% if reviewer %}{% url "review_detail" proposal.pk as detail_url %}{% else %}{% url "proposal_detail" proposal.pk as detail_url %}{% endif %} - Respond online at http://{{ current_site }}{{ detail_url }}#proposal-feedback -

    diff --git a/symposion/templates/emails/proposal_new_message/subject.txt b/symposion/templates/emails/proposal_new_message/subject.txt deleted file mode 100644 index 4d5d805d..00000000 --- a/symposion/templates/emails/proposal_new_message/subject.txt +++ /dev/null @@ -1 +0,0 @@ -{% load account_tags %}New message on "{{ proposal.title }}" from {% user_display message.user %} \ No newline at end of file diff --git a/symposion/templates/emails/proposal_updated/message.html b/symposion/templates/emails/proposal_updated/message.html deleted file mode 100644 index e20ed4f0..00000000 --- a/symposion/templates/emails/proposal_updated/message.html +++ /dev/null @@ -1,8 +0,0 @@ -{% load account_tags %} -

    - {% user_display user %} has made changes to {{ proposal.title }} which you have previously reviewed or commented on. -

    -

    - {% url "review_detail" proposal.pk as detail_url %} - View the latest version of the proposal online at http://{{ current_site }}{{ detail_url }} -

    diff --git a/symposion/templates/emails/proposal_updated/subject.txt b/symposion/templates/emails/proposal_updated/subject.txt deleted file mode 100644 index ba9117c1..00000000 --- a/symposion/templates/emails/proposal_updated/subject.txt +++ /dev/null @@ -1 +0,0 @@ -{% load account_tags %}"{{ proposal.title }}" has been updated by {% user_display user %} \ No newline at end of file diff --git a/symposion/templates/emails/speaker_addition/message.html b/symposion/templates/emails/speaker_addition/message.html deleted file mode 100644 index 1d01ab7e..00000000 --- a/symposion/templates/emails/speaker_addition/message.html +++ /dev/null @@ -1,6 +0,0 @@ -

    {{ proposal.speaker.name }} attached you as an additional speaker to a - talk proposal for {{ current_site.name }} entitled "{{ proposal.title }}".

    - -

    For more details, visit the {{ current_site.name }} speaker dashboard: - http://{{ current_site }}{% url "dashboard" %} -

    diff --git a/symposion/templates/emails/speaker_addition/subject.txt b/symposion/templates/emails/speaker_addition/subject.txt deleted file mode 100644 index 778d07e9..00000000 --- a/symposion/templates/emails/speaker_addition/subject.txt +++ /dev/null @@ -1 +0,0 @@ -{{ proposal.speaker.name }} has invited you to join as a speaker on {{ proposal.title }} diff --git a/symposion/templates/emails/speaker_invite/message.html b/symposion/templates/emails/speaker_invite/message.html deleted file mode 100644 index 57cea710..00000000 --- a/symposion/templates/emails/speaker_invite/message.html +++ /dev/null @@ -1,10 +0,0 @@ -

    {{ proposal.speaker.name }} attached you as an additional speaker to a - talk proposal for {{ current_site.name }} entitled "{{ proposal.title }}".

    - -

    Go to

    - -

    http://{{ current_site }}{% url "speaker_create_token" token %}

    - -

    to confirm.

    - -

    If you don't have account on the website, you will be asked to create one.

    diff --git a/symposion/templates/emails/speaker_invite/subject.txt b/symposion/templates/emails/speaker_invite/subject.txt deleted file mode 100644 index 778d07e9..00000000 --- a/symposion/templates/emails/speaker_invite/subject.txt +++ /dev/null @@ -1 +0,0 @@ -{{ proposal.speaker.name }} has invited you to join as a speaker on {{ proposal.title }} diff --git a/symposion/templates/emails/speaker_no_profile/message.html b/symposion/templates/emails/speaker_no_profile/message.html deleted file mode 100644 index d77693c1..00000000 --- a/symposion/templates/emails/speaker_no_profile/message.html +++ /dev/null @@ -1,9 +0,0 @@ -{% load url from future %} -

    {{ proposal.speaker.name }} attached you as an additional speaker to a - talk proposal for {{ current_site.name }} entitled "{{ proposal.title }}".

    - -

    Go to

    - -

    http://{{ current_site }}{% url 'speaker_create_token' token %}

    - -

    to confirm and fill out your speaker profile.

    diff --git a/symposion/templates/emails/speaker_no_profile/subject.txt b/symposion/templates/emails/speaker_no_profile/subject.txt deleted file mode 100644 index 778d07e9..00000000 --- a/symposion/templates/emails/speaker_no_profile/subject.txt +++ /dev/null @@ -1 +0,0 @@ -{{ proposal.speaker.name }} has invited you to join as a speaker on {{ proposal.title }} diff --git a/symposion/templates/emails/sponsor_signup/message.html b/symposion/templates/emails/sponsor_signup/message.html deleted file mode 100644 index 8d982a64..00000000 --- a/symposion/templates/emails/sponsor_signup/message.html +++ /dev/null @@ -1,9 +0,0 @@ -

    {{ sponsor.name }} has applied to be a sponsor.

    - -
      -
    • Applicant: {{ sponsor.applicant }}
    • -
    • Sponsor Name: {{ sponsor.name }}
    • -
    • Sponsor Contact: {{ sponsor.contact_name }}
    • -
    • Sponsor Contact Email: {{ sponsor.contact_email }}
    • -
    • Sponsorship Level: {{ sponsor.level }}
    • -
    diff --git a/symposion/templates/emails/sponsor_signup/subject.txt b/symposion/templates/emails/sponsor_signup/subject.txt deleted file mode 100644 index 4e65099f..00000000 --- a/symposion/templates/emails/sponsor_signup/subject.txt +++ /dev/null @@ -1 +0,0 @@ -New Sponsor Application \ No newline at end of file diff --git a/symposion/templates/emails/teams_user_applied/message.html b/symposion/templates/emails/teams_user_applied/message.html deleted file mode 100644 index b05d6c8b..00000000 --- a/symposion/templates/emails/teams_user_applied/message.html +++ /dev/null @@ -1,12 +0,0 @@ -{% load i18n account_tags %} -{% user_display user as username %} -{% blocktrans with team_name=team team_url=team.get_absolute_url site_name=current_site.name site_url=current_site %} -

    - User "{{ username }}" has applied to join {{ team_name }} on {{ site_name }}. -

    - -

    - To accept this application and see any other pending applications, visit the following url: - http://{{ site_url }}{{ team_url }} -

    -{% endblocktrans %} diff --git a/symposion/templates/emails/teams_user_applied/subject.txt b/symposion/templates/emails/teams_user_applied/subject.txt deleted file mode 100644 index 84b29384..00000000 --- a/symposion/templates/emails/teams_user_applied/subject.txt +++ /dev/null @@ -1 +0,0 @@ -{% load i18n account_tags %}{% user_display user as username %}{% blocktrans %}{{ username}} has applied to to join "{{ team }}"{% endblocktrans %} \ No newline at end of file diff --git a/symposion/templates/emails/teams_user_invited/message.html b/symposion/templates/emails/teams_user_invited/message.html deleted file mode 100644 index b498d247..00000000 --- a/symposion/templates/emails/teams_user_invited/message.html +++ /dev/null @@ -1,12 +0,0 @@ -{% load i18n account_tags %} - -{% blocktrans with team_name=team team_url=team.get_absolute_url site_name=current_site.name site_url=current_site %} -

    - You have been invited to join {{ team_name }} on {{ site_name }}. -

    - -

    - To accept this invitation, visit the following url: - http://{{ site_url }}{{ team_url }} -

    -{% endblocktrans %} \ No newline at end of file diff --git a/symposion/templates/emails/teams_user_invited/subject.txt b/symposion/templates/emails/teams_user_invited/subject.txt deleted file mode 100644 index 434412bc..00000000 --- a/symposion/templates/emails/teams_user_invited/subject.txt +++ /dev/null @@ -1 +0,0 @@ -{% load i18n %}{% blocktrans %}You have been invited to join "{{ team }}"{% endblocktrans %} \ No newline at end of file diff --git a/symposion/templates/proposals/_pending_proposal_row.html b/symposion/templates/proposals/_pending_proposal_row.html deleted file mode 100644 index 2dda000b..00000000 --- a/symposion/templates/proposals/_pending_proposal_row.html +++ /dev/null @@ -1,42 +0,0 @@ -{% load i18n %} - - - - {{ proposal.title }} - - - {{ proposal.kind.name }} - - - {% if proposal.cancelled %} - {% trans 'Cancelled' %} - {% else %} - {% if request.user == proposal.speaker.user %} - {% if proposal.result.status == "accepted" %} - {% trans 'Accepted' %} - {% else %} - {% trans 'Submitted' %} - {% endif %} - {% else %} - {% trans 'Invited' %} - {% endif %} - {% endif %} - - - - {% if not proposal.cancelled %} - - {% endif %} - - diff --git a/symposion/templates/proposals/_proposal_fields.html b/symposion/templates/proposals/_proposal_fields.html deleted file mode 100644 index 49f94aa9..00000000 --- a/symposion/templates/proposals/_proposal_fields.html +++ /dev/null @@ -1,60 +0,0 @@ -{% load i18n %} - -
    -
    {% trans "Submitted by" %}
    -
    {{ proposal.speaker }}
    - -
    {% trans "Track" %}
    -
    {{ proposal.track }} 
    - -
    {% trans "Audience Level" %}
    -
    {{ proposal.get_audience_level_display }} 
    - - {% if proposal.additional_speakers.all %} -
    {% trans "Additional Speakers" %}
    -
    - {% for speaker in proposal.additional_speakers.all %} -
  • - {% if speaker.user %} - {{ speaker.name }} <{{ speaker.email }}> - {% else %} - {{ speaker.email }} ({% trans "Invitation Sent" %}) - {% endif %} -
  • - {% endfor %} -
    - {% endif %} - -
    {% trans "Description" %}
    -
    {{ proposal.description }} 
    - -
    {% trans "Abstract" %}
    -
    {{ proposal.abstract|safe }} 
    - -
    {% trans "Notes" %}
    -
    {{ proposal.additional_notes|safe }} 
    - -
    {% trans "Speaker Bio" %}
    -
    {{ proposal.speaker.biography|safe }} 
    - -
    {% trans "Documents" %}
    -
    - {% if proposal.supporting_documents.exists %} - - {% for document in proposal.supporting_documents.all %} - - - - - {% endfor %} -
    {{ document.description }} -
    - {% csrf_token %} - -
    -
    - {% else %} - No supporting documents attached to this proposal. - {% endif %} -
    -
    diff --git a/symposion/templates/proposals/_proposal_row.html b/symposion/templates/proposals/_proposal_row.html deleted file mode 100644 index de820cfd..00000000 --- a/symposion/templates/proposals/_proposal_row.html +++ /dev/null @@ -1,32 +0,0 @@ - - - {{ proposal.title }} - - - {{ proposal.kind.name }} - - - {% if proposal.cancelled %} - Cancelled - {% else %} - {% if request.user == proposal.speaker.user %} - {% if proposal.result.status == "accepted" %} - Accepted - {% else %} - Submitted - {% endif %} - {% else %} - Associated - {% endif %} - {% endif %} - - - - {% if not proposal.cancelled %} - {% if request.user == proposal.speaker.user and proposal.can_edit %} - Edit - Manage Additional Speakers - {% endif %} - {% endif %} - - diff --git a/symposion/templates/proposals/base.html b/symposion/templates/proposals/base.html deleted file mode 100644 index d6a6615b..00000000 --- a/symposion/templates/proposals/base.html +++ /dev/null @@ -1,5 +0,0 @@ -{% extends "site_base.html" %} - -{% block body_outer %} - {% block body %}{% endblock %} -{% endblock %} diff --git a/symposion/templates/proposals/document_create.html b/symposion/templates/proposals/document_create.html deleted file mode 100644 index ffdcf96f..00000000 --- a/symposion/templates/proposals/document_create.html +++ /dev/null @@ -1,17 +0,0 @@ -{% extends "proposals/base.html" %} - -{% load bootstrap_tags %} - -{% block page_title %}Upload Document to '{{ proposal.title }}'{% endblock %} - -{% block body %} -
    - {% csrf_token %} -
    - {{ form|as_bootstrap }} -
    -
    - -
    -
    -{% endblock %} diff --git a/symposion/templates/proposals/proposal_cancel.html b/symposion/templates/proposals/proposal_cancel.html deleted file mode 100644 index 5e57c80a..00000000 --- a/symposion/templates/proposals/proposal_cancel.html +++ /dev/null @@ -1,18 +0,0 @@ -{% extends "proposals/base.html" %} - -{% load i18n %} - -{% load bootstrap_tags %} - -{% block head_title %}{% trans 'Cancel Proposal' %}{% endblock %} - -{% block body %} -

    Cancel: {{ proposal.title }}

    - -
    - {% csrf_token %} -

    Are you sure you want to cancel {{ proposal.title }}?

    - - {% trans 'No, keep it for now' %} -
    -{% endblock %} diff --git a/symposion/templates/proposals/proposal_detail.html b/symposion/templates/proposals/proposal_detail.html deleted file mode 100644 index 7f02504c..00000000 --- a/symposion/templates/proposals/proposal_detail.html +++ /dev/null @@ -1,120 +0,0 @@ -{% extends "proposals/base.html" %} - -{% load i18n %} -{% load account_tags %} -{% load bootstrap_tags %} - -{% block head_title %}{{ proposal.title }}{% endblock %} - -{% block body %} -
    - {% if not proposal.cancelled %} - {% if request.user == proposal.speaker.user %} - - {% trans "Edit this proposal" %} - - - {% trans "Cancel this proposal" %} - - {% else %} - - {% trans "Remove me from this proposal" %} - - {% endif %} - {% else %} - {% trans 'Cancelled' } - {% endif %} -
    - -

    #{{ proposal.number }}: {{ proposal.title }} ({{ proposal.speaker }}, Track: {{ proposal.track }})

    - -
    - -
    -
    - {% include "proposals/_proposal_fields.html" %} -
    - {% if request.user == proposal.speaker.user %} -
    -

    {% trans 'Supporting Documents' %}

    - - {% if proposal.supporting_documents.exists %} - - {% for document in proposal.supporting_documents.all %} - - - - - {% endfor %} -
    {{ document.description }} -
    - {% csrf_token %} - -
    -
    - {% else %} -

    {% trans 'No supporting documents attached to this proposal.' %}

    - {% endif %} - {% trans 'Add Document' %} -
    - {% endif %} - - {% if message_form %} -
    - -

    {% trans 'Conversation with Reviewers' %}

    - - {% for message in proposal.messages.all %} -
    -
    {{ message.message|safe }}
    - -
    -
    - {% endfor %} - -

    {% trans 'Leave a Message' %}

    - -

    {% trans 'You can leave a message for the reviewers here.' %}

    - -
    - {% csrf_token %} -
    - {{ message_form|as_bootstrap }} -
    -
    - -
    -
    -
    - {% endif %} -
    -
    -{% endblock %} - -{% block extra_script %} - - -{% endblock extra_script %} diff --git a/symposion/templates/proposals/proposal_edit.html b/symposion/templates/proposals/proposal_edit.html deleted file mode 100644 index e9db1f6f..00000000 --- a/symposion/templates/proposals/proposal_edit.html +++ /dev/null @@ -1,23 +0,0 @@ -{% extends "proposals/base.html" %} - -{% load bootstrap_tags %} -{% load markitup_tags %} - -{% block head_title %}Editing {{ proposal.title }}{% endblock %} - -{% block body %} -

    Edit: {{ proposal.title }}

    - -

    Manage speakers

    - -
    - {% csrf_token %} -
    - {{ form|as_bootstrap }} -
    -
    - - Cancel -
    -
    -{% endblock %} diff --git a/symposion/templates/proposals/proposal_leave.html b/symposion/templates/proposals/proposal_leave.html deleted file mode 100644 index 4ae0cbdc..00000000 --- a/symposion/templates/proposals/proposal_leave.html +++ /dev/null @@ -1,11 +0,0 @@ -{% extends "proposals/base.html" %} - -{% block body %} -

    Leaving {{ proposal.title }}

    - -
    - {% csrf_token %} -

    Are you sure you wish to leave as a speaker on {{ proposal.title }}?

    - -
    -{% endblock %} diff --git a/symposion/templates/proposals/proposal_speaker_manage.html b/symposion/templates/proposals/proposal_speaker_manage.html deleted file mode 100644 index f3b6324b..00000000 --- a/symposion/templates/proposals/proposal_speaker_manage.html +++ /dev/null @@ -1,41 +0,0 @@ -{% extends "proposals/base.html" %} -{% load i18n %} - -{% load bootstrap_tags %} - -{% block body %} -

    {% trans 'Proposal:' %} {{ proposal.title }}

    - -

    - {% trans 'Edit proposal' %} - -

    - -

    {% trans 'Current Speakers' %}

    - - {% for speaker in speakers %} - {% if speaker.user %} -

    {{ speaker.name }} — {{ speaker.email }}

    - {% else %} -

    {{ speaker.email }} — {% trans 'pending invitation' %}

    - {% endif %} - {% endfor %} - -

    {% trans 'Add another speaker' %}

    - -
    - {% csrf_token %} - {{ add_speaker_form|as_bootstrap }} -
    - -
    -
    -{% endblock %} - -{% block extra_body %} - -{% endblock %} diff --git a/symposion/templates/proposals/proposal_submit.html b/symposion/templates/proposals/proposal_submit.html deleted file mode 100644 index 1d116ba5..00000000 --- a/symposion/templates/proposals/proposal_submit.html +++ /dev/null @@ -1,22 +0,0 @@ -{% extends "proposals/base.html" %} - -{% load boxes_tags %} -{% load i18n %} - -{% block page_title %}{% trans "Submit A Proposal" %}{% endblock %} - -{% block body %} - {% box "proposal_submit" %} - - {% if kinds %} -

    Select what kind of proposal you'd like to submit:

    - -
      - {% for kind in kinds %} -
    • {{ kind }}
    • - {% endfor %} -
    - {% else %} -

    Proposals are not currently open for submission.

    - {% endif %} -{% endblock %} diff --git a/symposion/templates/proposals/proposal_submit_kind.html b/symposion/templates/proposals/proposal_submit_kind.html deleted file mode 100644 index bd8cb92d..00000000 --- a/symposion/templates/proposals/proposal_submit_kind.html +++ /dev/null @@ -1,27 +0,0 @@ -{% extends "proposals/base.html" %} - -{% load bootstrap_tags %} -{% load boxes_tags %} -{% load markitup_tags %} -{% load i18n %} - -{% block page_title %}Submit a Proposal for a {{ kind.name }}{% endblock %} - -{% block body %} - {% box "example_proposal" %} - -
    - {% csrf_token %} -
    - {{ form|as_bootstrap }} -
    -
    -

    - You will be able to edit your proposal after it has been submitted. The program committee may ask questions, provide feedback, and even suggest changes to your proposal as part of the review processes. -

    - - - -
    -
    -{% endblock %} diff --git a/symposion/templates/reviews/_result_notification_prepare_help.html b/symposion/templates/reviews/_result_notification_prepare_help.html deleted file mode 100644 index e0ef7ddf..00000000 --- a/symposion/templates/reviews/_result_notification_prepare_help.html +++ /dev/null @@ -1,7 +0,0 @@ - Body may include the following variables which will be substituted in the email with a value - specific to each proposal: -
      -
    • {% templatetag openvariable %} proposal.title {% templatetag closevariable %} e.g. {{ proposals.0.title }} -
    • {% templatetag openvariable %} proposal.kind {% templatetag closevariable %} e.g. {{ proposals.0.kind }} -
    • {% templatetag openvariable %} proposal.speaker {% templatetag closevariable %} e.g. {{ proposals.0.speaker }} -
    \ No newline at end of file diff --git a/symposion/templates/reviews/_review_table.html b/symposion/templates/reviews/_review_table.html deleted file mode 100644 index 7bb6bba2..00000000 --- a/symposion/templates/reviews/_review_table.html +++ /dev/null @@ -1,37 +0,0 @@ -{% load i18n %} - - - - - - - - - - - - - - - - {% for proposal in proposals %} - - - - - - - - - - - - {% endfor %} - -
    #{% trans "Speaker / Title" %}{% trans "Category" %}{% trans "+1" %}{% trans "+0" %}{% trans "-0" %}{% trans "-1" %}
    {{ proposal.number }} - - {{ proposal.speaker }} -
    - {{ proposal.title }} -
    -
    {{ proposal.track }}{{ proposal.comment_count }}{{ proposal.plus_one }}{{ proposal.plus_zero }}{{ proposal.minus_zero }}{{ proposal.minus_one }}{{ proposal.user_vote|default:"" }}
    diff --git a/symposion/templates/reviews/access_not_permitted.html b/symposion/templates/reviews/access_not_permitted.html deleted file mode 100644 index 225d05e0..00000000 --- a/symposion/templates/reviews/access_not_permitted.html +++ /dev/null @@ -1,10 +0,0 @@ -{% extends "reviews/base.html" %} - -{% block body %} -

    Access Not Permitted

    - -

    - Sorry, you do not have permission to access this page. If you - believe this is a bug, please contact us immediately. -

    -{% endblock %} diff --git a/symposion/templates/reviews/base.html b/symposion/templates/reviews/base.html deleted file mode 100644 index c965a6cb..00000000 --- a/symposion/templates/reviews/base.html +++ /dev/null @@ -1,117 +0,0 @@ -{% extends "site_base.html" %} - -{% load i18n %} -{% load sitetree %} - -{% block extra_style %} - -{% endblock %} - -{% block body_class %}reviews{% endblock %} - -{% block body_outer %} -
    -
    - {% block sidebar %} - - {% endblock %} -
    -
    - {% block body %} - {% endblock %} -
    -
    -{% endblock %} - -{% block extra_script %} - - - - -{% endblock %} diff --git a/symposion/templates/reviews/result_notification.html b/symposion/templates/reviews/result_notification.html deleted file mode 100644 index 6d2ecf77..00000000 --- a/symposion/templates/reviews/result_notification.html +++ /dev/null @@ -1,153 +0,0 @@ -{% extends "reviews/base.html" %} - -{% load i18n %} - -{% block extra_style %} - -{% endblock %} - -{% block body %} - - - -

    Result Notification

    - -
    - - {% csrf_token %} - -

    - Select one or more proposals (0 currently selected) -
    - then pick an email template - -
    - -

    - - - - - - - - - - - - - {% for proposal in proposals %} - - - - - - - - - {% endfor %} - -
    #{% trans "Speaker / Title" %}{% trans "Category" %}{% trans "Status" %}{% trans "Notified?" %}
    {{ proposal.number }} - - {{ proposal.speaker }} -
    - {{ proposal.title }} -
    -
    {{ proposal.track }} - {% with proposal.result.status as status %} -
    - {% if status != "undecided" %} - {{ status }} - {% endif %} -
    - {% endwith %} -
    - {% if proposal.notifications.exists %}yes{% endif %} -
    -
    -{% endblock %} - -{% block extra_script %} - -{% endblock %} diff --git a/symposion/templates/reviews/result_notification_prepare.html b/symposion/templates/reviews/result_notification_prepare.html deleted file mode 100644 index 2afa5875..00000000 --- a/symposion/templates/reviews/result_notification_prepare.html +++ /dev/null @@ -1,49 +0,0 @@ -{% extends "reviews/base.html" %} - -{% load i18n %} - -{% block body %} -

    Result Notification Prepare

    - -
    -
    -

    Proposals

    - - {% for proposal in proposals %} - - - - {% endfor %} -
    - {{ proposal.speaker }} ({{ proposal.speaker.email }}) -
    - {{ proposal.title }} -
    -
    -
    -

    Email

    - -
    - - {% csrf_token %} - - - -
    - - -
    - - -
    - - - - {% include "reviews/_result_notification_prepare_help.html" %} - - - Cancel -
    -
    - -{% endblock %} diff --git a/symposion/templates/reviews/review_admin.html b/symposion/templates/reviews/review_admin.html deleted file mode 100644 index 38efa50b..00000000 --- a/symposion/templates/reviews/review_admin.html +++ /dev/null @@ -1,56 +0,0 @@ -{% extends "reviews/base.html" %} - -{% block body %} -

    Reviewers

    - - - - - - - - - - - {% for reviewer in reviewers %} - - - - - - - - - - {% endfor %} -
    - Reviewer - - Proposals
    Reviewed - -
    - Comments - - +1 - - +0 - - −0 - - −1 -
    - {{ reviewer.get_full_name }} - - {{ reviewer.total_votes }} - - {{ reviewer.comment_count }} - - {{ reviewer.plus_one }} - - {{ reviewer.plus_zero }} - - {{ reviewer.minus_zero }} - - {{ reviewer.minus_one }} -
    -{% endblock %} diff --git a/symposion/templates/reviews/review_assignment.html b/symposion/templates/reviews/review_assignment.html deleted file mode 100644 index d450efdf..00000000 --- a/symposion/templates/reviews/review_assignment.html +++ /dev/null @@ -1,32 +0,0 @@ -{% extends "reviews/base.html" %} - -{% block body %} -

    Review Assignments

    - - {% if assignments %} - - - - - - - {% for assignment in assignments %} - - - - - {% endfor %} -
    ProposalOpted outOpt out
    - - {{ assignment.proposal.title }} - - -
    - {% csrf_token %} - -
    -
    - {% else %} -

    You do not have any assignments.

    - {% endif %} -{% endblock %} diff --git a/symposion/templates/reviews/review_bulk_accept.html b/symposion/templates/reviews/review_bulk_accept.html deleted file mode 100644 index b70d7b43..00000000 --- a/symposion/templates/reviews/review_bulk_accept.html +++ /dev/null @@ -1,15 +0,0 @@ -{% extends "reviews/base.html" %} - -{% load bootstrap_tags %} - -{% block body %} - -

    Bulk Accept

    -
    - {% csrf_token %} - {{ form|as_bootstrap }} -
    - -
    -
    -{% endblock %} diff --git a/symposion/templates/reviews/review_comment.html b/symposion/templates/reviews/review_comment.html deleted file mode 100644 index d7fa96a4..00000000 --- a/symposion/templates/reviews/review_comment.html +++ /dev/null @@ -1,2 +0,0 @@ -{{ proposal }}: proposal obj -{{ form }}: comment form obj diff --git a/symposion/templates/reviews/review_detail.html b/symposion/templates/reviews/review_detail.html deleted file mode 100644 index 8072aa5c..00000000 --- a/symposion/templates/reviews/review_detail.html +++ /dev/null @@ -1,198 +0,0 @@ -{% extends "reviews/base.html" %} - -{% load i18n %} -{% load markitup_tags %} -{% load bootstrap_tags %} -{% load account_tags %} - -{% block extra_style %} - - -{% endblock %} - -{% block body %} - {% if request.user.is_staff %} -
    -
    - {% csrf_token %} -
    - {% if proposal.result.status == "accepted" %} - Accepted - - {% else %} - {% if proposal.result.status == "rejected" %} - Rejected - - {% else %} - {% if proposal.result.status == "standby" %} - Standby - - {% else %} - Undecided - - {% endif %} - {% endif %} - {% endif %} -
    -
    -
    - {% endif %} - -

    #{{ proposal.number }}: {{ proposal.title }} ({{ proposal.speaker }})

    - -
    - -
    -
    - {% include "proposals/_proposal_fields.html" %} -
    -
    - -

    {% trans "Current Results" %}

    - - - - - - - - - - - - - - - - -
    +1 votes+0 votes-0 votes-1 votes{% trans "Total Responses" %} -
    {{ proposal.plus_one }}{{ proposal.plus_zero }}{{ proposal.minus_zero }}{{ proposal.minus_one }}{{ proposal.total_votes }}
    - -
    - - {% if review_form %} -
    - {% trans "Submit Review" %} -

    Enter your vote and any comment to go along with it. You can revise your vote or comment multiple times with an existing vote (your previously recorded score will be replaced during calculations). Your vote and comments are not public and will only be viewable by other reviewers.

    - {% csrf_token %} - {{ review_form|as_bootstrap }} -
    - -
    -
    - {% else %} -

    You do not have permission to vote on this proposal.

    - {% endif %} - - {% if reviews %} -
    Review Comments
    - {% for review in reviews %} -
    -
    - {{ review.vote }} -
    - {% if is_manager %} -
    -
    - {% csrf_token %} - -
    -
    - {% endif %} -
    - {% user_display review.user %} - {{ review.submitted_at|timesince }} ago
    - {{ review.comment|safe }} -
    -
    - {% endfor %} - {% endif %} - - {% markitup_media "no-jquery" %} -
    -
    - {% if review_messages %} -

    {% trans "Conversation with the submitter" %}

    - {% for message in review_messages %} -
    -
    - {% user_display message.user %} - {{ message.submitted_at|timesince }} ago
    - {{ message.message|safe }} -
    -
    - {% endfor %} -
    - {% endif %} - -
    - {% trans "Send a message" %} -

    - {% blocktrans %} - If you'd like to communicate with the submitter, use the following form and he or she will be - notified and given the opportunity to respond. - {% endblocktrans %} -

    - {% csrf_token %} - {{ message_form|as_bootstrap }} -
    - -
    -
    -
    -
    -
    -{% endblock %} - -{% block extra_script %} - - - -{% endblock %} diff --git a/symposion/templates/reviews/review_list.html b/symposion/templates/reviews/review_list.html deleted file mode 100644 index 36504604..00000000 --- a/symposion/templates/reviews/review_list.html +++ /dev/null @@ -1,14 +0,0 @@ -{% extends "reviews/base.html" %} - -{% block body %} -

    {{ section }}

    - {% if reviewed == 'all_reviews' %} -

    All proposals

    - {% elif reviewed == 'user_reviewed' %} -

    Proposals you have reviewed

    - {% else %} -

    Proposals you have not yet reviewed

    - {% endif %} - - {% include "reviews/_review_table.html" %} -{% endblock %} diff --git a/symposion/templates/reviews/review_review.html b/symposion/templates/reviews/review_review.html deleted file mode 100644 index 4202d70e..00000000 --- a/symposion/templates/reviews/review_review.html +++ /dev/null @@ -1,80 +0,0 @@ -{% extends "site_base.html" %} - -{% load markitup_tags %} -{% load uni_form_tags %} - -{% block body_class %}review{% endblock %} - -{% block body %} -

    Proposal Review

    - -
    -

    {{ proposal.title }}

    - -

    - {% if proposal.cancelled %} - Cancelled - {% endif %} -

    - -
    - {{ proposal.description }} -
    - -

    Type: {{ proposal.get_session_type_display }}

    - -

    Abstract

    -
    - {{ proposal.abstract_html|safe }} -
    - -

    Audience level: {{ proposal.get_audience_level_display }}

    - -

    Submitting speaker: {{ proposal.speaker }}

    {# @@@ bio? #} - - {% if proposal.additional_speakers.all %} -

    Additional speakers:

    -
      - {% for speaker in proposal.additional_speakers.all %} - {% if speaker.user %} -
    • {{ speaker.name }} — {{ speaker.email }}
    • - {% else %} -
    • {{ speaker.email }} — pending invitation
    • - {% endif %} - {% endfor %} -
    - {% endif %} - -

    Additional Notes (private from submitter)

    -
    - {{ proposal.additional_notes }} -
    -
    - - {% markitup_media %} - -

    Review

    - -
    - {% csrf_token %} -
    - {{ review_form|as_uni_form }} -
    - -
    -
    -
    - -

    Comment

    - -
    - {% csrf_token %} -
    - {{ comment_form|as_uni_form }} -
    - -
    -
    -
    - -{% endblock %} diff --git a/symposion/templates/reviews/review_stats.html b/symposion/templates/reviews/review_stats.html deleted file mode 100644 index fb613d09..00000000 --- a/symposion/templates/reviews/review_stats.html +++ /dev/null @@ -1,82 +0,0 @@ -{% extends "reviews/base.html" %} - -{% block body %} -

    Voting Status ({{ section_slug }})

    - - {% if key %} - - -
    - {% if key == "positive" %} -

    Positive - proposals with at least {{ vote_threshold }} vote{{ vote_threshold|pluralize }} and at least one +1 and no −1s

    - {% endif %} - {% if key == "negative" %} -

    Negative - proposals with at least {{ vote_threshold }} vote{{ vote_threshold|pluralize }} and at least one −1 and no +1s

    - {% endif %} - {% if key == "indifferent" %} -

    Indifferent - proposals with at least {{ vote_threshold }} vote{{ vote_threshold|pluralize }} and neither a +1 or a −1

    - {% endif %} - {% if key == "controversial" %} -

    Controversial - proposals with at least {{ vote_threshold }} vote{{ vote_threshold|pluralize }} and both a +1 and −1

    - {% endif %} - {% if key == "too_few" %} -

    Too Few Reviews - proposals with fewer than {{ vote_threshold }} vote{{ vote_threshold|pluralize }}

    - {% endif %} - - {% include "reviews/_review_table.html" %} - -
    - {% else %} -

    Reviews are placed into one of five buckets depending on the state of their votes:

    - -
    -
    - Positive - {{ proposals.positive|length }} -
    -
    - proposals with at least {{ vote_threshold }} vote{{ vote_threshold|pluralize }} and at least one +1 and no −1s -
    -
    - Negative - {{ proposals.negative|length }} -
    -
    - proposals with at least {{ vote_threshold }} vote{{ vote_threshold|pluralize }} and at least one −1 and no +1s -
    -
    - Indifferent - {{ proposals.indifferent|length }} -
    -
    - proposals with at least {{ vote_threshold }} vote{{ vote_threshold|pluralize }} and neither a +1 or a −1 -
    -
    - Controversial - {{ proposals.controversial|length }} -
    -
    - proposals with at least {{ vote_threshold }} vote{{ vote_threshold|pluralize }} and both a +1 and −1 -
    -
    - Too Few Reviews - {{ proposals.too_few|length }} -
    -
    - proposals with fewer than {{ vote_threshold }} vote{{ vote_threshold|pluralize }} -
    -
    - {% endif %} - -{% endblock %} diff --git a/symposion/templates/schedule/_edit_grid.html b/symposion/templates/schedule/_edit_grid.html deleted file mode 100644 index 8b4d4146..00000000 --- a/symposion/templates/schedule/_edit_grid.html +++ /dev/null @@ -1,39 +0,0 @@ - - - - - {% for room in timetable.rooms %} - - {% endfor %} - - - - {% for row in timetable %} - - - {% for slot in row.slots %} - - {% endfor %} - {% if forloop.last %} - - {% endif %} - - {% endfor %} - -
     {{ room.name }}
    {{ row.time|date:"h:iA" }} - {% if slot.kind.label == "talk" or slot.kind.label == "tutorial" %} - {% if not slot.content %} - + - {% else %} - {{ slot.content.title }} - {{ slot.content.speaker }} - {% endif %} - {% else %} - {% if slot.content_override.raw %} - {{ slot.content_override.rendered|safe }} - {% else %} - {{ slot.kind.label }} - {% endif %} - — edit - {% endif %} -
    diff --git a/symposion/templates/schedule/_grid.html b/symposion/templates/schedule/_grid.html deleted file mode 100644 index 5df035b0..00000000 --- a/symposion/templates/schedule/_grid.html +++ /dev/null @@ -1,41 +0,0 @@ - - - - - {% for room in timetable.rooms %} - - {% endfor %} - - - - {% for row in timetable %} - - - {% for slot in row.slots %} - - {% endfor %} - {% if forloop.last %} - - {% endif %} - - {% endfor %} - -
     {{ room.name }}
    {{ row.time|date:"h:iA" }} - {% if slot.kind.label == "talk" or slot.kind.label == "tutorial" %} - {% if not slot.content %} - {% else %} - - {{ slot.content.title }} - - - {{ slot.content.speakers|join:", " }} - - {% endif %} - {% else %} - {% if slot.content_override.raw %} - {{ slot.content_override.rendered|safe }} - {% else %} - {{ slot.kind.label }} - {% endif %} - {% endif %} -
    diff --git a/symposion/templates/schedule/_slot_edit.html b/symposion/templates/schedule/_slot_edit.html deleted file mode 100644 index 81d48276..00000000 --- a/symposion/templates/schedule/_slot_edit.html +++ /dev/null @@ -1,14 +0,0 @@ -{% load i18n bootstrap_tags %} - diff --git a/symposion/templates/schedule/presentation_detail.html b/symposion/templates/schedule/presentation_detail.html deleted file mode 100644 index 4813f2ea..00000000 --- a/symposion/templates/schedule/presentation_detail.html +++ /dev/null @@ -1,35 +0,0 @@ -{% extends "site_base.html" %} - -{% load sitetree %} - -{% block head_title %}Presentation: {{ presentation.title }}{% endblock %} - -{% block breadcrumbs %}{% sitetree_breadcrumbs from "main" %}{% endblock %} - -{% block body %} - {% if presentation.slot %} -

    - {{ presentation.slot.day.date|date:"l" }} - {{ presentation.slot.start}}–{{ presentation.slot.end }} -

    - {% endif %} -

    {{ presentation.title }}

    - -

    - {% for speaker in presentation.speakers %} - {{ speaker }}{% if not forloop.last %}, {% endif %}{% endfor %} -

    - -
    -
    Audience level:
    -
    {{ presentation.proposal.get_audience_level_display }}
    -
    - -

    Description

    - -
    {{ presentation.description }}
    - -

    Abstract

    - -
    {{ presentation.abstract|safe }}
    -{% endblock %} diff --git a/symposion/templates/schedule/schedule_conference.html b/symposion/templates/schedule/schedule_conference.html deleted file mode 100644 index 768bb6ab..00000000 --- a/symposion/templates/schedule/schedule_conference.html +++ /dev/null @@ -1,38 +0,0 @@ -{% extends "site_base.html" %} - -{% load i18n %} -{% load bootstrap_tags %} -{% load boxes_tags %} -{% load cache %} - -{% block head_title %}Conference Schedule{% endblock %} - -{% block body_class %}full{% endblock %} - -{% block right %} -{% endblock %} - -{% block body_outer %} -
    -
    -
    -

    Conference Schedule

    - {% block breadcrumbs %}{% endblock %} -
    -
    -
    - {% box "schedule_top" %} - - {% for section in sections %} - {% cache 600 "schedule-table" section.schedule.section %} - {% for timetable in section.days %} -

    {{ section.schedule.section.name }} — {{ timetable.day.date }}

    - {% include "schedule/_grid.html" %} - {% endfor %} - {% endcache %} - {% endfor %} - - {% box "schedule_bottom" %} -
    -
    -{% endblock %} diff --git a/symposion/templates/schedule/schedule_detail.html b/symposion/templates/schedule/schedule_detail.html deleted file mode 100644 index 51f8ac51..00000000 --- a/symposion/templates/schedule/schedule_detail.html +++ /dev/null @@ -1,37 +0,0 @@ -{% extends "site_base.html" %} - -{% load i18n %} -{% load bootstrap_tags %} -{% load boxes_tags %} -{% load cache %} -{% load sitetree %} - -{% block head_title %}Conference Schedule{% endblock %} - -{% block body_class %}full{% endblock %} - -{% block right %} -{% endblock %} - -{% block body_outer %} -
    -
    -
    -

    {{ schedule.section }} Schedule

    - {% block breadcrumbs %}{% sitetree_breadcrumbs from "main" %}{% endblock %} -
    -
    -
    - {% box "schedule_top_"|add:schedule.section.name|slugify %} - - {% cache 600 "schedule-table" schedule.section %} - {% for timetable in days %} -

    {{ timetable.day.date }}

    - {% include "schedule/_grid.html" %} - {% endfor %} - {% endcache %} - - {% box "schedule_bottom" %} -
    -
    -{% endblock %} diff --git a/symposion/templates/schedule/schedule_edit.html b/symposion/templates/schedule/schedule_edit.html deleted file mode 100644 index 9b1ab864..00000000 --- a/symposion/templates/schedule/schedule_edit.html +++ /dev/null @@ -1,63 +0,0 @@ -{% extends "site_base.html" %} - -{% load i18n %} -{% load bootstrap_tags %} - -{% block head_title %}Conference Schedule Edit{% endblock %} - -{% block body_class %}full{% endblock %} - -{% block right %} -{% endblock %} - -{% block extra_head %} - -{% endblock %} - -{% block body_outer %} -
    -
    -

    Schedule Edit

    - - {% for timetable in days %} -

    {{ timetable.day.date }}

    - {% include "schedule/_edit_grid.html" %} - {% endfor %} -
    -
    {% csrf_token %} - {{ form.as_p }} - - -
    - -
    -{% endblock %} - -{% block extra_script %} - - -{% endblock %} diff --git a/symposion/templates/schedule/schedule_list.html b/symposion/templates/schedule/schedule_list.html deleted file mode 100644 index 1547e4c7..00000000 --- a/symposion/templates/schedule/schedule_list.html +++ /dev/null @@ -1,51 +0,0 @@ -{% extends "site_base.html" %} - -{% load i18n %} -{% load cache %} -{% load sitetree %} - -{% block head_title %}Presentation Listing{% endblock %} - -{% block extra_head %} - -{% endblock %} - -{% block breadcrumbs %}{% sitetree_breadcrumbs from "main" %}{% endblock %} - -{% block body %} -

    Accepted {{ schedule.section.name }}

    - {% cache 600 "schedule-list" schedule.section.name %} - {% for presentation in presentations %} -
    -
    -

    {{ presentation.title }}

    -

    {{ presentation.speakers|join:", " }}

    - {{ presentation.description }} - {% if presentation.slot %} -

    - {{ presentation.slot.day.date|date:"l" }} - {{ presentation.slot.start}}–{{ presentation.slot.end }} - in - {{ presentation.slot.rooms|join:", " }} -

    - {% endif %} -
    -
    - {% endfor %} - {% endcache %} -{% endblock %} diff --git a/symposion/templates/sitetree/breadcrumbs.html b/symposion/templates/sitetree/breadcrumbs.html deleted file mode 100644 index 3f292f2e..00000000 --- a/symposion/templates/sitetree/breadcrumbs.html +++ /dev/null @@ -1,17 +0,0 @@ -{% load sitetree %} -{% if sitetree_items %} - -{% else %} - -{% endif %} diff --git a/symposion/templates/sitetree/menu.html b/symposion/templates/sitetree/menu.html deleted file mode 100644 index 71e6ac1b..00000000 --- a/symposion/templates/sitetree/menu.html +++ /dev/null @@ -1,16 +0,0 @@ -{% load sitetree %} - diff --git a/symposion/templates/sitetree/submenu.html b/symposion/templates/sitetree/submenu.html deleted file mode 100644 index 93684588..00000000 --- a/symposion/templates/sitetree/submenu.html +++ /dev/null @@ -1,8 +0,0 @@ -{% load sitetree %} - diff --git a/symposion/templates/sitetree/tree.html b/symposion/templates/sitetree/tree.html deleted file mode 100644 index e7a1001f..00000000 --- a/symposion/templates/sitetree/tree.html +++ /dev/null @@ -1,15 +0,0 @@ -{% load sitetree %} -{% if sitetree_items %} -
      - {% for item in sitetree_items %} - {% if item.insitetree %} -
    • - {{ item.title_resolved }} - {% if item.has_children %} - {% sitetree_children of item for sitetree template "sitetree/tree.html" %} - {% endif %} -
    • - {% endif %} - {% endfor %} -
    -{% endif %} diff --git a/symposion/templates/speakers/base.html b/symposion/templates/speakers/base.html deleted file mode 100644 index 5c4b85b5..00000000 --- a/symposion/templates/speakers/base.html +++ /dev/null @@ -1 +0,0 @@ -{% extends "site_base_onecolumn.html" %} diff --git a/symposion/templates/speakers/speaker_create.html b/symposion/templates/speakers/speaker_create.html deleted file mode 100644 index 3d3e6a16..00000000 --- a/symposion/templates/speakers/speaker_create.html +++ /dev/null @@ -1,23 +0,0 @@ -{% extends "speakers/base.html" %} - -{% load bootstrap_tags %} -{% load i18n %} -{% load boxes_tags %} - -{% block page_title %}{% trans "Create Speaker Profile" %}{% endblock %} - -{% block body %} - {% box "speaker-profile" %} - -
    - {% csrf_token %} - {% trans "Create Speaker Profile" %} -
    - {{ form|as_bootstrap }} -
    -
    - - Cancel -
    -
    -{% endblock %} diff --git a/symposion/templates/speakers/speaker_edit.html b/symposion/templates/speakers/speaker_edit.html deleted file mode 100644 index b5d7ef16..00000000 --- a/symposion/templates/speakers/speaker_edit.html +++ /dev/null @@ -1,23 +0,0 @@ -{% extends "speakers/base.html" %} - -{% load bootstrap_tags %} -{% load i18n %} -{% load boxes_tags %} - -{% block page_title %}{% trans "Edit Speaker Profile" %}{% endblock %} - -{% block body %} - {% box "speaker-profile" %} - -
    - {% csrf_token %} - {% trans "Edit Speaker Profile" %} -
    - {{ form|as_bootstrap }} -
    -
    - - Cancel -
    -
    -{% endblock %} diff --git a/symposion/templates/speakers/speaker_profile.html b/symposion/templates/speakers/speaker_profile.html deleted file mode 100644 index 25ad6539..00000000 --- a/symposion/templates/speakers/speaker_profile.html +++ /dev/null @@ -1,41 +0,0 @@ -{% extends "site_base.html" %} - -{% load i18n %} -{% load thumbnail %} - - -{% block head_title %}{{ speaker.name }}{% endblock %} - -{% block body %} -
    -
    - {% if speaker.photo %} - {{ speaker.name }} - {% else %} -   - {% endif %} -
    -
    - {% if speaker.user == request.user or request.user.is_staff %} - Edit - {% endif %} -

    {{ speaker.name }}

    -
    {{ speaker.biography|safe }}
    - -

    Presentations

    - {% for presentation in presentations %} -

    {{ presentation.title }}

    - {% if presentation.slot %} -

    - {{ presentation.slot.day.date|date:"l" }} - {{ presentation.slot.start}}–{{ presentation.slot.end }} - in - {{ presentation.slot.rooms|join:", " }} -

    - {% endif %} - {% empty %} -

    No presentations. This page is only visible to staff until there is a presentation.

    - {% endfor %} -

    -
    -{% endblock %} diff --git a/symposion/templates/sponsorship/_horizontal_by_level.html b/symposion/templates/sponsorship/_horizontal_by_level.html deleted file mode 100644 index 98025053..00000000 --- a/symposion/templates/sponsorship/_horizontal_by_level.html +++ /dev/null @@ -1,11 +0,0 @@ -{% load sponsorship_tags %} -{% sponsor_levels as levels %} - diff --git a/symposion/templates/sponsorship/_sponsor_link.html b/symposion/templates/sponsorship/_sponsor_link.html deleted file mode 100644 index 7b912bdb..00000000 --- a/symposion/templates/sponsorship/_sponsor_link.html +++ /dev/null @@ -1,10 +0,0 @@ -{% load thumbnail %} -{% spaceless %} - - {% if dimensions %} - - {% else %} - - {% endif %} - -{% endspaceless %} diff --git a/symposion/templates/sponsorship/_vertical_by_level.html b/symposion/templates/sponsorship/_vertical_by_level.html deleted file mode 100644 index b2ed5ec4..00000000 --- a/symposion/templates/sponsorship/_vertical_by_level.html +++ /dev/null @@ -1,13 +0,0 @@ -{% load sponsorship_tags %} -{% sponsor_levels as levels %} - diff --git a/symposion/templates/sponsorship/_wall.html b/symposion/templates/sponsorship/_wall.html deleted file mode 100644 index 7f1b9896..00000000 --- a/symposion/templates/sponsorship/_wall.html +++ /dev/null @@ -1,7 +0,0 @@ -{% load sponsorship_tags %} -{% sponsors as sponsors %} - diff --git a/symposion/templates/sponsorship/add.html b/symposion/templates/sponsorship/add.html deleted file mode 100644 index 4364c76b..00000000 --- a/symposion/templates/sponsorship/add.html +++ /dev/null @@ -1,22 +0,0 @@ -{% extends "site_base.html" %} - -{% load bootstrap_tags %} -{% load i18n %} -{% load boxes_tags %} - -{% block head_title %}{% trans "Add a Sponsor" %}{% endblock %} - -{% block body_class %}sponsorships{% endblock %} - -{% block body %} -
    - {% csrf_token %} - {% trans "Add a Sponsor" %} - {{ form|as_bootstrap }} -
    - - Cancel -
    -
    - -{% endblock %} diff --git a/symposion/templates/sponsorship/apply.html b/symposion/templates/sponsorship/apply.html deleted file mode 100644 index 1468d10b..00000000 --- a/symposion/templates/sponsorship/apply.html +++ /dev/null @@ -1,28 +0,0 @@ -{% extends "site_base.html" %} - -{% load bootstrap_tags %} -{% load i18n %} -{% load boxes_tags %} - -{% block head_title %}{% trans "Apply to be a Sponsor" %}{% endblock %} - -{% block body_class %}sponsorships{% endblock %} - -{% block body %} - - {% box "sponsorship-apply" %} - -
    - {% csrf_token %} - {% trans "Apply to Be a Sponsor" %} - {{ form|as_bootstrap }} -
    - - Cancel -

    - By submitting this sponsor application you are agreeing to the terms and conditions. -

    -
    -
    - -{% endblock %} diff --git a/symposion/templates/sponsorship/detail.html b/symposion/templates/sponsorship/detail.html deleted file mode 100644 index d322f8b0..00000000 --- a/symposion/templates/sponsorship/detail.html +++ /dev/null @@ -1,40 +0,0 @@ -{% extends "site_base.html" %} - -{% load bootstrap_tags %} -{% load i18n %} - -{% block head_title %}{{ sponsor }}{% endblock %} - -{% block page_title %}{% trans "Sponsorship" %}{% endblock %} - -{% block body %} -

    {{ sponsor.name }} ({{ sponsor.level }})

    - -
    - {% csrf_token %} -
    - {{ form|as_bootstrap }} -
    - -

    {{ sponsor.level }} Sponsor Benefits

    - - {{ formset.management_form }} - {{ formset.non_form_errors }} - - {% for form in formset.forms %} -
    - -
    - {{ form }} -

    {{ form.instance.benefit.description }}

    -
    -
    - {% endfor %} - -
    - - Cancel -
    - -
    -{% endblock %} diff --git a/symposion/templates/sponsorship/list.html b/symposion/templates/sponsorship/list.html deleted file mode 100644 index 0847f92b..00000000 --- a/symposion/templates/sponsorship/list.html +++ /dev/null @@ -1,44 +0,0 @@ -{% extends "site_base.html" %} - -{% load sponsorship_tags %} -{% load thumbnail %} -{% load i18n %} - -{% block head_title %}{% trans "About Our Sponsors" %}{% endblock %} - -{% block body_class %}sponsorships{% endblock %} - -{% block body_outer %} -
    -
    -

    {% trans "About Our Sponsors" %}

    - Learn how to become a sponsor - - {% sponsor_levels as levels %} - {% for level in levels %} - {% if level.sponsors %} -

    {{ level.name }}

    - - {% for sponsor in level.sponsors %} - {% if sponsor.website_logo %} -
    -
    -

    - - {{ sponsor.name }} - -

    -
    -
    -
    {{ sponsor.name }}
    -

    {{ sponsor.external_url }}

    -

    {{ sponsor.listing_text|urlize|linebreaks }}

    -
    -
    - {% endif %} - {% endfor %} - {% endif %} - {% endfor %} -
    -
    -{% endblock %} diff --git a/symposion/templates/teams/team_detail.html b/symposion/templates/teams/team_detail.html deleted file mode 100644 index eb70ed40..00000000 --- a/symposion/templates/teams/team_detail.html +++ /dev/null @@ -1,103 +0,0 @@ -{% extends "site_base.html" %} - -{% load bootstrap_tags %} - -{% block head_title %}{{ team.name }}{% endblock %} - -{% block body_outer %} -
    -
    -
    - {% if can_join %} -
    - {% csrf_token %} - -
    - {% endif %} - - {% if can_leave %} -
    - {% csrf_token %} - -
    - {% endif %} - - {% if can_apply %} -
    - {% csrf_token %} - -
    - {% endif %} -
    - -

    {{ team.name }}{% if state %} {{ state }}{% endif %}

    - - {% if team.description %}

    {{ team.description }}

    {% endif %} - - {% if state == "invited" %}

    You have been invited to join this team. Click join to the right to accept.

    {% endif %} - - {% if user.is_staff or state == "manager" %} - {% if team.managers %} -

    Managers

    - - {% for membership in team.managers %} - - - - - {% endfor %} -
    {{ membership.user.email }}{% if user == membership.user %} you{% endif %} -
    {% csrf_token %}
    -
    - {% endif %} - {% if team.members %} -

    Team Members

    - - {% for membership in team.members %} - - - - - {% endfor %} -
    {{ membership.user.email }}{% if user == membership.user %} you{% endif %} -
    {% csrf_token %}
    -
    - {% endif %} - {% if team.applicants and team.access == "application" %} -

    Applicants

    - - {% for membership in team.applicants %} - - - - - {% endfor %} -
    {{ membership.user.email }} -
    {% csrf_token %}
    -
    {% csrf_token %}
    -
    - {% endif %} - {% if team.invitees %} -

    Invitees

    - - {% for membership in team.invitees %} - - - - {% endfor %} -
    {{ membership.user.email }}
    - {% endif %} - {% if invite_form %} -
    - {% csrf_token %} - Invite User to Team - {{ invite_form|as_bootstrap }} -
    - -
    -
    - {% endif %} - {% endif %} -
    -
    -{% endblock %} From b694901831b2247a2b52d8a32cb5fe9d21d338cd Mon Sep 17 00:00:00 2001 From: Patrick Altman Date: Thu, 18 Dec 2014 08:14:17 -0600 Subject: [PATCH 579/751] Remove migrations These migrations were written for South. Now that we have Django 1.7, we will just use Django migrations once we hit a 1.0 release. --- symposion/boxes/migrations/0001_initial.py | 76 ----- symposion/boxes/migrations/__init__.py | 0 symposion/cms/migrations/0001_initial.py | 83 ----- symposion/cms/migrations/__init__.py | 0 .../conference/migrations/0001_initial.py | 61 ---- symposion/conference/migrations/__init__.py | 0 symposion/conference/models.py | 3 - .../proposals/migrations/0001_initial.py | 210 ------------ symposion/proposals/migrations/__init__.py | 0 symposion/reviews/migrations/0001_initial.py | 316 ------------------ symposion/reviews/migrations/__init__.py | 0 symposion/schedule/migrations/0001_initial.py | 284 ---------------- symposion/schedule/migrations/__init__.py | 0 symposion/speakers/migrations/0001_initial.py | 84 ----- symposion/speakers/migrations/__init__.py | 0 .../sponsorship/migrations/0001_initial.py | 188 ----------- symposion/sponsorship/migrations/__init__.py | 0 symposion/teams/migrations/0001_initial.py | 123 ------- symposion/teams/migrations/__init__.py | 0 19 files changed, 1428 deletions(-) delete mode 100644 symposion/boxes/migrations/0001_initial.py delete mode 100644 symposion/boxes/migrations/__init__.py delete mode 100644 symposion/cms/migrations/0001_initial.py delete mode 100644 symposion/cms/migrations/__init__.py delete mode 100644 symposion/conference/migrations/0001_initial.py delete mode 100644 symposion/conference/migrations/__init__.py delete mode 100644 symposion/proposals/migrations/0001_initial.py delete mode 100644 symposion/proposals/migrations/__init__.py delete mode 100644 symposion/reviews/migrations/0001_initial.py delete mode 100644 symposion/reviews/migrations/__init__.py delete mode 100644 symposion/schedule/migrations/0001_initial.py delete mode 100644 symposion/schedule/migrations/__init__.py delete mode 100644 symposion/speakers/migrations/0001_initial.py delete mode 100644 symposion/speakers/migrations/__init__.py delete mode 100644 symposion/sponsorship/migrations/0001_initial.py delete mode 100644 symposion/sponsorship/migrations/__init__.py delete mode 100644 symposion/teams/migrations/0001_initial.py delete mode 100644 symposion/teams/migrations/__init__.py diff --git a/symposion/boxes/migrations/0001_initial.py b/symposion/boxes/migrations/0001_initial.py deleted file mode 100644 index 7e0f7f75..00000000 --- a/symposion/boxes/migrations/0001_initial.py +++ /dev/null @@ -1,76 +0,0 @@ -# -*- coding: utf-8 -*- -from south.utils import datetime_utils as datetime -from south.db import db -from south.v2 import SchemaMigration -from django.db import models - - -class Migration(SchemaMigration): - - def forwards(self, orm): - # Adding model 'Box' - db.create_table('boxes_box', ( - ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('label', self.gf('django.db.models.fields.CharField')(max_length=100, db_index=True)), - ('content', self.gf('markitup.fields.MarkupField')(no_rendered_field=True, blank=True)), - ('created_by', self.gf('django.db.models.fields.related.ForeignKey')(related_name='boxes', to=orm['auth.User'])), - ('last_updated_by', self.gf('django.db.models.fields.related.ForeignKey')(related_name='updated_boxes', to=orm['auth.User'])), - ('_content_rendered', self.gf('django.db.models.fields.TextField')(blank=True)), - )) - db.send_create_signal('boxes', ['Box']) - - - def backwards(self, orm): - # Deleting model 'Box' - db.delete_table('boxes_box') - - - models = { - 'auth.group': { - 'Meta': {'object_name': 'Group'}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), - 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) - }, - 'auth.permission': { - 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, - 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - }, - 'auth.user': { - 'Meta': {'object_name': 'User'}, - 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), - 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), - 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), - 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) - }, - 'boxes.box': { - 'Meta': {'object_name': 'Box'}, - '_content_rendered': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'content': ('markitup.fields.MarkupField', [], {'no_rendered_field': 'True', 'blank': 'True'}), - 'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'boxes'", 'to': "orm['auth.User']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'label': ('django.db.models.fields.CharField', [], {'max_length': '100', 'db_index': 'True'}), - 'last_updated_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'updated_boxes'", 'to': "orm['auth.User']"}) - }, - 'contenttypes.contenttype': { - 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, - 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) - } - } - - complete_apps = ['boxes'] \ No newline at end of file diff --git a/symposion/boxes/migrations/__init__.py b/symposion/boxes/migrations/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/symposion/cms/migrations/0001_initial.py b/symposion/cms/migrations/0001_initial.py deleted file mode 100644 index beb391d2..00000000 --- a/symposion/cms/migrations/0001_initial.py +++ /dev/null @@ -1,83 +0,0 @@ -# -*- coding: utf-8 -*- -from south.utils import datetime_utils as datetime -from south.db import db -from south.v2 import SchemaMigration -from django.db import models - - -class Migration(SchemaMigration): - - def forwards(self, orm): - # Adding model 'Page' - db.create_table('cms_page', ( - ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('title', self.gf('django.db.models.fields.CharField')(max_length=100)), - ('path', self.gf('django.db.models.fields.CharField')(unique=True, max_length=100)), - ('body', self.gf('markitup.fields.MarkupField')(no_rendered_field=True)), - ('status', self.gf('django.db.models.fields.IntegerField')(default=2)), - ('publish_date', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now)), - ('created', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now)), - ('updated', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now)), - ('_body_rendered', self.gf('django.db.models.fields.TextField')(blank=True)), - )) - db.send_create_signal('cms', ['Page']) - - # Adding model 'File' - db.create_table('cms_file', ( - ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('file', self.gf('django.db.models.fields.files.FileField')(max_length=100)), - ('created', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now)), - )) - db.send_create_signal('cms', ['File']) - - - def backwards(self, orm): - # Deleting model 'Page' - db.delete_table('cms_page') - - # Deleting model 'File' - db.delete_table('cms_file') - - - models = { - 'cms.file': { - 'Meta': {'object_name': 'File'}, - 'created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'file': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) - }, - 'cms.page': { - 'Meta': {'object_name': 'Page'}, - '_body_rendered': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'body': ('markitup.fields.MarkupField', [], {'no_rendered_field': 'True'}), - 'created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'path': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}), - 'publish_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'status': ('django.db.models.fields.IntegerField', [], {'default': '2'}), - 'title': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'updated': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}) - }, - 'contenttypes.contenttype': { - 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, - 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) - }, - 'taggit.tag': { - 'Meta': {'object_name': 'Tag'}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '100'}) - }, - 'taggit.taggeditem': { - 'Meta': {'object_name': 'TaggedItem'}, - 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'taggit_taggeditem_tagged_items'", 'to': "orm['contenttypes.ContentType']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'object_id': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}), - 'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'taggit_taggeditem_items'", 'to': "orm['taggit.Tag']"}) - } - } - - complete_apps = ['cms'] \ No newline at end of file diff --git a/symposion/cms/migrations/__init__.py b/symposion/cms/migrations/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/symposion/conference/migrations/0001_initial.py b/symposion/conference/migrations/0001_initial.py deleted file mode 100644 index 7546ca52..00000000 --- a/symposion/conference/migrations/0001_initial.py +++ /dev/null @@ -1,61 +0,0 @@ -# -*- coding: utf-8 -*- -from south.utils import datetime_utils as datetime -from south.db import db -from south.v2 import SchemaMigration -from django.db import models - - -class Migration(SchemaMigration): - - def forwards(self, orm): - # Adding model 'Conference' - db.create_table('conference_conference', ( - ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('title', self.gf('django.db.models.fields.CharField')(max_length=100)), - ('start_date', self.gf('django.db.models.fields.DateField')(null=True, blank=True)), - ('end_date', self.gf('django.db.models.fields.DateField')(null=True, blank=True)), - ('timezone', self.gf('timezones.fields.TimeZoneField')(default='US/Eastern', max_length=100, blank=True)), - )) - db.send_create_signal('conference', ['Conference']) - - # Adding model 'Section' - db.create_table('conference_section', ( - ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('conference', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['conference.Conference'])), - ('name', self.gf('django.db.models.fields.CharField')(max_length=100)), - ('slug', self.gf('django.db.models.fields.SlugField')(max_length=50)), - ('start_date', self.gf('django.db.models.fields.DateField')(null=True, blank=True)), - ('end_date', self.gf('django.db.models.fields.DateField')(null=True, blank=True)), - )) - db.send_create_signal('conference', ['Section']) - - - def backwards(self, orm): - # Deleting model 'Conference' - db.delete_table('conference_conference') - - # Deleting model 'Section' - db.delete_table('conference_section') - - - models = { - 'conference.conference': { - 'Meta': {'object_name': 'Conference'}, - 'end_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'start_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), - 'timezone': ('timezones.fields.TimeZoneField', [], {'default': "'US/Eastern'", 'max_length': '100', 'blank': 'True'}), - 'title': ('django.db.models.fields.CharField', [], {'max_length': '100'}) - }, - 'conference.section': { - 'Meta': {'ordering': "['start_date']", 'object_name': 'Section'}, - 'conference': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['conference.Conference']"}), - 'end_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50'}), - 'start_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}) - } - } - - complete_apps = ['conference'] \ No newline at end of file diff --git a/symposion/conference/migrations/__init__.py b/symposion/conference/migrations/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/symposion/conference/models.py b/symposion/conference/models.py index db09050f..354403b6 100644 --- a/symposion/conference/models.py +++ b/symposion/conference/models.py @@ -3,9 +3,6 @@ from django.utils.translation import ugettext_lazy as _ from timezones.fields import TimeZoneField -from south.modelsinspector import add_introspection_rules -add_introspection_rules([], [r"^timezones\.fields\.TimeZoneField"]) - CONFERENCE_CACHE = {} diff --git a/symposion/proposals/migrations/0001_initial.py b/symposion/proposals/migrations/0001_initial.py deleted file mode 100644 index dc013cbd..00000000 --- a/symposion/proposals/migrations/0001_initial.py +++ /dev/null @@ -1,210 +0,0 @@ -# -*- coding: utf-8 -*- -import datetime -from south.db import db -from south.v2 import SchemaMigration -from django.db import models - - -class Migration(SchemaMigration): - - depends_on = ( - ("speakers", "0001_initial"), - ) - - def forwards(self, orm): - # Adding model 'ProposalSection' - db.create_table('proposals_proposalsection', ( - ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('section', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['conference.Section'], unique=True)), - ('start', self.gf('django.db.models.fields.DateTimeField')(null=True, blank=True)), - ('end', self.gf('django.db.models.fields.DateTimeField')(null=True, blank=True)), - ('closed', self.gf('django.db.models.fields.NullBooleanField')(null=True, blank=True)), - ('published', self.gf('django.db.models.fields.NullBooleanField')(null=True, blank=True)), - )) - db.send_create_signal('proposals', ['ProposalSection']) - - # Adding model 'ProposalKind' - db.create_table('proposals_proposalkind', ( - ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('section', self.gf('django.db.models.fields.related.ForeignKey')(related_name='proposal_kinds', to=orm['conference.Section'])), - ('name', self.gf('django.db.models.fields.CharField')(max_length=100)), - ('slug', self.gf('django.db.models.fields.SlugField')(max_length=50)), - )) - db.send_create_signal('proposals', ['ProposalKind']) - - # Adding model 'ProposalBase' - db.create_table('proposals_proposalbase', ( - ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('kind', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['proposals.ProposalKind'])), - ('title', self.gf('django.db.models.fields.CharField')(max_length=100)), - ('description', self.gf('django.db.models.fields.TextField')(max_length=400)), - ('abstract', self.gf('markitup.fields.MarkupField')(no_rendered_field=True)), - ('additional_notes', self.gf('markitup.fields.MarkupField')(no_rendered_field=True, blank=True)), - ('submitted', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now)), - ('speaker', self.gf('django.db.models.fields.related.ForeignKey')(related_name='proposals', to=orm['speakers.Speaker'])), - ('cancelled', self.gf('django.db.models.fields.BooleanField')(default=False)), - ('_abstract_rendered', self.gf('django.db.models.fields.TextField')(blank=True)), - ('_additional_notes_rendered', self.gf('django.db.models.fields.TextField')(blank=True)), - )) - db.send_create_signal('proposals', ['ProposalBase']) - - # Adding model 'AdditionalSpeaker' - db.create_table('proposals_proposalbase_additional_speakers', ( - ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('speaker', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['speakers.Speaker'])), - ('proposalbase', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['proposals.ProposalBase'])), - ('status', self.gf('django.db.models.fields.IntegerField')(default=1)), - )) - db.send_create_signal('proposals', ['AdditionalSpeaker']) - - # Adding unique constraint on 'AdditionalSpeaker', fields ['speaker', 'proposalbase'] - db.create_unique('proposals_proposalbase_additional_speakers', ['speaker_id', 'proposalbase_id']) - - # Adding model 'SupportingDocument' - db.create_table('proposals_supportingdocument', ( - ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('proposal', self.gf('django.db.models.fields.related.ForeignKey')(related_name='supporting_documents', to=orm['proposals.ProposalBase'])), - ('uploaded_by', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])), - ('created_at', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now)), - ('file', self.gf('django.db.models.fields.files.FileField')(max_length=100)), - ('description', self.gf('django.db.models.fields.CharField')(max_length=140)), - )) - db.send_create_signal('proposals', ['SupportingDocument']) - - - def backwards(self, orm): - # Removing unique constraint on 'AdditionalSpeaker', fields ['speaker', 'proposalbase'] - db.delete_unique('proposals_proposalbase_additional_speakers', ['speaker_id', 'proposalbase_id']) - - # Deleting model 'ProposalSection' - db.delete_table('proposals_proposalsection') - - # Deleting model 'ProposalKind' - db.delete_table('proposals_proposalkind') - - # Deleting model 'ProposalBase' - db.delete_table('proposals_proposalbase') - - # Deleting model 'AdditionalSpeaker' - db.delete_table('proposals_proposalbase_additional_speakers') - - # Deleting model 'SupportingDocument' - db.delete_table('proposals_supportingdocument') - - models = { - 'auth.group': { - 'Meta': {'object_name': 'Group'}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), - 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) - }, - 'auth.permission': { - 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, - 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - }, - 'auth.user': { - 'Meta': {'object_name': 'User'}, - 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), - 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), - 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), - 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) - }, - 'conference.conference': { - 'Meta': {'object_name': 'Conference'}, - 'end_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'start_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), - 'timezone': ('timezones.fields.TimeZoneField', [], {'default': "'US/Eastern'", 'max_length': '100', 'blank': 'True'}), - 'title': ('django.db.models.fields.CharField', [], {'max_length': '100'}) - }, - 'conference.section': { - 'Meta': {'ordering': "['start_date']", 'object_name': 'Section'}, - 'conference': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['conference.Conference']"}), - 'end_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50'}), - 'start_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}) - }, - 'contenttypes.contenttype': { - 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, - 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) - }, - 'proposals.additionalspeaker': { - 'Meta': {'unique_together': "(('speaker', 'proposalbase'),)", 'object_name': 'AdditionalSpeaker', 'db_table': "'proposals_proposalbase_additional_speakers'"}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'proposalbase': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['proposals.ProposalBase']"}), - 'speaker': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['speakers.Speaker']"}), - 'status': ('django.db.models.fields.IntegerField', [], {'default': '1'}) - }, - 'proposals.proposalbase': { - 'Meta': {'object_name': 'ProposalBase'}, - '_abstract_rendered': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - '_additional_notes_rendered': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'abstract': ('markitup.fields.MarkupField', [], {'no_rendered_field': 'True'}), - 'additional_notes': ('markitup.fields.MarkupField', [], {'no_rendered_field': 'True', 'blank': 'True'}), - 'additional_speakers': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['speakers.Speaker']", 'symmetrical': 'False', 'through': "orm['proposals.AdditionalSpeaker']", 'blank': 'True'}), - 'cancelled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'description': ('django.db.models.fields.TextField', [], {'max_length': '400'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'kind': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['proposals.ProposalKind']"}), - 'speaker': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'proposals'", 'to': "orm['speakers.Speaker']"}), - 'submitted': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'title': ('django.db.models.fields.CharField', [], {'max_length': '100'}) - }, - 'proposals.proposalkind': { - 'Meta': {'object_name': 'ProposalKind'}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'section': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'proposal_kinds'", 'to': "orm['conference.Section']"}), - 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50'}) - }, - 'proposals.proposalsection': { - 'Meta': {'object_name': 'ProposalSection'}, - 'closed': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), - 'end': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'published': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), - 'section': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['conference.Section']", 'unique': 'True'}), - 'start': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}) - }, - 'proposals.supportingdocument': { - 'Meta': {'object_name': 'SupportingDocument'}, - 'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'description': ('django.db.models.fields.CharField', [], {'max_length': '140'}), - 'file': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'proposal': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'supporting_documents'", 'to': "orm['proposals.ProposalBase']"}), - 'uploaded_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) - }, - 'speakers.speaker': { - 'Meta': {'ordering': "['name']", 'object_name': 'Speaker'}, - '_biography_rendered': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'annotation': ('django.db.models.fields.TextField', [], {}), - 'biography': ('markitup.fields.MarkupField', [], {'no_rendered_field': 'True', 'blank': 'True'}), - 'created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'invite_email': ('django.db.models.fields.CharField', [], {'max_length': '200', 'unique': 'True', 'null': 'True', 'db_index': 'True'}), - 'invite_token': ('django.db.models.fields.CharField', [], {'max_length': '40', 'db_index': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'photo': ('django.db.models.fields.files.ImageField', [], {'max_length': '100', 'blank': 'True'}), - 'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'speaker_profile'", 'unique': 'True', 'null': 'True', 'to': "orm['auth.User']"}) - } - } - - complete_apps = ['proposals'] diff --git a/symposion/proposals/migrations/__init__.py b/symposion/proposals/migrations/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/symposion/reviews/migrations/0001_initial.py b/symposion/reviews/migrations/0001_initial.py deleted file mode 100644 index 76df4e2f..00000000 --- a/symposion/reviews/migrations/0001_initial.py +++ /dev/null @@ -1,316 +0,0 @@ -# -*- coding: utf-8 -*- -from south.utils import datetime_utils as datetime -from south.db import db -from south.v2 import SchemaMigration -from django.db import models - - -class Migration(SchemaMigration): - - def forwards(self, orm): - # Adding model 'ReviewAssignment' - db.create_table('reviews_reviewassignment', ( - ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('proposal', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['proposals.ProposalBase'])), - ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])), - ('origin', self.gf('django.db.models.fields.IntegerField')()), - ('assigned_at', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now)), - ('opted_out', self.gf('django.db.models.fields.BooleanField')(default=False)), - )) - db.send_create_signal('reviews', ['ReviewAssignment']) - - # Adding model 'ProposalMessage' - db.create_table('reviews_proposalmessage', ( - ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('proposal', self.gf('django.db.models.fields.related.ForeignKey')(related_name='messages', to=orm['proposals.ProposalBase'])), - ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])), - ('message', self.gf('markitup.fields.MarkupField')(no_rendered_field=True)), - ('submitted_at', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now)), - ('_message_rendered', self.gf('django.db.models.fields.TextField')(blank=True)), - )) - db.send_create_signal('reviews', ['ProposalMessage']) - - # Adding model 'Review' - db.create_table('reviews_review', ( - ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('proposal', self.gf('django.db.models.fields.related.ForeignKey')(related_name='reviews', to=orm['proposals.ProposalBase'])), - ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])), - ('vote', self.gf('django.db.models.fields.CharField')(max_length=2, blank=True)), - ('comment', self.gf('markitup.fields.MarkupField')(no_rendered_field=True)), - ('submitted_at', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now)), - ('_comment_rendered', self.gf('django.db.models.fields.TextField')(blank=True)), - )) - db.send_create_signal('reviews', ['Review']) - - # Adding model 'LatestVote' - db.create_table('reviews_latestvote', ( - ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('proposal', self.gf('django.db.models.fields.related.ForeignKey')(related_name='votes', to=orm['proposals.ProposalBase'])), - ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])), - ('vote', self.gf('django.db.models.fields.CharField')(max_length=2)), - ('submitted_at', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now)), - )) - db.send_create_signal('reviews', ['LatestVote']) - - # Adding unique constraint on 'LatestVote', fields ['proposal', 'user'] - db.create_unique('reviews_latestvote', ['proposal_id', 'user_id']) - - # Adding model 'ProposalResult' - db.create_table('reviews_proposalresult', ( - ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('proposal', self.gf('django.db.models.fields.related.OneToOneField')(related_name='result', unique=True, to=orm['proposals.ProposalBase'])), - ('score', self.gf('django.db.models.fields.DecimalField')(default='0.00', max_digits=5, decimal_places=2)), - ('comment_count', self.gf('django.db.models.fields.PositiveIntegerField')(default=0)), - ('vote_count', self.gf('django.db.models.fields.PositiveIntegerField')(default=0)), - ('plus_one', self.gf('django.db.models.fields.PositiveIntegerField')(default=0)), - ('plus_zero', self.gf('django.db.models.fields.PositiveIntegerField')(default=0)), - ('minus_zero', self.gf('django.db.models.fields.PositiveIntegerField')(default=0)), - ('minus_one', self.gf('django.db.models.fields.PositiveIntegerField')(default=0)), - ('accepted', self.gf('django.db.models.fields.NullBooleanField')(default=None, null=True, blank=True)), - ('status', self.gf('django.db.models.fields.CharField')(default='undecided', max_length=20)), - )) - db.send_create_signal('reviews', ['ProposalResult']) - - # Adding model 'Comment' - db.create_table('reviews_comment', ( - ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('proposal', self.gf('django.db.models.fields.related.ForeignKey')(related_name='comments', to=orm['proposals.ProposalBase'])), - ('commenter', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])), - ('text', self.gf('markitup.fields.MarkupField')(no_rendered_field=True)), - ('public', self.gf('django.db.models.fields.BooleanField')(default=False)), - ('commented_at', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now)), - ('_text_rendered', self.gf('django.db.models.fields.TextField')(blank=True)), - )) - db.send_create_signal('reviews', ['Comment']) - - # Adding model 'NotificationTemplate' - db.create_table('reviews_notificationtemplate', ( - ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('label', self.gf('django.db.models.fields.CharField')(max_length=100)), - ('from_address', self.gf('django.db.models.fields.EmailField')(max_length=75)), - ('subject', self.gf('django.db.models.fields.CharField')(max_length=100)), - ('body', self.gf('django.db.models.fields.TextField')()), - )) - db.send_create_signal('reviews', ['NotificationTemplate']) - - # Adding model 'ResultNotification' - db.create_table('reviews_resultnotification', ( - ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('proposal', self.gf('django.db.models.fields.related.ForeignKey')(related_name='notifications', to=orm['proposals.ProposalBase'])), - ('template', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['reviews.NotificationTemplate'], null=True, on_delete=models.SET_NULL, blank=True)), - ('timestamp', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now)), - ('to_address', self.gf('django.db.models.fields.EmailField')(max_length=75)), - ('from_address', self.gf('django.db.models.fields.EmailField')(max_length=75)), - ('subject', self.gf('django.db.models.fields.CharField')(max_length=100)), - ('body', self.gf('django.db.models.fields.TextField')()), - )) - db.send_create_signal('reviews', ['ResultNotification']) - - - def backwards(self, orm): - # Removing unique constraint on 'LatestVote', fields ['proposal', 'user'] - db.delete_unique('reviews_latestvote', ['proposal_id', 'user_id']) - - # Deleting model 'ReviewAssignment' - db.delete_table('reviews_reviewassignment') - - # Deleting model 'ProposalMessage' - db.delete_table('reviews_proposalmessage') - - # Deleting model 'Review' - db.delete_table('reviews_review') - - # Deleting model 'LatestVote' - db.delete_table('reviews_latestvote') - - # Deleting model 'ProposalResult' - db.delete_table('reviews_proposalresult') - - # Deleting model 'Comment' - db.delete_table('reviews_comment') - - # Deleting model 'NotificationTemplate' - db.delete_table('reviews_notificationtemplate') - - # Deleting model 'ResultNotification' - db.delete_table('reviews_resultnotification') - - - models = { - 'auth.group': { - 'Meta': {'object_name': 'Group'}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), - 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) - }, - 'auth.permission': { - 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, - 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - }, - 'auth.user': { - 'Meta': {'object_name': 'User'}, - 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), - 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), - 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), - 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) - }, - 'conference.conference': { - 'Meta': {'object_name': 'Conference'}, - 'end_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'start_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), - 'timezone': ('timezones.fields.TimeZoneField', [], {'default': "'US/Eastern'", 'max_length': '100', 'blank': 'True'}), - 'title': ('django.db.models.fields.CharField', [], {'max_length': '100'}) - }, - 'conference.section': { - 'Meta': {'ordering': "['start_date']", 'object_name': 'Section'}, - 'conference': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['conference.Conference']"}), - 'end_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50'}), - 'start_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}) - }, - 'contenttypes.contenttype': { - 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, - 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) - }, - 'proposals.additionalspeaker': { - 'Meta': {'unique_together': "(('speaker', 'proposalbase'),)", 'object_name': 'AdditionalSpeaker', 'db_table': "'proposals_proposalbase_additional_speakers'"}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'proposalbase': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['proposals.ProposalBase']"}), - 'speaker': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['speakers.Speaker']"}), - 'status': ('django.db.models.fields.IntegerField', [], {'default': '1'}) - }, - 'proposals.proposalbase': { - 'Meta': {'object_name': 'ProposalBase'}, - '_abstract_rendered': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - '_additional_notes_rendered': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'abstract': ('markitup.fields.MarkupField', [], {'no_rendered_field': 'True'}), - 'additional_notes': ('markitup.fields.MarkupField', [], {'no_rendered_field': 'True', 'blank': 'True'}), - 'additional_speakers': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['speakers.Speaker']", 'symmetrical': 'False', 'through': "orm['proposals.AdditionalSpeaker']", 'blank': 'True'}), - 'cancelled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'description': ('django.db.models.fields.TextField', [], {'max_length': '400'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'kind': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['proposals.ProposalKind']"}), - 'speaker': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'proposals'", 'to': "orm['speakers.Speaker']"}), - 'submitted': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'title': ('django.db.models.fields.CharField', [], {'max_length': '100'}) - }, - 'proposals.proposalkind': { - 'Meta': {'object_name': 'ProposalKind'}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'section': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'proposal_kinds'", 'to': "orm['conference.Section']"}), - 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50'}) - }, - 'reviews.comment': { - 'Meta': {'object_name': 'Comment'}, - '_text_rendered': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'commented_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'commenter': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'proposal': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'comments'", 'to': "orm['proposals.ProposalBase']"}), - 'public': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'text': ('markitup.fields.MarkupField', [], {'no_rendered_field': 'True'}) - }, - 'reviews.latestvote': { - 'Meta': {'unique_together': "[('proposal', 'user')]", 'object_name': 'LatestVote'}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'proposal': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'votes'", 'to': "orm['proposals.ProposalBase']"}), - 'submitted_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}), - 'vote': ('django.db.models.fields.CharField', [], {'max_length': '2'}) - }, - 'reviews.notificationtemplate': { - 'Meta': {'object_name': 'NotificationTemplate'}, - 'body': ('django.db.models.fields.TextField', [], {}), - 'from_address': ('django.db.models.fields.EmailField', [], {'max_length': '75'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'subject': ('django.db.models.fields.CharField', [], {'max_length': '100'}) - }, - 'reviews.proposalmessage': { - 'Meta': {'ordering': "['submitted_at']", 'object_name': 'ProposalMessage'}, - '_message_rendered': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'message': ('markitup.fields.MarkupField', [], {'no_rendered_field': 'True'}), - 'proposal': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'messages'", 'to': "orm['proposals.ProposalBase']"}), - 'submitted_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) - }, - 'reviews.proposalresult': { - 'Meta': {'object_name': 'ProposalResult'}, - 'accepted': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), - 'comment_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'minus_one': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), - 'minus_zero': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), - 'plus_one': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), - 'plus_zero': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), - 'proposal': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'result'", 'unique': 'True', 'to': "orm['proposals.ProposalBase']"}), - 'score': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '5', 'decimal_places': '2'}), - 'status': ('django.db.models.fields.CharField', [], {'default': "'undecided'", 'max_length': '20'}), - 'vote_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}) - }, - 'reviews.resultnotification': { - 'Meta': {'object_name': 'ResultNotification'}, - 'body': ('django.db.models.fields.TextField', [], {}), - 'from_address': ('django.db.models.fields.EmailField', [], {'max_length': '75'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'proposal': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'notifications'", 'to': "orm['proposals.ProposalBase']"}), - 'subject': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'template': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['reviews.NotificationTemplate']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}), - 'timestamp': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'to_address': ('django.db.models.fields.EmailField', [], {'max_length': '75'}) - }, - 'reviews.review': { - 'Meta': {'object_name': 'Review'}, - '_comment_rendered': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'comment': ('markitup.fields.MarkupField', [], {'no_rendered_field': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'proposal': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'reviews'", 'to': "orm['proposals.ProposalBase']"}), - 'submitted_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}), - 'vote': ('django.db.models.fields.CharField', [], {'max_length': '2', 'blank': 'True'}) - }, - 'reviews.reviewassignment': { - 'Meta': {'object_name': 'ReviewAssignment'}, - 'assigned_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'opted_out': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'origin': ('django.db.models.fields.IntegerField', [], {}), - 'proposal': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['proposals.ProposalBase']"}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) - }, - 'speakers.speaker': { - 'Meta': {'ordering': "['name']", 'object_name': 'Speaker'}, - '_biography_rendered': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'annotation': ('django.db.models.fields.TextField', [], {}), - 'biography': ('markitup.fields.MarkupField', [], {'no_rendered_field': 'True', 'blank': 'True'}), - 'created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'invite_email': ('django.db.models.fields.CharField', [], {'max_length': '200', 'unique': 'True', 'null': 'True', 'db_index': 'True'}), - 'invite_token': ('django.db.models.fields.CharField', [], {'max_length': '40', 'db_index': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'photo': ('django.db.models.fields.files.ImageField', [], {'max_length': '100', 'blank': 'True'}), - 'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'speaker_profile'", 'unique': 'True', 'null': 'True', 'to': "orm['auth.User']"}) - } - } - - complete_apps = ['reviews'] \ No newline at end of file diff --git a/symposion/reviews/migrations/__init__.py b/symposion/reviews/migrations/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/symposion/schedule/migrations/0001_initial.py b/symposion/schedule/migrations/0001_initial.py deleted file mode 100644 index 3798087d..00000000 --- a/symposion/schedule/migrations/0001_initial.py +++ /dev/null @@ -1,284 +0,0 @@ -# -*- coding: utf-8 -*- -from south.utils import datetime_utils as datetime -from south.db import db -from south.v2 import SchemaMigration -from django.db import models - - -class Migration(SchemaMigration): - - def forwards(self, orm): - # Adding model 'Schedule' - db.create_table('schedule_schedule', ( - ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('section', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['conference.Section'], unique=True)), - ('published', self.gf('django.db.models.fields.BooleanField')(default=True)), - ('hidden', self.gf('django.db.models.fields.BooleanField')(default=False)), - )) - db.send_create_signal('schedule', ['Schedule']) - - # Adding model 'Day' - db.create_table('schedule_day', ( - ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('schedule', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['schedule.Schedule'])), - ('date', self.gf('django.db.models.fields.DateField')()), - )) - db.send_create_signal('schedule', ['Day']) - - # Adding unique constraint on 'Day', fields ['schedule', 'date'] - db.create_unique('schedule_day', ['schedule_id', 'date']) - - # Adding model 'Room' - db.create_table('schedule_room', ( - ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('schedule', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['schedule.Schedule'])), - ('name', self.gf('django.db.models.fields.CharField')(max_length=65)), - ('order', self.gf('django.db.models.fields.PositiveIntegerField')()), - )) - db.send_create_signal('schedule', ['Room']) - - # Adding model 'SlotKind' - db.create_table('schedule_slotkind', ( - ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('schedule', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['schedule.Schedule'])), - ('label', self.gf('django.db.models.fields.CharField')(max_length=50)), - )) - db.send_create_signal('schedule', ['SlotKind']) - - # Adding model 'Slot' - db.create_table('schedule_slot', ( - ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('day', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['schedule.Day'])), - ('kind', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['schedule.SlotKind'])), - ('start', self.gf('django.db.models.fields.TimeField')()), - ('end', self.gf('django.db.models.fields.TimeField')()), - ('content_override', self.gf('markitup.fields.MarkupField')(no_rendered_field=True, blank=True)), - ('_content_override_rendered', self.gf('django.db.models.fields.TextField')(blank=True)), - )) - db.send_create_signal('schedule', ['Slot']) - - # Adding model 'SlotRoom' - db.create_table('schedule_slotroom', ( - ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('slot', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['schedule.Slot'])), - ('room', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['schedule.Room'])), - )) - db.send_create_signal('schedule', ['SlotRoom']) - - # Adding unique constraint on 'SlotRoom', fields ['slot', 'room'] - db.create_unique('schedule_slotroom', ['slot_id', 'room_id']) - - # Adding model 'Presentation' - db.create_table('schedule_presentation', ( - ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('slot', self.gf('django.db.models.fields.related.OneToOneField')(blank=True, related_name='content_ptr', unique=True, null=True, to=orm['schedule.Slot'])), - ('title', self.gf('django.db.models.fields.CharField')(max_length=100)), - ('description', self.gf('markitup.fields.MarkupField')(no_rendered_field=True)), - ('abstract', self.gf('markitup.fields.MarkupField')(no_rendered_field=True)), - ('speaker', self.gf('django.db.models.fields.related.ForeignKey')(related_name='presentations', to=orm['speakers.Speaker'])), - ('cancelled', self.gf('django.db.models.fields.BooleanField')(default=False)), - ('proposal_base', self.gf('django.db.models.fields.related.OneToOneField')(related_name='presentation', unique=True, to=orm['proposals.ProposalBase'])), - ('section', self.gf('django.db.models.fields.related.ForeignKey')(related_name='presentations', to=orm['conference.Section'])), - ('_description_rendered', self.gf('django.db.models.fields.TextField')(blank=True)), - ('_abstract_rendered', self.gf('django.db.models.fields.TextField')(blank=True)), - )) - db.send_create_signal('schedule', ['Presentation']) - - # Adding M2M table for field additional_speakers on 'Presentation' - m2m_table_name = db.shorten_name('schedule_presentation_additional_speakers') - db.create_table(m2m_table_name, ( - ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), - ('presentation', models.ForeignKey(orm['schedule.presentation'], null=False)), - ('speaker', models.ForeignKey(orm['speakers.speaker'], null=False)) - )) - db.create_unique(m2m_table_name, ['presentation_id', 'speaker_id']) - - - def backwards(self, orm): - # Removing unique constraint on 'SlotRoom', fields ['slot', 'room'] - db.delete_unique('schedule_slotroom', ['slot_id', 'room_id']) - - # Removing unique constraint on 'Day', fields ['schedule', 'date'] - db.delete_unique('schedule_day', ['schedule_id', 'date']) - - # Deleting model 'Schedule' - db.delete_table('schedule_schedule') - - # Deleting model 'Day' - db.delete_table('schedule_day') - - # Deleting model 'Room' - db.delete_table('schedule_room') - - # Deleting model 'SlotKind' - db.delete_table('schedule_slotkind') - - # Deleting model 'Slot' - db.delete_table('schedule_slot') - - # Deleting model 'SlotRoom' - db.delete_table('schedule_slotroom') - - # Deleting model 'Presentation' - db.delete_table('schedule_presentation') - - # Removing M2M table for field additional_speakers on 'Presentation' - db.delete_table(db.shorten_name('schedule_presentation_additional_speakers')) - - - models = { - 'auth.group': { - 'Meta': {'object_name': 'Group'}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), - 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) - }, - 'auth.permission': { - 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, - 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - }, - 'auth.user': { - 'Meta': {'object_name': 'User'}, - 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), - 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), - 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), - 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) - }, - 'conference.conference': { - 'Meta': {'object_name': 'Conference'}, - 'end_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'start_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), - 'timezone': ('timezones.fields.TimeZoneField', [], {'default': "'US/Eastern'", 'max_length': '100', 'blank': 'True'}), - 'title': ('django.db.models.fields.CharField', [], {'max_length': '100'}) - }, - 'conference.section': { - 'Meta': {'ordering': "['start_date']", 'object_name': 'Section'}, - 'conference': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['conference.Conference']"}), - 'end_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50'}), - 'start_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}) - }, - 'contenttypes.contenttype': { - 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, - 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) - }, - 'proposals.additionalspeaker': { - 'Meta': {'unique_together': "(('speaker', 'proposalbase'),)", 'object_name': 'AdditionalSpeaker', 'db_table': "'proposals_proposalbase_additional_speakers'"}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'proposalbase': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['proposals.ProposalBase']"}), - 'speaker': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['speakers.Speaker']"}), - 'status': ('django.db.models.fields.IntegerField', [], {'default': '1'}) - }, - 'proposals.proposalbase': { - 'Meta': {'object_name': 'ProposalBase'}, - '_abstract_rendered': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - '_additional_notes_rendered': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'abstract': ('markitup.fields.MarkupField', [], {'no_rendered_field': 'True'}), - 'additional_notes': ('markitup.fields.MarkupField', [], {'no_rendered_field': 'True', 'blank': 'True'}), - 'additional_speakers': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['speakers.Speaker']", 'symmetrical': 'False', 'through': "orm['proposals.AdditionalSpeaker']", 'blank': 'True'}), - 'cancelled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'description': ('django.db.models.fields.TextField', [], {'max_length': '400'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'kind': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['proposals.ProposalKind']"}), - 'speaker': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'proposals'", 'to': "orm['speakers.Speaker']"}), - 'submitted': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'title': ('django.db.models.fields.CharField', [], {'max_length': '100'}) - }, - 'proposals.proposalkind': { - 'Meta': {'object_name': 'ProposalKind'}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'section': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'proposal_kinds'", 'to': "orm['conference.Section']"}), - 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50'}) - }, - 'schedule.day': { - 'Meta': {'ordering': "['date']", 'unique_together': "[('schedule', 'date')]", 'object_name': 'Day'}, - 'date': ('django.db.models.fields.DateField', [], {}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'schedule': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['schedule.Schedule']"}) - }, - 'schedule.presentation': { - 'Meta': {'ordering': "['slot']", 'object_name': 'Presentation'}, - '_abstract_rendered': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - '_description_rendered': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'abstract': ('markitup.fields.MarkupField', [], {'no_rendered_field': 'True'}), - 'additional_speakers': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'copresentations'", 'blank': 'True', 'to': "orm['speakers.Speaker']"}), - 'cancelled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'description': ('markitup.fields.MarkupField', [], {'no_rendered_field': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'proposal_base': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'presentation'", 'unique': 'True', 'to': "orm['proposals.ProposalBase']"}), - 'section': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'presentations'", 'to': "orm['conference.Section']"}), - 'slot': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'content_ptr'", 'unique': 'True', 'null': 'True', 'to': "orm['schedule.Slot']"}), - 'speaker': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'presentations'", 'to': "orm['speakers.Speaker']"}), - 'title': ('django.db.models.fields.CharField', [], {'max_length': '100'}) - }, - 'schedule.room': { - 'Meta': {'object_name': 'Room'}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '65'}), - 'order': ('django.db.models.fields.PositiveIntegerField', [], {}), - 'schedule': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['schedule.Schedule']"}) - }, - 'schedule.schedule': { - 'Meta': {'ordering': "['section']", 'object_name': 'Schedule'}, - 'hidden': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'published': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'section': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['conference.Section']", 'unique': 'True'}) - }, - 'schedule.slot': { - 'Meta': {'ordering': "['day', 'start', 'end']", 'object_name': 'Slot'}, - '_content_override_rendered': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'content_override': ('markitup.fields.MarkupField', [], {'no_rendered_field': 'True', 'blank': 'True'}), - 'day': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['schedule.Day']"}), - 'end': ('django.db.models.fields.TimeField', [], {}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'kind': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['schedule.SlotKind']"}), - 'start': ('django.db.models.fields.TimeField', [], {}) - }, - 'schedule.slotkind': { - 'Meta': {'object_name': 'SlotKind'}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'label': ('django.db.models.fields.CharField', [], {'max_length': '50'}), - 'schedule': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['schedule.Schedule']"}) - }, - 'schedule.slotroom': { - 'Meta': {'ordering': "['slot', 'room__order']", 'unique_together': "[('slot', 'room')]", 'object_name': 'SlotRoom'}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'room': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['schedule.Room']"}), - 'slot': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['schedule.Slot']"}) - }, - 'speakers.speaker': { - 'Meta': {'ordering': "['name']", 'object_name': 'Speaker'}, - '_biography_rendered': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'annotation': ('django.db.models.fields.TextField', [], {}), - 'biography': ('markitup.fields.MarkupField', [], {'no_rendered_field': 'True', 'blank': 'True'}), - 'created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'invite_email': ('django.db.models.fields.CharField', [], {'max_length': '200', 'unique': 'True', 'null': 'True', 'db_index': 'True'}), - 'invite_token': ('django.db.models.fields.CharField', [], {'max_length': '40', 'db_index': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'photo': ('django.db.models.fields.files.ImageField', [], {'max_length': '100', 'blank': 'True'}), - 'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'speaker_profile'", 'unique': 'True', 'null': 'True', 'to': "orm['auth.User']"}) - } - } - - complete_apps = ['schedule'] \ No newline at end of file diff --git a/symposion/schedule/migrations/__init__.py b/symposion/schedule/migrations/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/symposion/speakers/migrations/0001_initial.py b/symposion/speakers/migrations/0001_initial.py deleted file mode 100644 index 1db93f8c..00000000 --- a/symposion/speakers/migrations/0001_initial.py +++ /dev/null @@ -1,84 +0,0 @@ -# -*- coding: utf-8 -*- -from south.utils import datetime_utils as datetime -from south.db import db -from south.v2 import SchemaMigration -from django.db import models - - -class Migration(SchemaMigration): - - def forwards(self, orm): - # Adding model 'Speaker' - db.create_table('speakers_speaker', ( - ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('user', self.gf('django.db.models.fields.related.OneToOneField')(related_name='speaker_profile', unique=True, null=True, to=orm['auth.User'])), - ('name', self.gf('django.db.models.fields.CharField')(max_length=100)), - ('biography', self.gf('markitup.fields.MarkupField')(no_rendered_field=True, blank=True)), - ('photo', self.gf('django.db.models.fields.files.ImageField')(max_length=100, blank=True)), - ('annotation', self.gf('django.db.models.fields.TextField')()), - ('invite_email', self.gf('django.db.models.fields.CharField')(max_length=200, unique=True, null=True, db_index=True)), - ('invite_token', self.gf('django.db.models.fields.CharField')(max_length=40, db_index=True)), - ('created', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now)), - ('_biography_rendered', self.gf('django.db.models.fields.TextField')(blank=True)), - )) - db.send_create_signal('speakers', ['Speaker']) - - - def backwards(self, orm): - # Deleting model 'Speaker' - db.delete_table('speakers_speaker') - - - models = { - 'auth.group': { - 'Meta': {'object_name': 'Group'}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), - 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) - }, - 'auth.permission': { - 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, - 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - }, - 'auth.user': { - 'Meta': {'object_name': 'User'}, - 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), - 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), - 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), - 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) - }, - 'contenttypes.contenttype': { - 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, - 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) - }, - 'speakers.speaker': { - 'Meta': {'ordering': "['name']", 'object_name': 'Speaker'}, - '_biography_rendered': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'annotation': ('django.db.models.fields.TextField', [], {}), - 'biography': ('markitup.fields.MarkupField', [], {'no_rendered_field': 'True', 'blank': 'True'}), - 'created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'invite_email': ('django.db.models.fields.CharField', [], {'max_length': '200', 'unique': 'True', 'null': 'True', 'db_index': 'True'}), - 'invite_token': ('django.db.models.fields.CharField', [], {'max_length': '40', 'db_index': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'photo': ('django.db.models.fields.files.ImageField', [], {'max_length': '100', 'blank': 'True'}), - 'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'speaker_profile'", 'unique': 'True', 'null': 'True', 'to': "orm['auth.User']"}) - } - } - - complete_apps = ['speakers'] \ No newline at end of file diff --git a/symposion/speakers/migrations/__init__.py b/symposion/speakers/migrations/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/symposion/sponsorship/migrations/0001_initial.py b/symposion/sponsorship/migrations/0001_initial.py deleted file mode 100644 index 356b8a8d..00000000 --- a/symposion/sponsorship/migrations/0001_initial.py +++ /dev/null @@ -1,188 +0,0 @@ -# -*- coding: utf-8 -*- -from south.utils import datetime_utils as datetime -from south.db import db -from south.v2 import SchemaMigration -from django.db import models - - -class Migration(SchemaMigration): - - depends_on = ( - ("conference", "0001_initial"), - ) - - def forwards(self, orm): - # Adding model 'SponsorLevel' - db.create_table('sponsorship_sponsorlevel', ( - ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('conference', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['conference.Conference'])), - ('name', self.gf('django.db.models.fields.CharField')(max_length=100)), - ('order', self.gf('django.db.models.fields.IntegerField')(default=0)), - ('cost', self.gf('django.db.models.fields.PositiveIntegerField')()), - ('description', self.gf('django.db.models.fields.TextField')(blank=True)), - )) - db.send_create_signal('sponsorship', ['SponsorLevel']) - - # Adding model 'Sponsor' - db.create_table('sponsorship_sponsor', ( - ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('applicant', self.gf('django.db.models.fields.related.ForeignKey')(related_name='sponsorships', null=True, to=orm['auth.User'])), - ('name', self.gf('django.db.models.fields.CharField')(max_length=100)), - ('external_url', self.gf('django.db.models.fields.URLField')(max_length=200)), - ('annotation', self.gf('django.db.models.fields.TextField')(blank=True)), - ('contact_name', self.gf('django.db.models.fields.CharField')(max_length=100)), - ('contact_email', self.gf('django.db.models.fields.EmailField')(max_length=75)), - ('level', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['sponsorship.SponsorLevel'])), - ('added', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now)), - ('active', self.gf('django.db.models.fields.BooleanField')(default=False)), - ('sponsor_logo', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='+', null=True, to=orm['sponsorship.SponsorBenefit'])), - )) - db.send_create_signal('sponsorship', ['Sponsor']) - - # Adding model 'Benefit' - db.create_table('sponsorship_benefit', ( - ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('name', self.gf('django.db.models.fields.CharField')(max_length=100)), - ('description', self.gf('django.db.models.fields.TextField')(blank=True)), - ('type', self.gf('django.db.models.fields.CharField')(default='simple', max_length=10)), - )) - db.send_create_signal('sponsorship', ['Benefit']) - - # Adding model 'BenefitLevel' - db.create_table('sponsorship_benefitlevel', ( - ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('benefit', self.gf('django.db.models.fields.related.ForeignKey')(related_name='benefit_levels', to=orm['sponsorship.Benefit'])), - ('level', self.gf('django.db.models.fields.related.ForeignKey')(related_name='benefit_levels', to=orm['sponsorship.SponsorLevel'])), - ('max_words', self.gf('django.db.models.fields.PositiveIntegerField')(null=True, blank=True)), - ('other_limits', self.gf('django.db.models.fields.CharField')(max_length=200, blank=True)), - )) - db.send_create_signal('sponsorship', ['BenefitLevel']) - - # Adding model 'SponsorBenefit' - db.create_table('sponsorship_sponsorbenefit', ( - ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('sponsor', self.gf('django.db.models.fields.related.ForeignKey')(related_name='sponsor_benefits', to=orm['sponsorship.Sponsor'])), - ('benefit', self.gf('django.db.models.fields.related.ForeignKey')(related_name='sponsor_benefits', to=orm['sponsorship.Benefit'])), - ('active', self.gf('django.db.models.fields.BooleanField')(default=True)), - ('max_words', self.gf('django.db.models.fields.PositiveIntegerField')(null=True, blank=True)), - ('other_limits', self.gf('django.db.models.fields.CharField')(max_length=200, blank=True)), - ('text', self.gf('django.db.models.fields.TextField')(blank=True)), - ('upload', self.gf('django.db.models.fields.files.FileField')(max_length=100, blank=True)), - )) - db.send_create_signal('sponsorship', ['SponsorBenefit']) - - - def backwards(self, orm): - # Deleting model 'SponsorLevel' - db.delete_table('sponsorship_sponsorlevel') - - # Deleting model 'Sponsor' - db.delete_table('sponsorship_sponsor') - - # Deleting model 'Benefit' - db.delete_table('sponsorship_benefit') - - # Deleting model 'BenefitLevel' - db.delete_table('sponsorship_benefitlevel') - - # Deleting model 'SponsorBenefit' - db.delete_table('sponsorship_sponsorbenefit') - - - models = { - 'auth.group': { - 'Meta': {'object_name': 'Group'}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), - 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) - }, - 'auth.permission': { - 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, - 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - }, - 'auth.user': { - 'Meta': {'object_name': 'User'}, - 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), - 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), - 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), - 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) - }, - 'conference.conference': { - 'Meta': {'object_name': 'Conference'}, - 'end_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'start_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), - 'timezone': ('timezones.fields.TimeZoneField', [], {'default': "'US/Eastern'", 'max_length': '100', 'blank': 'True'}), - 'title': ('django.db.models.fields.CharField', [], {'max_length': '100'}) - }, - 'contenttypes.contenttype': { - 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, - 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) - }, - 'sponsorship.benefit': { - 'Meta': {'object_name': 'Benefit'}, - 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'type': ('django.db.models.fields.CharField', [], {'default': "'simple'", 'max_length': '10'}) - }, - 'sponsorship.benefitlevel': { - 'Meta': {'ordering': "['level']", 'object_name': 'BenefitLevel'}, - 'benefit': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'benefit_levels'", 'to': "orm['sponsorship.Benefit']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'level': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'benefit_levels'", 'to': "orm['sponsorship.SponsorLevel']"}), - 'max_words': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), - 'other_limits': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'}) - }, - 'sponsorship.sponsor': { - 'Meta': {'object_name': 'Sponsor'}, - 'active': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'added': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'annotation': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'applicant': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'sponsorships'", 'null': 'True', 'to': "orm['auth.User']"}), - 'contact_email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}), - 'contact_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'external_url': ('django.db.models.fields.URLField', [], {'max_length': '200'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'level': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sponsorship.SponsorLevel']"}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'sponsor_logo': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'to': "orm['sponsorship.SponsorBenefit']"}) - }, - 'sponsorship.sponsorbenefit': { - 'Meta': {'ordering': "['-active']", 'object_name': 'SponsorBenefit'}, - 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'benefit': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'sponsor_benefits'", 'to': "orm['sponsorship.Benefit']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'max_words': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), - 'other_limits': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'}), - 'sponsor': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'sponsor_benefits'", 'to': "orm['sponsorship.Sponsor']"}), - 'text': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'upload': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'blank': 'True'}) - }, - 'sponsorship.sponsorlevel': { - 'Meta': {'ordering': "['conference', 'order']", 'object_name': 'SponsorLevel'}, - 'conference': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['conference.Conference']"}), - 'cost': ('django.db.models.fields.PositiveIntegerField', [], {}), - 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}) - } - } - - complete_apps = ['sponsorship'] diff --git a/symposion/sponsorship/migrations/__init__.py b/symposion/sponsorship/migrations/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/symposion/teams/migrations/0001_initial.py b/symposion/teams/migrations/0001_initial.py deleted file mode 100644 index d1ab1600..00000000 --- a/symposion/teams/migrations/0001_initial.py +++ /dev/null @@ -1,123 +0,0 @@ -# -*- coding: utf-8 -*- -from south.utils import datetime_utils as datetime -from south.db import db -from south.v2 import SchemaMigration -from django.db import models - - -class Migration(SchemaMigration): - - def forwards(self, orm): - # Adding model 'Team' - db.create_table('teams_team', ( - ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('slug', self.gf('django.db.models.fields.SlugField')(unique=True, max_length=50)), - ('name', self.gf('django.db.models.fields.CharField')(max_length=100)), - ('description', self.gf('django.db.models.fields.TextField')(blank=True)), - ('access', self.gf('django.db.models.fields.CharField')(max_length=20)), - ('created', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now)), - )) - db.send_create_signal('teams', ['Team']) - - # Adding M2M table for field permissions on 'Team' - m2m_table_name = db.shorten_name('teams_team_permissions') - db.create_table(m2m_table_name, ( - ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), - ('team', models.ForeignKey(orm['teams.team'], null=False)), - ('permission', models.ForeignKey(orm['auth.permission'], null=False)) - )) - db.create_unique(m2m_table_name, ['team_id', 'permission_id']) - - # Adding M2M table for field manager_permissions on 'Team' - m2m_table_name = db.shorten_name('teams_team_manager_permissions') - db.create_table(m2m_table_name, ( - ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), - ('team', models.ForeignKey(orm['teams.team'], null=False)), - ('permission', models.ForeignKey(orm['auth.permission'], null=False)) - )) - db.create_unique(m2m_table_name, ['team_id', 'permission_id']) - - # Adding model 'Membership' - db.create_table('teams_membership', ( - ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('user', self.gf('django.db.models.fields.related.ForeignKey')(related_name='memberships', to=orm['auth.User'])), - ('team', self.gf('django.db.models.fields.related.ForeignKey')(related_name='memberships', to=orm['teams.Team'])), - ('state', self.gf('django.db.models.fields.CharField')(max_length=20)), - ('message', self.gf('django.db.models.fields.TextField')(blank=True)), - )) - db.send_create_signal('teams', ['Membership']) - - - def backwards(self, orm): - # Deleting model 'Team' - db.delete_table('teams_team') - - # Removing M2M table for field permissions on 'Team' - db.delete_table(db.shorten_name('teams_team_permissions')) - - # Removing M2M table for field manager_permissions on 'Team' - db.delete_table(db.shorten_name('teams_team_manager_permissions')) - - # Deleting model 'Membership' - db.delete_table('teams_membership') - - - models = { - 'auth.group': { - 'Meta': {'object_name': 'Group'}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), - 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) - }, - 'auth.permission': { - 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, - 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - }, - 'auth.user': { - 'Meta': {'object_name': 'User'}, - 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), - 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), - 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), - 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) - }, - 'contenttypes.contenttype': { - 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, - 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) - }, - 'teams.membership': { - 'Meta': {'object_name': 'Membership'}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'message': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'state': ('django.db.models.fields.CharField', [], {'max_length': '20'}), - 'team': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'memberships'", 'to': "orm['teams.Team']"}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'memberships'", 'to': "orm['auth.User']"}) - }, - 'teams.team': { - 'Meta': {'object_name': 'Team'}, - 'access': ('django.db.models.fields.CharField', [], {'max_length': '20'}), - 'created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'manager_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'manager_teams'", 'blank': 'True', 'to': "orm['auth.Permission']"}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'member_teams'", 'blank': 'True', 'to': "orm['auth.Permission']"}), - 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '50'}) - } - } - - complete_apps = ['teams'] \ No newline at end of file diff --git a/symposion/teams/migrations/__init__.py b/symposion/teams/migrations/__init__.py deleted file mode 100644 index e69de29b..00000000 From b7d1d1a96802c8a01eae62c329b89935c55f88fa Mon Sep 17 00:00:00 2001 From: Patrick Altman Date: Thu, 18 Dec 2014 08:14:32 -0600 Subject: [PATCH 580/751] Fix the appconf --- symposion/conf.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/symposion/conf.py b/symposion/conf.py index dd2a6d50..ad9a1324 100644 --- a/symposion/conf.py +++ b/symposion/conf.py @@ -1,3 +1,5 @@ +from django.conf import settings + from appconf import AppConf From a33e1cfb1f3e5e7e053cee049895993312c76dbe Mon Sep 17 00:00:00 2001 From: Patrick Altman Date: Thu, 18 Dec 2014 08:15:07 -0600 Subject: [PATCH 581/751] Rename get_query_set to get_queryset --- symposion/cms/managers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/symposion/cms/managers.py b/symposion/cms/managers.py index fcf4a4e1..a2e8f156 100644 --- a/symposion/cms/managers.py +++ b/symposion/cms/managers.py @@ -5,6 +5,6 @@ from django.db import models class PublishedPageManager(models.Manager): - def get_query_set(self): - qs = super(PublishedPageManager, self).get_query_set() return qs.filter(publish_date__lte=datetime.now()) + def get_queryset(self): + qs = super(PublishedPageManager, self).get_queryset() From d488c1e021c3ce4335223a407cbd82182fd83708 Mon Sep 17 00:00:00 2001 From: Patrick Altman Date: Thu, 18 Dec 2014 08:15:47 -0600 Subject: [PATCH 582/751] Use timezone.now instead of datetime.now --- symposion/cms/managers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/symposion/cms/managers.py b/symposion/cms/managers.py index a2e8f156..9ffb6592 100644 --- a/symposion/cms/managers.py +++ b/symposion/cms/managers.py @@ -1,10 +1,10 @@ -from datetime import datetime +from django.utils import timezone from django.db import models class PublishedPageManager(models.Manager): - return qs.filter(publish_date__lte=datetime.now()) def get_queryset(self): qs = super(PublishedPageManager, self).get_queryset() + return qs.filter(publish_date__lte=timezone.now()) From 2ec9449e38e7976dd3c7752e7ef2227938625710 Mon Sep 17 00:00:00 2001 From: Patrick Altman Date: Thu, 18 Dec 2014 08:16:07 -0600 Subject: [PATCH 583/751] Not necessary --- symposion/proposals/models.py | 1 - 1 file changed, 1 deletion(-) diff --git a/symposion/proposals/models.py b/symposion/proposals/models.py index 699f70b6..7678d961 100644 --- a/symposion/proposals/models.py +++ b/symposion/proposals/models.py @@ -158,7 +158,6 @@ class AdditionalSpeaker(models.Model): status = models.IntegerField(choices=SPEAKING_STATUS, default=SPEAKING_STATUS_PENDING) class Meta: - db_table = "proposals_proposalbase_additional_speakers" unique_together = ("speaker", "proposalbase") From 5f311ca7bd4e5e2ec7fe99bf903ed2793ea83d6f Mon Sep 17 00:00:00 2001 From: Patrick Altman Date: Thu, 18 Dec 2014 08:17:35 -0600 Subject: [PATCH 584/751] Replace dotted notation FKs with actual objects --- symposion/proposals/models.py | 7 ++++--- symposion/reviews/models.py | 21 +++++++++------------ symposion/schedule/models.py | 5 +++-- 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/symposion/proposals/models.py b/symposion/proposals/models.py index 7678d961..3c953c39 100644 --- a/symposion/proposals/models.py +++ b/symposion/proposals/models.py @@ -16,6 +16,7 @@ from markitup.fields import MarkupField from model_utils.managers import InheritanceManager from symposion.conference.models import Section +from symposion.speakers.models import Speaker class ProposalSection(models.Model): @@ -103,8 +104,8 @@ class ProposalBase(models.Model): default=now, editable=False, ) - speaker = models.ForeignKey("speakers.Speaker", related_name="proposals") - additional_speakers = models.ManyToManyField("speakers.Speaker", through="AdditionalSpeaker", + speaker = models.ForeignKey(Speaker, related_name="proposals") + additional_speakers = models.ManyToManyField(Speaker, through="AdditionalSpeaker", blank=True) cancelled = models.BooleanField(default=False) @@ -153,7 +154,7 @@ class AdditionalSpeaker(models.Model): (SPEAKING_STATUS_DECLINED, _("Declined")), ] - speaker = models.ForeignKey("speakers.Speaker") + speaker = models.ForeignKey(Speaker) proposalbase = models.ForeignKey(ProposalBase) status = models.IntegerField(choices=SPEAKING_STATUS, default=SPEAKING_STATUS_PENDING) diff --git a/symposion/reviews/models.py b/symposion/reviews/models.py index b601e0c2..b51ef2f0 100644 --- a/symposion/reviews/models.py +++ b/symposion/reviews/models.py @@ -52,13 +52,13 @@ class ReviewAssignment(models.Model): (AUTO_ASSIGNED_LATER, "auto-assigned, later"), ] - proposal = models.ForeignKey("proposals.ProposalBase") + proposal = models.ForeignKey(ProposalBase) user = models.ForeignKey(User) origin = models.IntegerField(choices=ORIGIN_CHOICES) assigned_at = models.DateTimeField(default=datetime.now) - opted_out = models.BooleanField() + opted_out = models.BooleanField(default=False) @classmethod def create_assignments(cls, proposal, origin=AUTO_ASSIGNED_INITIAL): @@ -92,7 +92,7 @@ class ReviewAssignment(models.Model): class ProposalMessage(models.Model): - proposal = models.ForeignKey("proposals.ProposalBase", related_name="messages") + proposal = models.ForeignKey(ProposalBase, related_name="messages") user = models.ForeignKey(User) message = MarkupField() @@ -105,7 +105,7 @@ class ProposalMessage(models.Model): class Review(models.Model): VOTES = VOTES - proposal = models.ForeignKey("proposals.ProposalBase", related_name="reviews") + proposal = models.ForeignKey(ProposalBase, related_name="reviews") user = models.ForeignKey(User) # No way to encode "-0" vs. "+0" into an IntegerField, and I don't feel @@ -184,7 +184,7 @@ class Review(models.Model): class LatestVote(models.Model): VOTES = VOTES - proposal = models.ForeignKey("proposals.ProposalBase", related_name="votes") + proposal = models.ForeignKey(ProposalBase, related_name="votes") user = models.ForeignKey(User) # No way to encode "-0" vs. "+0" into an IntegerField, and I don't feel @@ -205,7 +205,7 @@ class LatestVote(models.Model): class ProposalResult(models.Model): - proposal = models.OneToOneField("proposals.ProposalBase", related_name="result") + proposal = models.OneToOneField(ProposalBase, related_name="result") score = models.DecimalField(max_digits=5, decimal_places=2, default=Decimal("0.00")) comment_count = models.PositiveIntegerField(default=0) vote_count = models.PositiveIntegerField(default=0) @@ -281,15 +281,12 @@ class ProposalResult(models.Model): class Comment(models.Model): - proposal = models.ForeignKey("proposals.ProposalBase", related_name="comments") + proposal = models.ForeignKey(ProposalBase, related_name="comments") commenter = models.ForeignKey(User) text = MarkupField() # Or perhaps more accurately, can the user see this comment. - public = models.BooleanField(choices=[ - (True, "public"), - (False, "private"), - ]) + public = models.BooleanField(choices=[(True, "public"), (False, "private")], default=False) commented_at = models.DateTimeField(default=datetime.now) @@ -303,7 +300,7 @@ class NotificationTemplate(models.Model): class ResultNotification(models.Model): - proposal = models.ForeignKey("proposals.ProposalBase", related_name="notifications") + proposal = models.ForeignKey(ProposalBase, related_name="notifications") template = models.ForeignKey(NotificationTemplate, null=True, blank=True, on_delete=models.SET_NULL) timestamp = models.DateTimeField(default=datetime.now) diff --git a/symposion/schedule/models.py b/symposion/schedule/models.py index 38ff6f09..14ea87e4 100644 --- a/symposion/schedule/models.py +++ b/symposion/schedule/models.py @@ -7,6 +7,7 @@ from markitup.fields import MarkupField from symposion.proposals.models import ProposalBase from symposion.conference.models import Section +from symposion.speakers.models import Speaker class Schedule(models.Model): @@ -151,8 +152,8 @@ class Presentation(models.Model): title = models.CharField(max_length=100) description = MarkupField() abstract = MarkupField() - speaker = models.ForeignKey("speakers.Speaker", related_name="presentations") - additional_speakers = models.ManyToManyField("speakers.Speaker", related_name="copresentations", + speaker = models.ForeignKey(Speaker, related_name="presentations") + additional_speakers = models.ManyToManyField(Speaker, related_name="copresentations", blank=True) cancelled = models.BooleanField(default=False) proposal_base = models.OneToOneField(ProposalBase, related_name="presentation") From 71e9ca11bd73c272bca0ec651bd5ed84b13e94c1 Mon Sep 17 00:00:00 2001 From: Patrick Altman Date: Thu, 18 Dec 2014 08:17:58 -0600 Subject: [PATCH 585/751] Namespace the apps --- symposion/boxes/__init__.py | 3 +++ symposion/cms/__init__.py | 2 ++ symposion/cms/apps.py | 7 +++++++ symposion/conference/__init__.py | 1 + symposion/conference/apps.py | 7 +++++++ symposion/proposals/__init__.py | 1 + symposion/proposals/apps.py | 7 +++++++ symposion/reviews/__init__.py | 1 + symposion/reviews/apps.py | 7 +++++++ symposion/schedule/__init__.py | 1 + symposion/schedule/apps.py | 7 +++++++ symposion/speakers/__init__.py | 1 + symposion/speakers/apps.py | 7 +++++++ symposion/sponsorship/__init__.py | 1 + symposion/sponsorship/apps.py | 7 +++++++ symposion/teams/__init__.py | 1 + 16 files changed, 61 insertions(+) create mode 100644 symposion/cms/apps.py create mode 100644 symposion/conference/apps.py create mode 100644 symposion/proposals/apps.py create mode 100644 symposion/reviews/apps.py create mode 100644 symposion/schedule/apps.py create mode 100644 symposion/speakers/apps.py create mode 100644 symposion/sponsorship/apps.py diff --git a/symposion/boxes/__init__.py b/symposion/boxes/__init__.py index e69de29b..8a6e37e9 100644 --- a/symposion/boxes/__init__.py +++ b/symposion/boxes/__init__.py @@ -0,0 +1,3 @@ +# @@@ Reconcile differences with django-boxes [1] and remove from Symposion + +# [1] http://github.com/eldarion/django-boxes/ diff --git a/symposion/cms/__init__.py b/symposion/cms/__init__.py index e69de29b..010e607a 100644 --- a/symposion/cms/__init__.py +++ b/symposion/cms/__init__.py @@ -0,0 +1,2 @@ +# @@@ Consider replacing with pinax-wiki [1] +default_app_config = "symposion.cms.apps.CMSConfig" diff --git a/symposion/cms/apps.py b/symposion/cms/apps.py new file mode 100644 index 00000000..d506db12 --- /dev/null +++ b/symposion/cms/apps.py @@ -0,0 +1,7 @@ +from django.apps import AppConfig + + +class CMSConfig(AppConfig): + name = "symposion.cms" + label = "symposion_cms" + verbose_name = "Symposion CMS" diff --git a/symposion/conference/__init__.py b/symposion/conference/__init__.py index e69de29b..1f126f31 100644 --- a/symposion/conference/__init__.py +++ b/symposion/conference/__init__.py @@ -0,0 +1 @@ +default_app_config = "symposion.conference.apps.ConferenceConfig" diff --git a/symposion/conference/apps.py b/symposion/conference/apps.py new file mode 100644 index 00000000..d7759eeb --- /dev/null +++ b/symposion/conference/apps.py @@ -0,0 +1,7 @@ +from django.apps import AppConfig + + +class ConferenceConfig(AppConfig): + name = "symposion.conference" + label = "symposion_conference" + verbose_name = "Symposion Conference" diff --git a/symposion/proposals/__init__.py b/symposion/proposals/__init__.py index e69de29b..ac65f575 100644 --- a/symposion/proposals/__init__.py +++ b/symposion/proposals/__init__.py @@ -0,0 +1 @@ +default_app_config = "symposion.proposals.apps.ProposalsConfig" diff --git a/symposion/proposals/apps.py b/symposion/proposals/apps.py new file mode 100644 index 00000000..9a317525 --- /dev/null +++ b/symposion/proposals/apps.py @@ -0,0 +1,7 @@ +from django.apps import AppConfig + + +class ProposalsConfig(AppConfig): + name = "symposion.proposals" + label = "symposion_proposals" + verbose_name = "Symposion Proposals" diff --git a/symposion/reviews/__init__.py b/symposion/reviews/__init__.py index e69de29b..8c9647e8 100644 --- a/symposion/reviews/__init__.py +++ b/symposion/reviews/__init__.py @@ -0,0 +1 @@ +default_app_config = "symposion.reviews.apps.ReviewsConfig" diff --git a/symposion/reviews/apps.py b/symposion/reviews/apps.py new file mode 100644 index 00000000..683b0c0f --- /dev/null +++ b/symposion/reviews/apps.py @@ -0,0 +1,7 @@ +from django.apps import AppConfig + + +class ReviewsConfig(AppConfig): + name = "symposion.reviews" + label = "symposion_reviews" + verbose_name = "Symposion Reviews" diff --git a/symposion/schedule/__init__.py b/symposion/schedule/__init__.py index e69de29b..d59b84df 100644 --- a/symposion/schedule/__init__.py +++ b/symposion/schedule/__init__.py @@ -0,0 +1 @@ +default_app_config = "symposion.schedule.apps.ScheduleConfig" diff --git a/symposion/schedule/apps.py b/symposion/schedule/apps.py new file mode 100644 index 00000000..03773bdc --- /dev/null +++ b/symposion/schedule/apps.py @@ -0,0 +1,7 @@ +from django.apps import AppConfig + + +class ScheduleConfig(AppConfig): + name = "symposion.schedule" + label = "symposion_schedule" + verbose_name = "Symposion Schedule" diff --git a/symposion/speakers/__init__.py b/symposion/speakers/__init__.py index e69de29b..a47c10fc 100644 --- a/symposion/speakers/__init__.py +++ b/symposion/speakers/__init__.py @@ -0,0 +1 @@ +default_app_config = "symposion.speakers.apps.SpeakersConfig" diff --git a/symposion/speakers/apps.py b/symposion/speakers/apps.py new file mode 100644 index 00000000..b4abf5d5 --- /dev/null +++ b/symposion/speakers/apps.py @@ -0,0 +1,7 @@ +from django.apps import AppConfig + + +class SpeakersConfig(AppConfig): + name = "symposion.speakers" + label = "symposion_speakers" + verbose_name = "Symposion Speakers" diff --git a/symposion/sponsorship/__init__.py b/symposion/sponsorship/__init__.py index e69de29b..30b11cfe 100644 --- a/symposion/sponsorship/__init__.py +++ b/symposion/sponsorship/__init__.py @@ -0,0 +1 @@ +default_app_config = "symposion.sponsorship.apps.SponsorshipConfig" diff --git a/symposion/sponsorship/apps.py b/symposion/sponsorship/apps.py new file mode 100644 index 00000000..80bf19d1 --- /dev/null +++ b/symposion/sponsorship/apps.py @@ -0,0 +1,7 @@ +from django.apps import AppConfig + + +class SponsorshipConfig(AppConfig): + name = "symposion.sponsorship" + label = "symposion_sponsorship" + verbose_name = "Symposion Sponsorship" diff --git a/symposion/teams/__init__.py b/symposion/teams/__init__.py index e69de29b..403c783d 100644 --- a/symposion/teams/__init__.py +++ b/symposion/teams/__init__.py @@ -0,0 +1 @@ +# @@@ Replace this with pinax-teams From 7ff4c0b4efdec5bc4559872d6e4275213f479b0e Mon Sep 17 00:00:00 2001 From: Patrick Altman Date: Thu, 18 Dec 2014 08:18:48 -0600 Subject: [PATCH 586/751] Ignore linting the settings import --- symposion/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/symposion/conf.py b/symposion/conf.py index ad9a1324..b2b729f7 100644 --- a/symposion/conf.py +++ b/symposion/conf.py @@ -1,4 +1,4 @@ -from django.conf import settings +from django.conf import settings # noqa from appconf import AppConf From 039ce4e00034f872bad5252a7d84dc3028394c5f Mon Sep 17 00:00:00 2001 From: Hiroshi Miura Date: Mon, 25 May 2015 08:35:55 +0900 Subject: [PATCH 587/751] Return unicode from _unicode_ methods Import from PyCon improvement. Author: Dan Poirier Date: Mon Jul 8 15:55:33 2013 -0400 Return unicode from __unicode__ methods Signed-off-by: Hiroshi Miura --- symposion/conference/models.py | 2 +- symposion/schedule/models.py | 10 +++++----- symposion/speakers/models.py | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/symposion/conference/models.py b/symposion/conference/models.py index 354403b6..582035b5 100644 --- a/symposion/conference/models.py +++ b/symposion/conference/models.py @@ -59,7 +59,7 @@ class Section(models.Model): end_date = models.DateField(_("end date"), null=True, blank=True) def __unicode__(self): - return "%s %s" % (self.conference, self.name) + return u"%s %s" % (self.conference, self.name) class Meta(object): verbose_name = _("section") diff --git a/symposion/schedule/models.py b/symposion/schedule/models.py index 14ea87e4..a8533abb 100644 --- a/symposion/schedule/models.py +++ b/symposion/schedule/models.py @@ -17,7 +17,7 @@ class Schedule(models.Model): hidden = models.BooleanField("Hide schedule from overall conference view", default=False) def __unicode__(self): - return "%s Schedule" % self.section + return u"%s Schedule" % self.section class Meta: ordering = ["section"] @@ -29,7 +29,7 @@ class Day(models.Model): date = models.DateField() def __unicode__(self): - return "%s" % self.date + return u"%s" % self.date class Meta: unique_together = [("schedule", "date")] @@ -124,7 +124,7 @@ class Slot(models.Model): return Room.objects.filter(pk__in=self.slotroom_set.values("room")) def __unicode__(self): - return "%s %s (%s - %s)" % (self.day, self.kind, self.start, self.end) + return u"%s %s (%s - %s)" % (self.day, self.kind, self.start, self.end) class Meta: ordering = ["day", "start", "end"] @@ -139,7 +139,7 @@ class SlotRoom(models.Model): room = models.ForeignKey(Room) def __unicode__(self): - return "%s %s" % (self.room, self.slot) + return u"%s %s" % (self.room, self.slot) class Meta: unique_together = [("slot", "room")] @@ -176,7 +176,7 @@ class Presentation(models.Model): yield speaker def __unicode__(self): - return "#%s %s (%s)" % (self.number, self.title, self.speaker) + return u"#%s %s (%s)" % (self.number, self.title, self.speaker) class Meta: ordering = ["slot"] diff --git a/symposion/speakers/models.py b/symposion/speakers/models.py index b5705bcc..0887ef45 100644 --- a/symposion/speakers/models.py +++ b/symposion/speakers/models.py @@ -38,7 +38,7 @@ class Speaker(models.Model): if self.user: return self.name else: - return "?" + return u"?" def get_absolute_url(self): return reverse("speaker_edit") From edb3336aa7fc9baa9fbf1ce016e589d0498fdbff Mon Sep 17 00:00:00 2001 From: Hiroshi Miura Date: Fri, 12 Jun 2015 01:36:11 +0900 Subject: [PATCH 588/751] notify to multiple speakers Signed-off-by: Hiroshi Miura --- symposion/proposals/models.py | 1 + 1 file changed, 1 insertion(+) diff --git a/symposion/proposals/models.py b/symposion/proposals/models.py index 3c953c39..c81a0791 100644 --- a/symposion/proposals/models.py +++ b/symposion/proposals/models.py @@ -135,6 +135,7 @@ class ProposalBase(models.Model): return { "title": self.title, "speaker": self.speaker.name, + "speakers": ', '.join([x.name for x in self.speakers()]), "kind": self.kind.name, } From 3dd2f14f7293950ccc6129b3d53a0ff9405b02d1 Mon Sep 17 00:00:00 2001 From: Hiroshi Miura Date: Fri, 12 Jun 2015 08:04:22 +0900 Subject: [PATCH 589/751] multiple recipients for email Signed-off-by: Hiroshi Miura --- symposion/reviews/models.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/symposion/reviews/models.py b/symposion/reviews/models.py index b51ef2f0..1537ae98 100644 --- a/symposion/reviews/models.py +++ b/symposion/reviews/models.py @@ -309,9 +309,13 @@ class ResultNotification(models.Model): subject = models.CharField(max_length=100) body = models.TextField() + def recipients(self): + for speaker in self.proposal.speakers(): + yield speaker.email + @property def email_args(self): - return (self.subject, self.body, self.from_address, [self.to_address]) + return (self.subject, self.body, self.from_address, self.recipients()) def promote_proposal(proposal): From 45706074d64460d4fe76bfd8f9709359262d53ad Mon Sep 17 00:00:00 2001 From: Hiroshi Miura Date: Fri, 12 Jun 2015 08:07:49 +0900 Subject: [PATCH 590/751] admin: show review result Signed-off-by: Hiroshi Miura --- symposion/reviews/admin.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/symposion/reviews/admin.py b/symposion/reviews/admin.py index c7273ad1..a4a33844 100644 --- a/symposion/reviews/admin.py +++ b/symposion/reviews/admin.py @@ -1,6 +1,18 @@ from django.contrib import admin -from symposion.reviews.models import NotificationTemplate +from symposion.reviews.models import NotificationTemplate, ProposalResult -admin.site.register(NotificationTemplate) +admin.site.register( + NotificationTemplate, + list_display=[ + 'label', + 'from_address', + 'subject' + ] +) + +admin.site.register( + ProposalResult, + list_display=['proposal', 'status', 'score', 'vote_count', 'accepted'] +) From 464d85b36a649525dbdfc3bb89fc2dbd21bac6fc Mon Sep 17 00:00:00 2001 From: Hiroshi Miura Date: Wed, 17 Jun 2015 08:30:41 +0900 Subject: [PATCH 591/751] Display proposal result status in proposal admin change lists feedback from pycon development commit 3401cdd8253d86e166d980a1d1eb5ba2de01932d Author: Dan Poirier Date: Thu Jun 6 15:10:57 2013 -0400 Signed-off-by: Hiroshi Miura --- symposion/proposals/models.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/symposion/proposals/models.py b/symposion/proposals/models.py index 3c953c39..b5880243 100644 --- a/symposion/proposals/models.py +++ b/symposion/proposals/models.py @@ -8,6 +8,7 @@ from django.utils.translation import ugettext_lazy as _ from django.utils.timezone import now from django.contrib.auth.models import User +from django.core.exceptions import ObjectDoesNotExist import reversion @@ -124,6 +125,13 @@ class ProposalBase(models.Model): def number(self): return str(self.pk).zfill(3) + @property + def status(self): + try: + return self.result.status + except ObjectDoesNotExist: + return 'undecided' + def speakers(self): yield self.speaker speakers = self.additional_speakers.exclude( From 5948aacd93f44cdbbfcaea5a850abb262d4375b5 Mon Sep 17 00:00:00 2001 From: Hiroshi Miura Date: Wed, 17 Jun 2015 10:43:44 +0900 Subject: [PATCH 592/751] Internatinalize status label - undecided Signed-off-by: Hiroshi Miura --- symposion/proposals/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/symposion/proposals/models.py b/symposion/proposals/models.py index b5880243..d02b1ebb 100644 --- a/symposion/proposals/models.py +++ b/symposion/proposals/models.py @@ -130,7 +130,7 @@ class ProposalBase(models.Model): try: return self.result.status except ObjectDoesNotExist: - return 'undecided' + return _('Undecided') def speakers(self): yield self.speaker From 44deb8a5363435abef524547c799c72fcbfd67b8 Mon Sep 17 00:00:00 2001 From: Hiroshi Miura Date: Fri, 12 Jun 2015 01:39:29 +0900 Subject: [PATCH 593/751] i18n for more mesages Signed-off-by: Hiroshi Miura --- symposion/proposals/views.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/symposion/proposals/views.py b/symposion/proposals/views.py index fe146c9b..46c3c1c6 100644 --- a/symposion/proposals/views.py +++ b/symposion/proposals/views.py @@ -14,6 +14,8 @@ from django.contrib import messages from django.contrib.auth.models import User from django.contrib.auth.decorators import login_required +from django.utils.translation import ugettext_lazy as _ + from account.models import EmailAddress from symposion.proposals.models import ( ProposalBase, ProposalSection, ProposalKind @@ -36,18 +38,18 @@ def get_form(name): def proposal_submit(request): if not request.user.is_authenticated(): - messages.info(request, "To submit a proposal, please " - "log in and create a speaker profile " - "via the dashboard.".format(settings.LOGIN_URL)) + messages.info(request, _("To submit a proposal, please " + "log in and create a speaker profile " + "via the dashboard.".format(settings.LOGIN_URL))) return redirect("home") # @@@ unauth'd speaker info page? else: try: request.user.speaker_profile except ObjectDoesNotExist: url = reverse("speaker_create") - messages.info(request, "To submit a proposal, first " - "create a speaker " - "profile.".format(url)) + messages.info(request, _("To submit a proposal, first " + "create a speaker " + "profile.".format(url))) return redirect("dashboard") kinds = [] From a41fb8bd35420f900001959ad12e520fa2e870d9 Mon Sep 17 00:00:00 2001 From: Hiroshi Miura Date: Mon, 15 Jun 2015 18:55:54 +0900 Subject: [PATCH 594/751] schedule: add session and session role port from pycon development Luke Hatcher committed 6889d05 2013-01-31 add session volunteer staff Signed-off-by: Hiroshi Miura --- symposion/schedule/admin.py | 4 +- symposion/schedule/models.py | 60 ++++++++++++++ .../schedule/tests/test_views_session.py | 58 +++++++++++++ symposion/schedule/urls.py | 3 + symposion/schedule/views.py | 83 ++++++++++++++++++- 5 files changed, 206 insertions(+), 2 deletions(-) create mode 100644 symposion/schedule/tests/test_views_session.py diff --git a/symposion/schedule/admin.py b/symposion/schedule/admin.py index 04aee8aa..50f252c3 100644 --- a/symposion/schedule/admin.py +++ b/symposion/schedule/admin.py @@ -1,6 +1,6 @@ from django.contrib import admin -from symposion.schedule.models import Schedule, Day, Room, SlotKind, Slot, SlotRoom, Presentation +from symposion.schedule.models import Schedule, Day, Room, SlotKind, Slot, SlotRoom, Presentation, Session, SessionRole admin.site.register(Schedule) @@ -15,4 +15,6 @@ admin.site.register( SlotRoom, list_display=("slot", "room") ) +admin.site.register(Session) +admin.site.register(SessionRole) admin.site.register(Presentation) diff --git a/symposion/schedule/models.py b/symposion/schedule/models.py index 14ea87e4..5be16eb8 100644 --- a/symposion/schedule/models.py +++ b/symposion/schedule/models.py @@ -1,6 +1,7 @@ import datetime from django.core.exceptions import ObjectDoesNotExist +from django.contrib.auth.models import User from django.db import models from markitup.fields import MarkupField @@ -180,3 +181,62 @@ class Presentation(models.Model): class Meta: ordering = ["slot"] + + +class Session(models.Model): + + day = models.ForeignKey(Day, related_name="sessions") + slots = models.ManyToManyField(Slot, related_name="sessions") + + def sorted_slots(self): + return self.slots.order_by("start") + + def start(self): + slots = self.sorted_slots() + if slots: + return list(slots)[0].start + else: + return None + + def end(self): + slots = self.sorted_slots() + if slots: + return list(slots)[-1].end + else: + return None + + def __unicode__(self): + start = self.start() + end = self.end() + if start and end: + return u"%s: %s - %s" % ( + self.day.date.strftime("%a"), + start.strftime("%X"), + end.strftime("%X") + ) + return u"" + + +class SessionRole(models.Model): + + SESSION_ROLE_CHAIR = 1 + SESSION_ROLE_RUNNER = 2 + + SESSION_ROLE_TYPES = [ + (SESSION_ROLE_CHAIR, "Session Chair"), + (SESSION_ROLE_RUNNER, "Session Runner"), + ] + + session = models.ForeignKey(Session) + user = models.ForeignKey(User) + role = models.IntegerField(choices=SESSION_ROLE_TYPES) + status = models.NullBooleanField() + + submitted = models.DateTimeField(default=datetime.datetime.now) + + class Meta: + unique_together = [("session", "user", "role")] + + def __unicode__(self): + return u"%s %s: %s" % (self.user, self.session, + self.SESSION_ROLE_TYPES[self.role - 1][1]) diff --git a/symposion/schedule/tests/test_views_session.py b/symposion/schedule/tests/test_views_session.py new file mode 100644 index 00000000..02c2f180 --- /dev/null +++ b/symposion/schedule/tests/test_views_session.py @@ -0,0 +1,58 @@ +from datetime import date + +from django.conf import settings +from django.contrib.auth.models import User +from django.core.urlresolvers import reverse +from django.test import TestCase + +from symposion.conference.models import Section, current_conference, Conference +from symposion.schedule.models import Day, Schedule, Session + + +class TestScheduleViews(TestCase): + username = "user@example.com" + first_name = "Sam" + last_name = "McGillicuddy" + + def setUp(self): + self.user = User.objects.create_user(self.username, + password="pass", + email=self.username) + self.user.first_name = self.first_name + self.user.last_name = self.last_name + self.user.save() + + def test_session_list(self): + # Really minimal test for session list + rsp = self.client.get(reverse("schedule_session_list")) + self.assertEqual(200, rsp.status_code) + + def test_session_staff_email(self): + # login and staff required + self.user.is_staff = True + self.user.save() + assert self.client.login(username=self.username, password="pass") + + url = reverse("schedule_session_staff_email") + rsp = self.client.get(url) + self.assertEqual(200, rsp.status_code) + + def test_session_detail(self): + # really minimal test + Conference.objects.get_or_create(id=settings.CONFERENCE_ID) + section = Section.objects.create( + conference=current_conference(), + ) + schedule = Schedule.objects.create( + section=section, + ) + day = Day.objects.create( + schedule=schedule, + date=date.today(), + ) + session = Session.objects.create( + day=day, + ) + url = reverse("schedule_session_detail", args=(session.pk,)) + rsp = self.client.get(url) + self.assertEqual(200, rsp.status_code) diff --git a/symposion/schedule/urls.py b/symposion/schedule/urls.py index 74064ba8..b0e07189 100644 --- a/symposion/schedule/urls.py +++ b/symposion/schedule/urls.py @@ -17,4 +17,7 @@ urlpatterns = patterns( url(r"^([\w\-]+)/edit/slot/(\d+)/", "schedule_slot_edit", name="schedule_slot_edit"), url(r"^conference.json", "schedule_json", name="schedule_json"), + url(r"^sessions/staff.txt$", "session_staff_email", name="schedule_session_staff_email"), + url(r"^sessions/$", "session_list", name="schedule_session_list"), + url(r"^session/(\d+)/$", "session_detail", name="schedule_session_detail"), ) diff --git a/symposion/schedule/views.py b/symposion/schedule/views.py index b82505d1..1307e5e5 100644 --- a/symposion/schedule/views.py +++ b/symposion/schedule/views.py @@ -6,11 +6,12 @@ from django.shortcuts import render, get_object_or_404, redirect from django.template import loader, Context from django.contrib.auth.decorators import login_required +from django.contrib.auth.models import User from django.contrib import messages from django.contrib.sites.models import Site from symposion.schedule.forms import SlotEditForm, ScheduleSectionForm -from symposion.schedule.models import Schedule, Day, Slot, Presentation +from symposion.schedule.models import Schedule, Day, Slot, Presentation, Session, SessionRole from symposion.schedule.timetable import TimeTable @@ -231,3 +232,83 @@ def schedule_json(request): json.dumps({'schedule': data}), content_type="application/json" ) + + +def session_list(request): + sessions = Session.objects.all().order_by('pk') + + return render(request, "schedule/session_list.html", { + "sessions": sessions, + }) + + +@login_required +def session_staff_email(request): + + if not request.user.is_staff: + return redirect("schedule_session_list") + + data = "\n".join(user.email for user in User.objects.filter(sessionrole__isnull=False).distinct()) + + return HttpResponse(data, content_type="text/plain;charset=UTF-8") + + +def session_detail(request, session_id): + + session = get_object_or_404(Session, id=session_id) + + chair = None + chair_denied = False + chairs = SessionRole.objects.filter(session=session, role=SessionRole.SESSION_ROLE_CHAIR).exclude(status=False) + if chairs: + chair = chairs[0].user + else: + if request.user.is_authenticated(): + # did the current user previously try to apply and got rejected? + if SessionRole.objects.filter(session=session, user=request.user, role=SessionRole.SESSION_ROLE_CHAIR, status=False): + chair_denied = True + + runner = None + runner_denied = False + runners = SessionRole.objects.filter(session=session, role=SessionRole.SESSION_ROLE_RUNNER).exclude(status=False) + if runners: + runner = runners[0].user + else: + if request.user.is_authenticated(): + # did the current user previously try to apply and got rejected? + if SessionRole.objects.filter(session=session, user=request.user, role=SessionRole.SESSION_ROLE_RUNNER, status=False): + runner_denied = True + + if request.method == "POST" and request.user.is_authenticated(): + if not hasattr(request.user, "profile") or not request.user.profile.is_complete: + response = redirect("profile_edit") + response["Location"] += "?next=%s" % request.path + return response + + role = request.POST.get("role") + if role == "chair": + if chair is None and not chair_denied: + SessionRole(session=session, role=SessionRole.SESSION_ROLE_CHAIR, user=request.user).save() + elif role == "runner": + if runner is None and not runner_denied: + SessionRole(session=session, role=SessionRole.SESSION_ROLE_RUNNER, user=request.user).save() + elif role == "un-chair": + if chair == request.user: + session_role = SessionRole.objects.filter(session=session, role=SessionRole.SESSION_ROLE_CHAIR, user=request.user) + if session_role: + session_role[0].delete() + elif role == "un-runner": + if runner == request.user: + session_role = SessionRole.objects.filter(session=session, role=SessionRole.SESSION_ROLE_RUNNER, user=request.user) + if session_role: + session_role[0].delete() + + return redirect("schedule_session_detail", session_id) + + return render(request, "schedule/session_detail.html", { + "session": session, + "chair": chair, + "chair_denied": chair_denied, + "runner": runner, + "runner_denied": runner_denied, + }) From 1d43ca5a66dda3a5714979f8300709e35b393d9a Mon Sep 17 00:00:00 2001 From: Hiroshi Miura Date: Thu, 18 Jun 2015 10:05:31 +0900 Subject: [PATCH 595/751] add japanese translation Signed-off-by: Hiroshi Miura --- symposion/locale/ja/LC_MESSAGES/django.mo | Bin 0 -> 9352 bytes symposion/locale/ja/LC_MESSAGES/django.po | 573 ++++++++++++++++++++++ 2 files changed, 573 insertions(+) create mode 100644 symposion/locale/ja/LC_MESSAGES/django.mo create mode 100644 symposion/locale/ja/LC_MESSAGES/django.po diff --git a/symposion/locale/ja/LC_MESSAGES/django.mo b/symposion/locale/ja/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..d3c829d1a3928ed0d94aef59a958e423db21b584 GIT binary patch literal 9352 zcmc(jdu$xnb;fThJ5H2`Qm3}#Caot*6-lv{q+}|V8A-NYwv_4>y<$6YoaOG29C3GN zJ+mvCMNw#XO<7Ni)ibnYOMXx}z^ z_jm5xSw8egTcGuTcfYx>bI(2ZJLlZthj-s`Q1PTGU!lA(NhxXgr%&?3^Vp}A`Vd?M z&i#Z^)?}q7ftx;~R1NJ89WC* z0R9BL7aRdap8R|P_yQ>V`6?*#>Os+O0VwOQ08fM817+PinRF*O1(f4gDC^gQ zB5yIspW4WejBf&qUegMk({v6~_{RclX{_`k@3Dmuy=rIkH{munN&LV^BK(R*x6nmyX8P^Vq z{0@V=L6N^7l>K*uqUS|W>~akh{r?6O`5%C??ucpsB`EVg0&Bs~vFRD$0#Md#0%bi9 z6uIpnlvE|t{}L#6dl{5*$4vVPQ1pKb6#e@^8TaR)=<`!k|65c40F-ebf~cbY5fpp< z3KTh?WHVBq0*b!(fnvvLp!C;+qSpdY)>&@aSAnwLCe!|X@JakBF8$zD$p0MpCy?_S z;M^}M^-aeA@cxj$he6TnA}Dry-{8*-{sLr*`WJ(r{_Rlzsi3sa1!Krv4gN0mKg1~n zkAUKLS3$Ac&rJK@8T>cXe-9**^&bRf{du6A_i|9|^fdTYFb#^lz2NVG-5?^=UzmCY zl=+|eoe;ka%DNAl`W`eM`mD}#5UtjPT=C~~Ko`V$790cD>)DDroM?}JC9ew<(0 zeNe`gKw0-FI3IiioDTlNw13J9$4>!0+8+RApZh?O({1n)D02Q3{08`!U<~|sQ0z4g zqln&fKv{P=DD&f>?Bh8Q71be7*nYpkuY=+r z4}oI$1*ZKegH52e1Gt#}5;zb1Gf>w32oyc;{#q#Keo)r=n!!gv>0e`Tlfh>|SvP4g z1B$-S8|(mO-adm}ru{f5dY%JCub&wFdxImO*yoo9zl70b+{2)(_XH^Gdst6T2dJUg6p2BNlvWBR(2v-E1x5V!QOb8H(bHgFmV(QpMiBQ?4&^D+CN_VV@;F8K zEYA}Z+(tk58ccvsQ^XgVDH|x$C>tsAq$z~d>a&nH;n^dmr53EGNX(S;{Wc|fe!xqU zsoViBqfDYCP1|blF3LJn7mye&&ti)3X^B+K^F5F-tn-rOnjcbrgYqmzo<$+3&0vZ$ zQ|gng+dM0ptadj)J!z$!R@=(CR>DoE^BE@*&)HU+lWVbZEw<(78`DlMXM3}(yw5P2 zn%q>%ZF4fsym@IWo=IA?x}N0&ZB8m>HQKjbIpgM>CdW?dsm;z7JEP^g*{tj3&>+_? z>Ug%Fbu-DDTZ#&|H$S_uI%1=5d)CxxGtkt_#MAanf2!3I_pNv~o09dAy4iIy*8IkW z(`MxCc>39J)co4Ug_fJKrp@r3oNXGzh3D5IUoS9rp$)DTPbBPY4#gcKAGSpH0trpL2S}?UGm&-QP)*9s)Gt)QP z>-VV(f6ajTweiqIdM`Ix?I|~pmA2R%8z~6 z7x$X&+=A)PHm2g4*69mZnE}Gr8np zZY@j49i*7rYC?@vw%DE@1_2#D!*iqUCVdh|tD#jb@oWjY=F~;aCMSi+yyrn3Sw5f2 zA;#3J>{x0iQclJWeO9lmOjDAV(d{p_bLfjxS6N$af!HFV#`(XvL5zun z4q@E0lmA~lh#bgQOTBngPHFb7Pr1=s=qXj3>F!A1aPat3mH|TD1c!%l8&r6fVK%(^ zR>L4~GTS%06**Pb7DueH*0;F%RFa@dW}!Lm#c9vB%tFBPPCB2q>g(#P zL<{W+(yL#iR!HOx`LkXkhOWcLm9_-uxV6gm{dlvjG~HIa@JA<1*-X+z8IC6hL0iK3 zJ#OPQYPCDdYPIdGh5M4ZaqKqQa<(tOIw3!**2c;EiFgK`@jkIpoPDKixdD*0GWm2P zFJaDa&k|X}cQRafWvEqa!)&J@v34u`P{`+38yeJF9Z1wVJMFTuG&c&*O@~IVo?PTS z*5kIFY>X#b86Ji~Y$K60ZLASFO|7-T$>B6neX-+K->WjBPLvZ5do&z@1$ohd-O|R5 zqI`>!RqHvDysy^ldyoNZk?<{y8es=MD%zQT=)ha`bVez>frR3#D6+zwE%<$n+Ti90 z`caf&0Gw!5n>ZED)+iuZS`H><*kJarPf~d2X$9nP$SKIYeO4cn+!*Lt>);($fQnql zl)0(EK^V}4IHKp6SD$x3r;?X zS2Cc+^_mw>UbSM?ve6CAuBn?$a*|*d+rX|HEU`yzHic(@O9vpIEWB~U0mS=xKU!9nR@P&^%!`h(K( zptx`7z$pd?g%d-^x`X15pl~E8?g|R~5f~J92ZfVCVNXzaFDP^cTZ`3;^-c5&3a{w3 zPq1G3$g4x=4#<+Hj}7*}%rb*lUJ44^|2Lv zR)RuLdEjVy7skS5>`)tSYtafnRc`vxq7Bn)rWN;<_Z}YZiDnjevh%^dL##JA@XFA^ zuAqRWG2sa@aB){SOU??bzkYT240i3(7U>BU4+?Jv1Z>+`B4iAdxOGp z5muLPxwLGhAsxc~(?eDOmyeD+AC|3KwL zari_*O)v%%UzeR0`-0NpptM~LUwpB02&VN84_w3aHwunALSlxpLk^l5A&3OME`6%p zf$zh)-k@|gDD4kQM26QMvpHMa))LRf96uJd)lR+bht=4qc^gG< zJd0|)5k~*IZiy{tkLb`IoQROkp|vY}J1ehY#@?WKE-1aB=>#!)R2ZgAcX8hh z-Q$N>t{`IoeR_h@wxIYn4y6cIVFVv3T&e7QRfWl=+##!lxBam#<<33j9ebe1IJ)TL z?j`;YpZ+m+8rl7p8k4WC2vKy(GK`n43aQx&N6K!Q;F<>PnSe`^uds(DqDFyb=^o>KKA6Y0jytQHM`o z9D4ggq@1o??|(V!-W|pMFtQzs(rwQ;T1JWUV`5+!9n|oi9o!N^!eQMKfkSL}`P9gf zZPEU2Vf=A}qP<5`C(z7{4T=|qiBL&?UkF>VoEkaOS3cJnlwJ#u7JcLv6A7JYP`Qjq z$heueZ!L^;>?vOZhu^sv#r5-*%YB1=KZ@4Sq!v<{$cCpAK>6El zY$ZFQn?GC!oz2Eox!5(lwPf5lbolH2$I$JMNku54$IDf5c-N0B2QOd&5m?$A6yFqg zEM1%tn;DsF_uw^*28E1p{c@~ww5NQ5mh;nVr!%rV&_VvvSIBBOmMbJnzO0GyVjwg%DjmB_SPi!ySreM+kArc$V?EOvf(iX&ImdVoUcL^r!F8$~dF9n8tZ^aL z$H*Na%n`7KDcFJ=fwrbd?v_Apl)I7tW9=Pqvnce09--q2Gbes_s0ml9|Mo4D9S~x; zeQ?px8}B})Do1yecfKnLj;p`ic68n&QsKp%JWPO-@7|)73(fFbeJZ3|lre@b6$h_w zs~qlDO!=!TKvzt6&J1Y$z8HVeMR63i1h6&5_-b%p!lBNp6E5k JIikv8^, 2015. +# +msgid "" +msgstr "" +"Project-Id-Version: symposion\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2014-07-31 14:47-0600\n" +"PO-Revision-Date: 2015-06-18 10:04+0900\n" +"Last-Translator: Hiroshi Miura \n" +"Language-Team: Japanese translation team \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Poedit 1.5.4\n" +"Language: ja\n" + +#: cms/models.py:23 +msgid "Draft" +msgstr "草稿" + +#: cms/models.py:24 +msgid "Public" +msgstr "公開" + +#: cms/models.py:57 +msgid "Path can only contain letters, numbers and hyphens and end with /" +msgstr "" +"パス名には、英数字、ハイフン(-)のみが使えます。また末尾は'/'の必要がありま" +"す。" + +#: conference/models.py:15 +msgid "title" +msgstr "タイトル" + +#: conference/models.py:18 conference/models.py:58 +msgid "start date" +msgstr "開始日" + +#: conference/models.py:19 conference/models.py:59 +msgid "end date" +msgstr "終了日" + +#: conference/models.py:22 +msgid "timezone" +msgstr "タイムゾーン" + +#: conference/models.py:41 conference/models.py:52 sponsorship/models.py:18 +msgid "conference" +msgstr "カンファレンス" + +#: conference/models.py:42 +msgid "conferences" +msgstr "カンファレンス" + +#: conference/models.py:54 sponsorship/models.py:19 sponsorship/models.py:155 +msgid "name" +msgstr "名前" + +#: conference/models.py:65 +msgid "section" +msgstr "セクション" + +#: conference/models.py:66 +msgid "sections" +msgstr "セクション" + +#: proposals/models.py:71 templates/conference/user_list.html:60 +msgid "Name" +msgstr "名前" + +#: proposals/models.py:86 +msgid "Brief Description" +msgstr "概要" + +#: proposals/models.py:88 +msgid "" +"If your proposal is accepted this will be made public and printed in the " +"program. Should be one paragraph, maximum 400 characters." +msgstr "" +"もし提案が受諾されたら、これは公開され、配布されるプログラムに印刷されます。" +"段落は一つのみで、400文字以内で記載してください。" + +#: proposals/models.py:92 +msgid "Detailed Abstract" +msgstr "提案の詳細" + +#: proposals/models.py:93 +msgid "" +"Detailed outline. Will be made public if your proposal is accepted. Edit " +"using Markdown." +msgstr "" +"講演詳細:提案が受諾された場合に公開されます。Markdownを用いて記述してください。" + +#: proposals/models.py:99 +msgid "" +"Anything else you'd like the program committee to know when making their " +"selection: your past experience, etc. This is not made public. Edit using Markdown." +msgstr "" +"プログラム委員に、講演者の過去の経験など、とくに伝えたいことがあれば、記述し" +"てください。これは公開されることはありません。Markdownを用いて記述してください。" + +#: proposals/models.py:153 +msgid "Pending" +msgstr "ペンディング" + +#: proposals/models.py:154 templates/proposals/_pending_proposal_row.html:16 +msgid "Accepted" +msgstr "アクセプト" + +#: proposals/models.py:155 +msgid "Declined" +msgstr "リジェクト" + +#: sponsorship/models.py:20 +msgid "order" +msgstr "順序" + +#: sponsorship/models.py:21 +msgid "cost" +msgstr "経費" + +#: sponsorship/models.py:22 sponsorship/models.py:156 +msgid "description" +msgstr "記述" + +#: sponsorship/models.py:22 +msgid "This is private." +msgstr "これは非公開です。" + +#: sponsorship/models.py:26 +msgid "sponsor level" +msgstr "スポンサーのグレード" + +#: sponsorship/models.py:27 +msgid "sponsor levels" +msgstr "スポンサーのグレード" + +#: sponsorship/models.py:38 +msgid "applicant" +msgstr "応募者" + +#: sponsorship/models.py:41 +msgid "Sponsor Name" +msgstr "スポンサー名" + +#: sponsorship/models.py:42 +msgid "external URL" +msgstr "外部URL" + +#: sponsorship/models.py:43 +msgid "annotation" +msgstr "" + +#: sponsorship/models.py:44 +msgid "Contact Name" +msgstr "連絡先の名前" + +#: sponsorship/models.py:45 +msgid "Contact Email" +msgstr "連絡先のEmail" + +#: sponsorship/models.py:46 sponsorship/models.py:167 +msgid "level" +msgstr "グレード" + +#: sponsorship/models.py:47 +msgid "added" +msgstr "" + +#: sponsorship/models.py:48 +msgid "active" +msgstr "有効" + +#: sponsorship/models.py:60 sponsorship/models.py:182 +msgid "sponsor" +msgstr "スポンサー" + +#: sponsorship/models.py:61 +msgid "sponsors" +msgstr "スポンサー" + +#: sponsorship/models.py:157 +msgid "type" +msgstr "タイプ" + +#: sponsorship/models.py:166 sponsorship/models.py:183 +msgid "benefit" +msgstr "" + +#: sponsorship/models.py:170 sponsorship/models.py:187 +msgid "max words" +msgstr "" + +#: sponsorship/models.py:171 sponsorship/models.py:188 +msgid "other limits" +msgstr "他の制限" + +#: sponsorship/models.py:192 +msgid "text" +msgstr "テキスト" + +#: sponsorship/models.py:193 +msgid "file" +msgstr "ファイル" + +#: templates/dashboard.html:16 +msgid "Speaking" +msgstr "講演" + +#: templates/dashboard.html:92 templates/sponsorship/detail.html:8 +msgid "Sponsorship" +msgstr "スポンサー" + +#: templates/dashboard.html:132 templates/reviews/review_detail.html:75 +msgid "Reviews" +msgstr "レビュー" + +#: templates/dashboard.html:177 +msgid "Teams" +msgstr "チーム" + +#: templates/boxes/box.html:9 +msgid "Editing content:" +msgstr "コンテンツ編集:" + +#: templates/cms/page_edit.html:11 +msgid "Edit page at:" +msgstr "" + +#: templates/conference/user_list.html:59 +msgid "Email" +msgstr "電子メール" + +#: templates/conference/user_list.html:61 +msgid "Speaker Profile?" +msgstr "講演者のプロフィール" + +#: templates/emails/teams_user_applied/message.html:3 +#, python-format +msgid "" +"\n" +"

    \n" +" User \"%(username)s\" has applied to join %(team_name)s on " +"%(site_name)s.\n" +"

    \n" +"\n" +"

    \n" +" To accept this application and see any other pending applications, " +"visit the following url:\n" +" http://%(site_url)s" +"%(team_url)s\n" +"

    \n" +msgstr "" +"\n" +"

    \n" +" ユーザ \"%(username)s\" は、%(site_name)s の%(team_name)s に" +"応募しました。 \n" +"

    \n" +"\n" +"

    \n" +" この応募を受諾したり、他の保留されている応募者を確認するには、つぎの" +"URLを参照してください:\n" +" http://%(site_url)s" +"%(team_url)s\n" +"

    \n" + +#: templates/emails/teams_user_applied/subject.txt:1 +#, python-format +msgid "%(username)s has applied to to join \"%(team)s\"" +msgstr "%(username)s は、 \"%(team)s\"に応募しました。" + +#: templates/emails/teams_user_invited/message.html:3 +#, python-format +msgid "" +"\n" +"

    \n" +" You have been invited to join %(team_name)s on " +"%(site_name)s.\n" +"

    \n" +"\n" +"

    \n" +" To accept this invitation, visit the following url:\n" +" http://%(site_url)s" +"%(team_url)s\n" +"

    \n" +msgstr "" +"\n" +"

    \n" +" %(site_name)sの%(team_name)sに参加するよう招待されました。\n" +"

    \n" +"\n" +"

    \n" +" 招待を受諾するには、つぎのURLをクリックしてください:\n" +" http://%(site_url)s" +"%(team_url)s\n" +"

    \n" + +#: templates/emails/teams_user_invited/subject.txt:1 +#, python-format +msgid "You have been invited to join \"%(team)s\"" +msgstr " \"%(team)s\"に参加するよう招待されました。" + +#: templates/proposals/_pending_proposal_row.html:12 +msgid "Cancelled" +msgstr "キャンセル済み" + +#: templates/proposals/_pending_proposal_row.html:18 +msgid "Submitted" +msgstr "投稿済み" + +#: templates/proposals/_pending_proposal_row.html:21 +msgid "Invited" +msgstr "招待された" + +#: templates/proposals/_pending_proposal_row.html:30 +msgid "Choose Response" +msgstr "応答の選択" + +#: templates/proposals/_pending_proposal_row.html:35 +msgid "Accept invitation" +msgstr "招待を受諾" + +#: templates/proposals/_pending_proposal_row.html:37 +msgid "Decline invitation" +msgstr "招待を拒否" + +#: templates/proposals/_proposal_fields.html:4 +msgid "Submitted by" +msgstr "" + +#: templates/proposals/_proposal_fields.html:7 +msgid "Track" +msgstr "トラック" + +#: templates/proposals/_proposal_fields.html:10 +msgid "Audience Level" +msgstr "受講者のレベル" + +#: templates/proposals/_proposal_fields.html:14 +msgid "Additional Speakers" +msgstr "追加の講演者" + +#: templates/proposals/_proposal_fields.html:21 +msgid "Invitation Sent" +msgstr "送信された招待" + +#: templates/proposals/_proposal_fields.html:28 +msgid "Description" +msgstr "記述" + +#: templates/proposals/_proposal_fields.html:31 +msgid "Abstract" +msgstr "講演概要" + +#: templates/proposals/_proposal_fields.html:34 +msgid "Notes" +msgstr "備考" + +#: templates/proposals/_proposal_fields.html:37 +msgid "Speaker Bio" +msgstr "講演者略歴" + +#: templates/proposals/_proposal_fields.html:40 +msgid "Documents" +msgstr "資料" + +#: templates/proposals/proposal_cancel.html:7 +msgid "Cancel Proposal" +msgstr "講演提案のキャンセル" + +#: templates/proposals/proposal_cancel.html:16 +msgid "No, keep it for now" +msgstr "いいえ、このままにします。" + +#: templates/proposals/proposal_detail.html:14 +msgid "Edit this proposal" +msgstr "この提案を編集" + +#: templates/proposals/proposal_detail.html:17 +msgid "Cancel this proposal" +msgstr "この提案をキャンセル" + +#: templates/proposals/proposal_detail.html:21 +msgid "Remove me from this proposal" +msgstr "この提案から自分を削除する。" + +#: templates/proposals/proposal_detail.html:33 +#: templates/reviews/review_detail.html:74 +msgid "Proposal Details" +msgstr "提案の詳細" + +#: templates/proposals/proposal_detail.html:35 +#: templates/proposals/proposal_detail.html:47 +msgid "Supporting Documents" +msgstr "補足資料" + +#: templates/proposals/proposal_detail.html:38 +msgid "Reviewer Feedback" +msgstr "レビュアーからのフィードバック" + +#: templates/proposals/proposal_detail.html:57 +msgid "delete" +msgstr "削除" + +#: templates/proposals/proposal_detail.html:64 +msgid "No supporting documents attached to this proposal." +msgstr "この提案に補足資料は添付されていません。" + +#: templates/proposals/proposal_detail.html:66 +msgid "Add Document" +msgstr "資料の追加" + +#: templates/proposals/proposal_detail.html:73 +msgid "Conversation with Reviewers" +msgstr "レビュアーとの会話記録" + +#: templates/proposals/proposal_detail.html:83 +msgid "Leave a Message" +msgstr "メッセージを残す" + +#: templates/proposals/proposal_detail.html:85 +msgid "You can leave a message for the reviewers here." +msgstr "メッセージをレビューアに残すことができます。" + +#: templates/proposals/proposal_detail.html:94 +msgid "Submit" +msgstr "投稿する" + +#: templates/proposals/proposal_speaker_manage.html:7 +msgid "Proposal:" +msgstr "講演提案:" + +#: templates/proposals/proposal_speaker_manage.html:10 +msgid "Edit proposal" +msgstr "講演提案の編集" + +#: templates/proposals/proposal_speaker_manage.html:14 +msgid "Current Speakers" +msgstr "現在登録されている講演者" + +#: templates/proposals/proposal_speaker_manage.html:20 +msgid "pending invitation" +msgstr "保留されている招待" + +#: templates/proposals/proposal_speaker_manage.html:24 +msgid "Add another speaker" +msgstr "他の講演者を追加" + +#: templates/proposals/proposal_submit.html:6 +msgid "Submit A Proposal" +msgstr "提案を投稿" + +#: templates/reviews/_review_table.html:6 +#: templates/reviews/result_notification.html:45 +msgid "Speaker / Title" +msgstr "講演者/タイトル" + +#: templates/reviews/_review_table.html:7 +#: templates/reviews/result_notification.html:46 +msgid "Category" +msgstr "カテゴリ" + +#: templates/reviews/_review_table.html:9 +msgid "+1" +msgstr "" + +#: templates/reviews/_review_table.html:10 +msgid "+0" +msgstr "" + +#: templates/reviews/_review_table.html:11 +msgid "-0" +msgstr "" + +#: templates/reviews/_review_table.html:12 +msgid "-1" +msgstr "" + +#: templates/reviews/_review_table.html:13 +msgid "Your Rating" +msgstr "あなたの投票" + +#: templates/reviews/base.html:64 +msgid "All Reviews" +msgstr "全てのレビュアー" + +#: templates/reviews/base.html:77 +msgid "Voting Status" +msgstr "投票の状態" + +#: templates/reviews/result_notification.html:47 +msgid "Status" +msgstr "状態" + +#: templates/reviews/result_notification.html:48 +msgid "Notified?" +msgstr "連絡済み?" + +#: templates/reviews/review_detail.html:76 +msgid "Speaker Feedback" +msgstr "講演者へのフィードバック" + +#: templates/reviews/review_detail.html:84 +msgid "Current Results" +msgstr "現在のところの結果" + +#: templates/reviews/review_detail.html:91 +msgid "Total Responses" +msgstr "全応答数" + +#: templates/reviews/review_detail.html:108 +msgid "Submit Review" +msgstr "レビューの投稿" + +#: templates/reviews/review_detail.html:148 +msgid "Conversation with the submitter" +msgstr "投稿者への連絡" + +#: templates/reviews/review_detail.html:162 +msgid "Send a message" +msgstr "メッセージ送信" + +#: templates/reviews/review_detail.html:164 +msgid "" +"\n" +" If you'd like to communicate with the submitter, " +"use the following form and he or she will be\n" +" notified and given the opportunity to respond.\n" +" " +msgstr "" +"\n" +" もし、投稿者と連絡したい場合は、次の投稿フォームを" +"用いて知らせることができます。\n" +" そして、回答の機会を与えることができます。\n" +" " + +#: templates/schedule/_slot_edit.html:5 +msgid "Edit Slot" +msgstr "スロットの編集" + +#: templates/speakers/speaker_create.html:7 +#: templates/speakers/speaker_create.html:14 +msgid "Create Speaker Profile" +msgstr "講演者プロフィールの作成" + +#: templates/speakers/speaker_edit.html:7 +#: templates/speakers/speaker_edit.html:14 +msgid "Edit Speaker Profile" +msgstr "講演者プロフィールの編集" + +#: templates/sponsorship/add.html:7 templates/sponsorship/add.html.py:14 +msgid "Add a Sponsor" +msgstr "スポンサーの追加" + +#: templates/sponsorship/apply.html:7 +msgid "Apply to be a Sponsor" +msgstr "スポンサーに応募する" + +#: templates/sponsorship/apply.html:17 +msgid "Apply to Be a Sponsor" +msgstr "スポンサーに応募する" + +#: templates/sponsorship/list.html:7 templates/sponsorship/list.html.py:14 +msgid "About Our Sponsors" +msgstr "スポンサーについて" From da82edf26d1bc4db8f3020853a725f4aaafa2f48 Mon Sep 17 00:00:00 2001 From: Hiroshi Miura Date: Sun, 21 Jun 2015 11:11:04 +0900 Subject: [PATCH 596/751] Enhanced sponsorship features MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It is a part of feedback from PyConJP development. Here picks a sponsorship benefit management enhancement. * sponsor zip download * export sponsor data as csv ``` commit eb3261c12c910ec562e016f10431cc48747baef8 Author: Dan Poirier Date: Wed Aug 21 11:51:20 2013 -0400 Enhanced sponsor admin page For #67: * admin list sorted by name * not limited to 100 per page * a sortable visual indicator for each sponsorship benefit (completed, missing, not applicable) * also the sponsorship level, admin contact name, and an active or not indication * the ones we have today: print logo, web logo, print description, web description and the ad. * an action pick list like “email” and check mark the sponsors * I want to email based on the assets that are missing. * in subject and body, replace %%NAME%% by sponsor name ``` Signed-off-by: Hiroshi Miura --- symposion/sponsorship/urls.py | 1 + symposion/sponsorship/views.py | 126 ++++++++++++++++++++++++++++++--- 2 files changed, 118 insertions(+), 9 deletions(-) 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 From fbf356b335dea6673dfca2ca61d0e2bbd6aed173 Mon Sep 17 00:00:00 2001 From: Hiroshi Miura Date: Sun, 21 Jun 2015 13:40:49 +0900 Subject: [PATCH 597/751] add export sponsor data zip command Signed-off-by: Hiroshi Miura --- .../commands/export_sponsors_data.py | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 symposion/sponsorship/management/commands/export_sponsors_data.py 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")) From 999c458c1b237bb9b2442cefeef825a7a4c82a71 Mon Sep 17 00:00:00 2001 From: Hiroshi Miura Date: Sun, 21 Jun 2015 13:57:05 +0900 Subject: [PATCH 598/751] add test Signed-off-by: Hiroshi Miura --- symposion/sponsorship/tests.py | 307 +++++++++++++++++++++++++++++++++ 1 file changed, 307 insertions(+) create mode 100644 symposion/sponsorship/tests.py 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") From c43b84fe945457ebfceee40869190f831614bd56 Mon Sep 17 00:00:00 2001 From: Hiroshi Miura Date: Sun, 21 Jun 2015 14:55:46 +0900 Subject: [PATCH 599/751] display_url Signed-off-by: Hiroshi Miura --- symposion/sponsorship/models.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/symposion/sponsorship/models.py b/symposion/sponsorship/models.py index e29ac3d1..95feeaaa 100644 --- a/symposion/sponsorship/models.py +++ b/symposion/sponsorship/models.py @@ -39,6 +39,7 @@ class Sponsor(models.Model): null=True) name = models.CharField(_("Sponsor Name"), max_length=100) + display_url = models.URLField(_("display URL"), blank=True) external_url = models.URLField(_("external URL")) annotation = models.TextField(_("annotation"), blank=True) contact_name = models.CharField(_("Contact Name"), max_length=100) @@ -65,6 +66,12 @@ class Sponsor(models.Model): return reverse("sponsor_detail", kwargs={"pk": self.pk}) return reverse("sponsor_list") + def get_display_url(self): + if self.display_url: + return self.display_url + else: + return self.external_url + @property def website_logo(self): if self.sponsor_logo is None: From 3653cf3c69b5963b922d0c954718ab2c578806fd Mon Sep 17 00:00:00 2001 From: Hiroshi Miura Date: Sun, 21 Jun 2015 14:50:26 +0900 Subject: [PATCH 600/751] admin contact Feedback from pycon development ``` commit eb3261c12c910ec562e016f10431cc48747baef8 Author: Dan Poirier Date: Wed Aug 21 11:51:20 2013 -0400 Enhanced sponsor admin page ``` Signed-off-by: Hiroshi Miura --- symposion/sponsorship/admin.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/symposion/sponsorship/admin.py b/symposion/sponsorship/admin.py index b64c1a13..77a84b8d 100644 --- a/symposion/sponsorship/admin.py +++ b/symposion/sponsorship/admin.py @@ -1,4 +1,7 @@ from django.contrib import admin +from django.utils.html import escape +from django.utils.safestring import mark_safe +from django.utils.translation import ugettext_lazy as _ from symposion.sponsorship.models import SponsorLevel, Sponsor, Benefit, BenefitLevel, \ SponsorBenefit @@ -43,7 +46,16 @@ class SponsorAdmin(admin.ModelAdmin): }) ] inlines = [SponsorBenefitInline] - list_display = ["name", "external_url", "level", "active"] + list_display = ["name", "external_url", "level", "active", "contact", "applicant_field"] + + def contact(self, sponsor): + return mark_safe('%s' % (escape(sponsor.contact_email), escape(sponsor.contact_name))) + + def applicant_field(self, sponsor): + name = sponsor.applicant.get_full_name() + email = sponsor.applicant.email + return mark_safe('%s' % (escape(email), escape(name))) + applicant_field.short_description = _(u"Applicant") def get_form(self, *args, **kwargs): # @@@ kinda ugly but using choices= on NullBooleanField is broken From b50974263c36682eab7ccab600201455af754076 Mon Sep 17 00:00:00 2001 From: Hiroshi Miura Date: Sun, 21 Jun 2015 14:48:17 +0900 Subject: [PATCH 601/751] i18n sponsor fields Feedback from PyConJP development ``` commit 5973e32ebdc231b209b5c058664e8b2b4a1dbc54 Author: MURAOKA Yusuke Date: Mon Mar 31 15:35:40 2014 +0900 introduce Benefit.content_type which is used to display localized text ``` Signed-off-by: Hiroshi Miura --- symposion/sponsorship/models.py | 17 ++++++-- .../templatetags/sponsorship_tags.py | 43 +++++++++++++++++++ 2 files changed, 57 insertions(+), 3 deletions(-) diff --git a/symposion/sponsorship/models.py b/symposion/sponsorship/models.py index e29ac3d1..d6319f0e 100644 --- a/symposion/sponsorship/models.py +++ b/symposion/sponsorship/models.py @@ -1,5 +1,6 @@ import datetime +from django.conf import settings from django.core.exceptions import ValidationError from django.core.urlresolvers import reverse from django.db import models @@ -144,9 +145,17 @@ post_save.connect(_check_level_change, sender=Sponsor) BENEFIT_TYPE_CHOICES = [ ("text", "Text"), + ("richtext", "Rich Text"), ("file", "File"), ("weblogo", "Web Logo"), - ("simple", "Simple") + ("simple", "Simple"), + ("option", "Option") +] + +CONTENT_TYPE_CHOICES = [ + ("simple", "Simple"), +] + [ + ("listing_text_%s" % lang, "Listing Text (%s)" % label) for lang, label in settings.LANGUAGES ] @@ -154,8 +163,10 @@ class Benefit(models.Model): name = models.CharField(_("name"), max_length=100) description = models.TextField(_("description"), blank=True) - type = models.CharField(_("type"), choices=BENEFIT_TYPE_CHOICES, max_length=10, - default="simple") + type = models.CharField(_("type"), choices=BENEFIT_TYPE_CHOICES, + max_length=10, default="simple") + content_type = models.CharField(_("content type"), choices=CONTENT_TYPE_CHOICES, + max_length=20, default="simple") def __unicode__(self): return self.name diff --git a/symposion/sponsorship/templatetags/sponsorship_tags.py b/symposion/sponsorship/templatetags/sponsorship_tags.py index d88243c4..efbdda09 100644 --- a/symposion/sponsorship/templatetags/sponsorship_tags.py +++ b/symposion/sponsorship/templatetags/sponsorship_tags.py @@ -1,4 +1,5 @@ from django import template +from django.template.defaultfilters import linebreaks, urlize from symposion.conference.models import current_conference from symposion.sponsorship.models import Sponsor, SponsorLevel @@ -75,3 +76,45 @@ def sponsor_levels(parser, token): {% sponsor_levels as levels %} """ return SponsorLevelNode.handle_token(parser, token) + + +class LocalizedTextNode(template.Node): + + @classmethod + def handle_token(cls, parser, token): + bits = token.split_contents() + if len(bits) == 3: + return cls(bits[2], bits[1][1:-1]) + elif len(bits) == 5 and bits[-2] == "as": + return cls(bits[2], bits[1][1:-1], bits[4]) + else: + raise template.TemplateSyntaxError("%r takes 'as var'" % bits[0]) + + def __init__(self, sponsor, content_type, context_var=None): + self.sponsor_var = template.Variable(sponsor) + self.content_type = content_type + self.content_var = context_var + + def render(self, context): + s = '' + try: + sponsor = self.sponsor_var.resolve(context) + content_type = '%s_%s' % (self.content_type, context['request'].LANGUAGE_CODE) + texts = sponsor.sponsor_benefits.filter(benefit__content_type=content_type) + if texts.count() > 0: + s = linebreaks(urlize(texts[0].text, autoescape=True)) + if self.content_var: + context[self.content_var] = s + s = '' + except: + pass + return s + + +@register.tag +def localized_text(parser, token): + """ + {% localized_text "content_type" sponsor %} + {% localized_text "content_type" sponsor as localized_text %} + """ + return LocalizedTextNode.handle_token(parser, token) From a9efef01d0d462f5b804a515e96862e4503cd547 Mon Sep 17 00:00:00 2001 From: Hiroshi Miura Date: Fri, 26 Jun 2015 09:22:36 +0900 Subject: [PATCH 602/751] distribute locale files Signed-off-by: Hiroshi Miura --- MANIFEST.in | 1 + 1 file changed, 1 insertion(+) diff --git a/MANIFEST.in b/MANIFEST.in index 383827a5..55bb6eaa 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,3 +1,4 @@ include README LICENSE recursive-include symposion/templates *.html *.txt recursive-include symposion/static * +recursive-include symposion/locale * From 8bc696a2dcc057f0be7f4026e29539aacdb5b114 Mon Sep 17 00:00:00 2001 From: Hiroshi Miura Date: Sat, 11 Jul 2015 11:27:12 +0900 Subject: [PATCH 603/751] sponsorship benefit relation administration Signed-off-by: Hiroshi Miura --- symposion/sponsorship/admin.py | 50 +++++++++++++++++++++++++++++---- symposion/sponsorship/models.py | 39 ++++++++++++++++++++++++- 2 files changed, 82 insertions(+), 7 deletions(-) diff --git a/symposion/sponsorship/admin.py b/symposion/sponsorship/admin.py index b64c1a13..c38228fb 100644 --- a/symposion/sponsorship/admin.py +++ b/symposion/sponsorship/admin.py @@ -1,7 +1,8 @@ from django.contrib import admin +from django.utils.translation import ugettext_lazy as _ -from symposion.sponsorship.models import SponsorLevel, Sponsor, Benefit, BenefitLevel, \ - SponsorBenefit +from symposion.sponsorship.models import SponsorLevel, Sponsor, Benefit, \ + BenefitLevel, SponsorBenefit, BENEFITS class BenefitLevelInline(admin.TabularInline): @@ -44,29 +45,66 @@ class SponsorAdmin(admin.ModelAdmin): ] inlines = [SponsorBenefitInline] list_display = ["name", "external_url", "level", "active"] + list_filter = ["level", "active"] def get_form(self, *args, **kwargs): # @@@ kinda ugly but using choices= on NullBooleanField is broken form = super(SponsorAdmin, self).get_form(*args, **kwargs) form.base_fields["active"].widget.choices = [ - (u"1", "unreviewed"), - (u"2", "approved"), - (u"3", "rejected") + (u"1", _("unreviewed")), + (u"2", _("approved")), + (u"3", _("rejected")) ] return form + # Define accessor functions for our benefit fields and add them to + # list_display, so we can sort on them and give them sensible names. + # Add the fields to list_filters while we're at it. + for benefit in BENEFITS: + benefit_name = benefit['name'] + field_name = benefit['field_name'] + + def func_generator(ben): + def column_func(obj): + return getattr(obj, ben['field_name']) + column_func.short_description = ben['column_title'] + column_func.boolean = True + column_func.admin_order_field = ben['field_name'] + return column_func + list_display.append(func_generator(benefit)) + list_filter.append(field_name) + + def save_related(self, request, form, formsets, change): + super(SponsorAdmin, self).save_related(request, form, formsets, change) + obj = form.instance + obj.save() + class BenefitAdmin(admin.ModelAdmin): - list_display = ["name", "type", "description"] + list_display = ["name", "type", "description", "levels"] inlines = [BenefitLevelInline] + def levels(self, benefit): + return u", ".join(l.level.name for l in benefit.benefit_levels.all()) + class SponsorLevelAdmin(admin.ModelAdmin): inlines = [BenefitLevelInline] +class SponsorBenefitAdmin(admin.ModelAdmin): + list_display = ('benefit', 'sponsor', 'active', '_is_complete', 'show_text') + + def show_text(self, sponsor_benefit): + if sponsor_benefit.text: + return sponsor_benefit.text[:100] + else: + return _("None") + + admin.site.register(SponsorLevel, SponsorLevelAdmin) admin.site.register(Sponsor, SponsorAdmin) admin.site.register(Benefit, BenefitAdmin) +admin.site.register(SponsorBenefit, SponsorBenefitAdmin) diff --git a/symposion/sponsorship/models.py b/symposion/sponsorship/models.py index e29ac3d1..d1e8551e 100644 --- a/symposion/sponsorship/models.py +++ b/symposion/sponsorship/models.py @@ -13,6 +13,35 @@ from symposion.conference.models import Conference from symposion.sponsorship.managers import SponsorManager +# The benefits we track as individual fields on sponsors +# Names are the names in the database as defined by organizers. +# Field names are the benefit names, lowercased, with +# spaces changed to _, and with "_benefit" appended. +# Column titles are arbitrary. + +# "really just care about the ones we have today: print logo, web logo, print description, web description and the ad." + +BENEFITS = [ + { + 'name': 'Web logo', + 'field_name': 'web_logo_benefit', + 'column_title': _(u"Web Logo"), + }, { + 'name': 'Print logo', + 'field_name': 'print_logo_benefit', + 'column_title': _(u"Print Logo"), + }, { + 'name': 'Company Description', + 'field_name': 'company_description_benefit', + 'column_title': _(u"Web Desc"), + }, { + 'name': 'Print Description', + 'field_name': 'print_description_benefit', + 'column_title': _(u"Print Desc"), + } +] + + class SponsorLevel(models.Model): conference = models.ForeignKey(Conference, verbose_name=_("conference")) @@ -27,7 +56,7 @@ class SponsorLevel(models.Model): verbose_name_plural = _("sponsor levels") def __unicode__(self): - return self.name + return u"%s %s" % (self.conference, self.name) def sponsors(self): return self.sponsor_set.filter(active=True).order_by("added") @@ -51,6 +80,13 @@ class Sponsor(models.Model): sponsor_logo = models.ForeignKey("SponsorBenefit", related_name="+", null=True, blank=True, editable=False) + # Whether things are complete + # True = complete, False = incomplate, Null = n/a for this sponsor level + web_logo_benefit = models.NullBooleanField(_("Web logo benefit"), help_text=_(u"Web logo benefit is complete")) + print_logo_benefit = models.NullBooleanField(_("Print logo benefit"), help_text=_(u"Print logo benefit is complete")) + print_description_benefit = models.NullBooleanField(_("Print description benefit"), help_text=_(u"Print description benefit is complete")) + company_description_benefit = models.NullBooleanField(_("Company description benefit"), help_text=_(u"Company description benefit is complete")) + objects = SponsorManager() def __unicode__(self): @@ -59,6 +95,7 @@ class Sponsor(models.Model): class Meta: verbose_name = _("sponsor") verbose_name_plural = _("sponsors") + ordering = ['name'] def get_absolute_url(self): if self.active: From 0d7df4db004c11117f37270b323f3a29ee79e9f6 Mon Sep 17 00:00:00 2001 From: Hiroshi Miura Date: Sat, 11 Jul 2015 11:27:30 +0900 Subject: [PATCH 604/751] sponsor benfit is complete? Signed-off-by: Hiroshi Miura --- symposion/sponsorship/models.py | 46 +++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/symposion/sponsorship/models.py b/symposion/sponsorship/models.py index d1e8551e..3378a621 100644 --- a/symposion/sponsorship/models.py +++ b/symposion/sponsorship/models.py @@ -97,6 +97,14 @@ class Sponsor(models.Model): verbose_name_plural = _("sponsors") ordering = ['name'] + def save(self, *args, **kwargs): + # Set fields related to benefits being complete + for benefit in BENEFITS: + field_name = benefit['field_name'] + benefit_name = benefit['name'] + setattr(self, field_name, self.benefit_is_complete(benefit_name)) + super(Sponsor, self).save(*args, **kwargs) + def get_absolute_url(self): if self.active: return reverse("sponsor_detail", kwargs={"pk": self.pk}) @@ -166,6 +174,19 @@ class Sponsor(models.Model): def send_coordinator_emails(self): pass # @@@ should this just be done centrally? + def benefit_is_complete(self, name): + """Return True - benefit is complete, False - benefit is not complete, + or None - benefit not applicable for this sponsor's level """ + if BenefitLevel.objects.filter(level=self.level, benefit__name=name).exists(): + try: + benefit = self.sponsor_benefits.get(benefit__name=name) + except SponsorBenefit.DoesNotExist: + return False + else: + return benefit.is_complete + else: + return None # Not an applicable benefit for this sponsor's level + def _store_initial_level(sender, instance, **kwargs): if instance: @@ -229,12 +250,23 @@ class SponsorBenefit(models.Model): text = models.TextField(_("text"), blank=True) upload = models.FileField(_("file"), blank=True, upload_to="sponsor_files") + # Whether any assets required from the sponsor have been provided + # (e.g. a logo file for a Web logo benefit). + is_complete = models.NullBooleanField(_("Complete?"), help_text=_(u"True - benefit complete; False - benefit incomplete; Null - n/a")) + class Meta: ordering = ["-active"] def __unicode__(self): return u"%s - %s" % (self.sponsor, self.benefit) + def save(self, *args, **kwargs): + # Validate - save() doesn't clean your model by default, so call + # it explicitly before saving + self.full_clean() + self.is_complete = self._is_complete() + super(SponsorBenefit, self).save(*args, **kwargs) + def clean(self): num_words = len(self.text.split()) if self.max_words and num_words > self.max_words: @@ -252,3 +284,17 @@ class SponsorBenefit(models.Model): elif self.benefit.type == "text": return ["text"] return [] + + def _is_complete(self): + return self.active and \ + ((self.benefit.type in ('text', 'richtext', 'simple') and bool(self.text)) + or (self.benefit.type in ('file', 'weblogo') and bool(self.upload))) + + +def _denorm_weblogo(sender, instance, created, **kwargs): + if instance: + if instance.benefit.type == "weblogo" and instance.upload: + sponsor = instance.sponsor + sponsor.sponsor_logo = instance + sponsor.save() +post_save.connect(_denorm_weblogo, sender=SponsorBenefit) From d1740081497421340e29b5993aebba38f64b90e8 Mon Sep 17 00:00:00 2001 From: Hiroshi Miura Date: Sat, 11 Jul 2015 11:22:15 +0900 Subject: [PATCH 605/751] sponsor benefit type richitext, simple and option Signed-off-by: Hiroshi Miura --- symposion/sponsorship/forms.py | 3 +++ symposion/sponsorship/models.py | 5 +++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/symposion/sponsorship/forms.py b/symposion/sponsorship/forms.py index 33b52f42..164d62fd 100644 --- a/symposion/sponsorship/forms.py +++ b/symposion/sponsorship/forms.py @@ -47,6 +47,9 @@ class SponsorDetailsForm(forms.ModelForm): class SponsorBenefitsInlineFormSet(BaseInlineFormSet): + def __init__(self, *args, **kwargs): + kwargs['queryset'] = kwargs.get('queryset', self.model._default_manager).exclude(benefit__type="option") + super(SponsorBenefitsInlineFormSet, self).__init__(*args, **kwargs) def _construct_form(self, i, **kwargs): form = super(SponsorBenefitsInlineFormSet, self)._construct_form(i, **kwargs) diff --git a/symposion/sponsorship/models.py b/symposion/sponsorship/models.py index 3378a621..343800b6 100644 --- a/symposion/sponsorship/models.py +++ b/symposion/sponsorship/models.py @@ -258,7 +258,8 @@ class SponsorBenefit(models.Model): ordering = ["-active"] def __unicode__(self): - return u"%s - %s" % (self.sponsor, self.benefit) + return u"%s - %s (%s)" % (self.sponsor, self.benefit, + self.benefit.type) def save(self, *args, **kwargs): # Validate - save() doesn't clean your model by default, so call @@ -281,7 +282,7 @@ class SponsorBenefit(models.Model): """ if self.benefit.type == "file" or self.benefit.type == "weblogo": return ["upload"] - elif self.benefit.type == "text": + elif self.benefit.type in ("text", "richtext", "simple", "option"): return ["text"] return [] From 0fa4801941c41a83d8ad876bd6620cb0156fe902 Mon Sep 17 00:00:00 2001 From: Hiroshi Miura Date: Sat, 11 Jul 2015 16:02:35 +0900 Subject: [PATCH 606/751] drop old unused chosen Signed-off-by: Hiroshi Miura --- symposion/static/chosen/chosen.css | 384 ------------------- symposion/static/chosen/chosen.jquery.min.js | 10 - 2 files changed, 394 deletions(-) delete mode 100755 symposion/static/chosen/chosen.css delete mode 100755 symposion/static/chosen/chosen.jquery.min.js diff --git a/symposion/static/chosen/chosen.css b/symposion/static/chosen/chosen.css deleted file mode 100755 index 1ca56178..00000000 --- a/symposion/static/chosen/chosen.css +++ /dev/null @@ -1,384 +0,0 @@ -/* @group Base */ -.chzn-container { - font-size: 13px; - position: relative; - display: inline-block; - zoom: 1; - *display: inline; -} -.chzn-container .chzn-drop { - background: #fff; - border: 1px solid #aaa; - border-top: 0; - position: absolute; - top: 29px; - left: 0; - -webkit-box-shadow: 0 4px 5px rgba(0,0,0,.15); - -moz-box-shadow : 0 4px 5px rgba(0,0,0,.15); - box-shadow : 0 4px 5px rgba(0,0,0,.15); - z-index: 1010; -} -/* @end */ - -/* @group Single Chosen */ -.chzn-container-single .chzn-single { - background-color: #ffffff; - filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#eeeeee', GradientType=0 ); - background-image: -webkit-gradient(linear, 0 0, 0 100%, color-stop(20%, #ffffff), color-stop(50%, #f6f6f6), color-stop(52%, #eeeeee), color-stop(100%, #f4f4f4)); - background-image: -webkit-linear-gradient(top, #ffffff 20%, #f6f6f6 50%, #eeeeee 52%, #f4f4f4 100%); - background-image: -moz-linear-gradient(top, #ffffff 20%, #f6f6f6 50%, #eeeeee 52%, #f4f4f4 100%); - background-image: -o-linear-gradient(top, #ffffff 20%, #f6f6f6 50%, #eeeeee 52%, #f4f4f4 100%); - background-image: linear-gradient(#ffffff 20%, #f6f6f6 50%, #eeeeee 52%, #f4f4f4 100%); - -webkit-border-radius: 5px; - -moz-border-radius : 5px; - border-radius : 5px; - -moz-background-clip : padding; - -webkit-background-clip: padding-box; - background-clip : padding-box; - border: 1px solid #aaaaaa; - -webkit-box-shadow: 0 0 3px #ffffff inset, 0 1px 1px rgba(0,0,0,0.1); - -moz-box-shadow : 0 0 3px #ffffff inset, 0 1px 1px rgba(0,0,0,0.1); - box-shadow : 0 0 3px #ffffff inset, 0 1px 1px rgba(0,0,0,0.1); - display: block; - overflow: hidden; - white-space: nowrap; - position: relative; - height: 23px; - line-height: 24px; - padding: 0 0 0 8px; - color: #444444; - text-decoration: none; -} -.chzn-container-single .chzn-default { - color: #999; -} -.chzn-container-single .chzn-single span { - margin-right: 26px; - display: block; - overflow: hidden; - white-space: nowrap; - -o-text-overflow: ellipsis; - -ms-text-overflow: ellipsis; - text-overflow: ellipsis; -} -.chzn-container-single .chzn-single abbr { - display: block; - position: absolute; - right: 26px; - top: 6px; - width: 12px; - height: 13px; - font-size: 1px; - background: url('chosen-sprite.png') right top no-repeat; -} -.chzn-container-single .chzn-single abbr:hover { - background-position: right -11px; -} -.chzn-container-single.chzn-disabled .chzn-single abbr:hover { - background-position: right top; -} -.chzn-container-single .chzn-single div { - position: absolute; - right: 0; - top: 0; - display: block; - height: 100%; - width: 18px; -} -.chzn-container-single .chzn-single div b { - background: url('chosen-sprite.png') no-repeat 0 0; - display: block; - width: 100%; - height: 100%; -} -.chzn-container-single .chzn-search { - padding: 3px 4px; - position: relative; - margin: 0; - white-space: nowrap; - z-index: 1010; -} -.chzn-container-single .chzn-search input { - background: #fff url('chosen-sprite.png') no-repeat 100% -22px; - background: url('chosen-sprite.png') no-repeat 100% -22px, -webkit-gradient(linear, 0 0, 0 100%, color-stop(1%, #eeeeee), color-stop(15%, #ffffff)); - background: url('chosen-sprite.png') no-repeat 100% -22px, -webkit-linear-gradient(top, #eeeeee 1%, #ffffff 15%); - background: url('chosen-sprite.png') no-repeat 100% -22px, -moz-linear-gradient(top, #eeeeee 1%, #ffffff 15%); - background: url('chosen-sprite.png') no-repeat 100% -22px, -o-linear-gradient(top, #eeeeee 1%, #ffffff 15%); - background: url('chosen-sprite.png') no-repeat 100% -22px, linear-gradient(#eeeeee 1%, #ffffff 15%); - margin: 1px 0; - padding: 4px 20px 4px 5px; - outline: 0; - border: 1px solid #aaa; - font-family: sans-serif; - font-size: 1em; -} -.chzn-container-single .chzn-drop { - -webkit-border-radius: 0 0 4px 4px; - -moz-border-radius : 0 0 4px 4px; - border-radius : 0 0 4px 4px; - -moz-background-clip : padding; - -webkit-background-clip: padding-box; - background-clip : padding-box; -} -/* @end */ - -.chzn-container-single-nosearch .chzn-search input { - position: absolute; - left: -9000px; -} - -/* @group Multi Chosen */ -.chzn-container-multi .chzn-choices { - background-color: #fff; - background-image: -webkit-gradient(linear, 0 0, 0 100%, color-stop(1%, #eeeeee), color-stop(15%, #ffffff)); - background-image: -webkit-linear-gradient(top, #eeeeee 1%, #ffffff 15%); - background-image: -moz-linear-gradient(top, #eeeeee 1%, #ffffff 15%); - background-image: -o-linear-gradient(top, #eeeeee 1%, #ffffff 15%); - background-image: linear-gradient(#eeeeee 1%, #ffffff 15%); - border: 1px solid #aaa; - margin: 0; - padding: 0; - cursor: text; - overflow: hidden; - height: auto !important; - height: 1%; - position: relative; -} -.chzn-container-multi .chzn-choices li { - float: left; - list-style: none; -} -.chzn-container-multi .chzn-choices .search-field { - white-space: nowrap; - margin: 0; - padding: 0; -} -.chzn-container-multi .chzn-choices .search-field input { - color: #666; - background: transparent !important; - border: 0 !important; - font-family: sans-serif; - font-size: 100%; - height: 15px; - padding: 5px; - margin: 1px 0; - outline: 0; - -webkit-box-shadow: none; - -moz-box-shadow : none; - box-shadow : none; -} -.chzn-container-multi .chzn-choices .search-field .default { - color: #999; -} -.chzn-container-multi .chzn-choices .search-choice { - -webkit-border-radius: 3px; - -moz-border-radius : 3px; - border-radius : 3px; - -moz-background-clip : padding; - -webkit-background-clip: padding-box; - background-clip : padding-box; - background-color: #e4e4e4; - filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#f4f4f4', endColorstr='#eeeeee', GradientType=0 ); - background-image: -webkit-gradient(linear, 0 0, 0 100%, color-stop(20%, #f4f4f4), color-stop(50%, #f0f0f0), color-stop(52%, #e8e8e8), color-stop(100%, #eeeeee)); - background-image: -webkit-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%); - background-image: -moz-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%); - background-image: -o-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%); - background-image: linear-gradient(#f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%); - -webkit-box-shadow: 0 0 2px #ffffff inset, 0 1px 0 rgba(0,0,0,0.05); - -moz-box-shadow : 0 0 2px #ffffff inset, 0 1px 0 rgba(0,0,0,0.05); - box-shadow : 0 0 2px #ffffff inset, 0 1px 0 rgba(0,0,0,0.05); - color: #333; - border: 1px solid #aaaaaa; - line-height: 13px; - padding: 3px 20px 3px 5px; - margin: 3px 0 3px 5px; - position: relative; - cursor: default; -} -.chzn-container-multi .chzn-choices .search-choice-focus { - background: #d4d4d4; -} -.chzn-container-multi .chzn-choices .search-choice .search-choice-close { - display: block; - position: absolute; - right: 3px; - top: 4px; - width: 12px; - height: 13px; - font-size: 1px; - background: url('chosen-sprite.png') right top no-repeat; -} -.chzn-container-multi .chzn-choices .search-choice .search-choice-close:hover { - background-position: right -11px; -} -.chzn-container-multi .chzn-choices .search-choice-focus .search-choice-close { - background-position: right -11px; -} -/* @end */ - -/* @group Results */ -.chzn-container .chzn-results { - margin: 0 4px 4px 0; - max-height: 240px; - padding: 0 0 0 4px; - position: relative; - overflow-x: hidden; - overflow-y: auto; - -webkit-overflow-scrolling: touch; -} -.chzn-container-multi .chzn-results { - margin: -1px 0 0; - padding: 0; -} -.chzn-container .chzn-results li { - display: none; - line-height: 15px; - padding: 5px 6px; - margin: 0; - list-style: none; -} -.chzn-container .chzn-results .active-result { - cursor: pointer; - display: list-item; -} -.chzn-container .chzn-results .highlighted { - background-color: #3875d7; - filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#3875d7', endColorstr='#2a62bc', GradientType=0 ); - background-image: -webkit-gradient(linear, 0 0, 0 100%, color-stop(20%, #3875d7), color-stop(90%, #2a62bc)); - background-image: -webkit-linear-gradient(top, #3875d7 20%, #2a62bc 90%); - background-image: -moz-linear-gradient(top, #3875d7 20%, #2a62bc 90%); - background-image: -o-linear-gradient(top, #3875d7 20%, #2a62bc 90%); - background-image: linear-gradient(#3875d7 20%, #2a62bc 90%); - color: #fff; -} -.chzn-container .chzn-results li em { - background: #feffde; - font-style: normal; -} -.chzn-container .chzn-results .highlighted em { - background: transparent; -} -.chzn-container .chzn-results .no-results { - background: #f4f4f4; - display: list-item; -} -.chzn-container .chzn-results .group-result { - cursor: default; - color: #999; - font-weight: bold; -} -.chzn-container .chzn-results .group-option { - padding-left: 15px; -} -.chzn-container-multi .chzn-drop .result-selected { - display: none; -} -.chzn-container .chzn-results-scroll { - background: white; - margin: 0 4px; - position: absolute; - text-align: center; - width: 321px; /* This should by dynamic with js */ - z-index: 1; -} -.chzn-container .chzn-results-scroll span { - display: inline-block; - height: 17px; - text-indent: -5000px; - width: 9px; -} -.chzn-container .chzn-results-scroll-down { - bottom: 0; -} -.chzn-container .chzn-results-scroll-down span { - background: url('chosen-sprite.png') no-repeat -4px -3px; -} -.chzn-container .chzn-results-scroll-up span { - background: url('chosen-sprite.png') no-repeat -22px -3px; -} -/* @end */ - -/* @group Active */ -.chzn-container-active .chzn-single { - -webkit-box-shadow: 0 0 5px rgba(0,0,0,.3); - -moz-box-shadow : 0 0 5px rgba(0,0,0,.3); - box-shadow : 0 0 5px rgba(0,0,0,.3); - border: 1px solid #5897fb; -} -.chzn-container-active .chzn-single-with-drop { - border: 1px solid #aaa; - -webkit-box-shadow: 0 1px 0 #fff inset; - -moz-box-shadow : 0 1px 0 #fff inset; - box-shadow : 0 1px 0 #fff inset; - background-color: #eee; - filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#eeeeee', endColorstr='#ffffff', GradientType=0 ); - background-image: -webkit-gradient(linear, 0 0, 0 100%, color-stop(20%, #eeeeee), color-stop(80%, #ffffff)); - background-image: -webkit-linear-gradient(top, #eeeeee 20%, #ffffff 80%); - background-image: -moz-linear-gradient(top, #eeeeee 20%, #ffffff 80%); - background-image: -o-linear-gradient(top, #eeeeee 20%, #ffffff 80%); - background-image: linear-gradient(#eeeeee 20%, #ffffff 80%); - -webkit-border-bottom-left-radius : 0; - -webkit-border-bottom-right-radius: 0; - -moz-border-radius-bottomleft : 0; - -moz-border-radius-bottomright: 0; - border-bottom-left-radius : 0; - border-bottom-right-radius: 0; -} -.chzn-container-active .chzn-single-with-drop div { - background: transparent; - border-left: none; -} -.chzn-container-active .chzn-single-with-drop div b { - background-position: -18px 1px; -} -.chzn-container-active .chzn-choices { - -webkit-box-shadow: 0 0 5px rgba(0,0,0,.3); - -moz-box-shadow : 0 0 5px rgba(0,0,0,.3); - box-shadow : 0 0 5px rgba(0,0,0,.3); - border: 1px solid #5897fb; -} -.chzn-container-active .chzn-choices .search-field input { - color: #111 !important; -} -/* @end */ - -/* @group Disabled Support */ -.chzn-disabled { - cursor: default; - opacity:0.5 !important; -} -.chzn-disabled .chzn-single { - cursor: default; -} -.chzn-disabled .chzn-choices .search-choice .search-choice-close { - cursor: default; -} - -/* @group Right to Left */ -.chzn-rtl { text-align: right; } -.chzn-rtl .chzn-single { padding: 0 8px 0 0; overflow: visible; } -.chzn-rtl .chzn-single span { margin-left: 26px; margin-right: 0; direction: rtl; } - -.chzn-rtl .chzn-single div { left: 3px; right: auto; } -.chzn-rtl .chzn-single abbr { - left: 26px; - right: auto; -} -.chzn-rtl .chzn-choices .search-field input { direction: rtl; } -.chzn-rtl .chzn-choices li { float: right; } -.chzn-rtl .chzn-choices .search-choice { padding: 3px 5px 3px 19px; margin: 3px 5px 3px 0; } -.chzn-rtl .chzn-choices .search-choice .search-choice-close { left: 4px; right: auto; background-position: right top;} -.chzn-rtl.chzn-container-single .chzn-results { margin: 0 0 4px 4px; padding: 0 4px 0 0; } -.chzn-rtl .chzn-results .group-option { padding-left: 0; padding-right: 15px; } -.chzn-rtl.chzn-container-active .chzn-single-with-drop div { border-right: none; } -.chzn-rtl .chzn-search input { - background: #fff url('chosen-sprite.png') no-repeat -38px -22px; - background: url('chosen-sprite.png') no-repeat -38px -22px, -webkit-gradient(linear, 0 0, 0 100%, color-stop(1%, #eeeeee), color-stop(15%, #ffffff)); - background: url('chosen-sprite.png') no-repeat -38px -22px, -webkit-linear-gradient(top, #eeeeee 1%, #ffffff 15%); - background: url('chosen-sprite.png') no-repeat -38px -22px, -moz-linear-gradient(top, #eeeeee 1%, #ffffff 15%); - background: url('chosen-sprite.png') no-repeat -38px -22px, -o-linear-gradient(top, #eeeeee 1%, #ffffff 15%); - background: url('chosen-sprite.png') no-repeat -38px -22px, linear-gradient(#eeeeee 1%, #ffffff 15%); - padding: 4px 5px 4px 20px; - direction: rtl; -} -/* @end */ diff --git a/symposion/static/chosen/chosen.jquery.min.js b/symposion/static/chosen/chosen.jquery.min.js deleted file mode 100755 index b7806202..00000000 --- a/symposion/static/chosen/chosen.jquery.min.js +++ /dev/null @@ -1,10 +0,0 @@ -// Chosen, a Select Box Enhancer for jQuery and Protoype -// by Patrick Filler for Harvest, http://getharvest.com -// -// Version 0.9.8 -// Full source at https://github.com/harvesthq/chosen -// Copyright (c) 2011 Harvest http://getharvest.com - -// MIT License, https://github.com/harvesthq/chosen/blob/master/LICENSE.md -// This file is generated by `cake build`, do not edit it by hand. -(function(){var SelectParser;SelectParser=function(){function SelectParser(){this.options_index=0,this.parsed=[]}return SelectParser.prototype.add_node=function(child){return child.nodeName==="OPTGROUP"?this.add_group(child):this.add_option(child)},SelectParser.prototype.add_group=function(group){var group_position,option,_i,_len,_ref,_results;group_position=this.parsed.length,this.parsed.push({array_index:group_position,group:!0,label:group.label,children:0,disabled:group.disabled}),_ref=group.childNodes,_results=[];for(_i=0,_len=_ref.length;_i<_len;_i++)option=_ref[_i],_results.push(this.add_option(option,group_position,group.disabled));return _results},SelectParser.prototype.add_option=function(option,group_position,group_disabled){if(option.nodeName==="OPTION")return option.text!==""?(group_position!=null&&(this.parsed[group_position].children+=1),this.parsed.push({array_index:this.parsed.length,options_index:this.options_index,value:option.value,text:option.text,html:option.innerHTML,selected:option.selected,disabled:group_disabled===!0?group_disabled:option.disabled,group_array_index:group_position,classes:option.className,style:option.style.cssText})):this.parsed.push({array_index:this.parsed.length,options_index:this.options_index,empty:!0}),this.options_index+=1},SelectParser}(),SelectParser.select_to_array=function(select){var child,parser,_i,_len,_ref;parser=new SelectParser,_ref=select.childNodes;for(_i=0,_len=_ref.length;_i<_len;_i++)child=_ref[_i],parser.add_node(child);return parser.parsed},this.SelectParser=SelectParser}).call(this),function(){var AbstractChosen,root;root=this,AbstractChosen=function(){function AbstractChosen(form_field,options){this.form_field=form_field,this.options=options!=null?options:{},this.set_default_values(),this.is_multiple=this.form_field.multiple,this.set_default_text(),this.setup(),this.set_up_html(),this.register_observers(),this.finish_setup()}return AbstractChosen.prototype.set_default_values=function(){var _this=this;return this.click_test_action=function(evt){return _this.test_active_click(evt)},this.activate_action=function(evt){return _this.activate_field(evt)},this.active_field=!1,this.mouse_on_container=!1,this.results_showing=!1,this.result_highlighted=null,this.result_single_selected=null,this.allow_single_deselect=this.options.allow_single_deselect!=null&&this.form_field.options[0]!=null&&this.form_field.options[0].text===""?this.options.allow_single_deselect:!1,this.disable_search_threshold=this.options.disable_search_threshold||0,this.disable_search=this.options.disable_search||!1,this.search_contains=this.options.search_contains||!1,this.choices=0,this.single_backstroke_delete=this.options.single_backstroke_delete||!1,this.max_selected_options=this.options.max_selected_options||Infinity},AbstractChosen.prototype.set_default_text=function(){return this.form_field.getAttribute("data-placeholder")?this.default_text=this.form_field.getAttribute("data-placeholder"):this.is_multiple?this.default_text=this.options.placeholder_text_multiple||this.options.placeholder_text||"Select Some Options":this.default_text=this.options.placeholder_text_single||this.options.placeholder_text||"Select an Option",this.results_none_found=this.form_field.getAttribute("data-no_results_text")||this.options.no_results_text||"No results match"},AbstractChosen.prototype.mouse_enter=function(){return this.mouse_on_container=!0},AbstractChosen.prototype.mouse_leave=function(){return this.mouse_on_container=!1},AbstractChosen.prototype.input_focus=function(evt){var _this=this;if(!this.active_field)return setTimeout(function(){return _this.container_mousedown()},50)},AbstractChosen.prototype.input_blur=function(evt){var _this=this;if(!this.mouse_on_container)return this.active_field=!1,setTimeout(function(){return _this.blur_test()},100)},AbstractChosen.prototype.result_add_option=function(option){var classes,style;return option.disabled?"":(option.dom_id=this.container_id+"_o_"+option.array_index,classes=option.selected&&this.is_multiple?[]:["active-result"],option.selected&&classes.push("result-selected"),option.group_array_index!=null&&classes.push("group-option"),option.classes!==""&&classes.push(option.classes),style=option.style.cssText!==""?' style="'+option.style+'"':"",'
  • "+option.html+"
  • ")},AbstractChosen.prototype.results_update_field=function(){return this.is_multiple||this.results_reset_cleanup(),this.result_clear_highlight(),this.result_single_selected=null,this.results_build()},AbstractChosen.prototype.results_toggle=function(){return this.results_showing?this.results_hide():this.results_show()},AbstractChosen.prototype.results_search=function(evt){return this.results_showing?this.winnow_results():this.results_show()},AbstractChosen.prototype.keyup_checker=function(evt){var stroke,_ref;stroke=(_ref=evt.which)!=null?_ref:evt.keyCode,this.search_field_scale();switch(stroke){case 8:if(this.is_multiple&&this.backstroke_length<1&&this.choices>0)return this.keydown_backstroke();if(!this.pending_backstroke)return this.result_clear_highlight(),this.results_search();break;case 13:evt.preventDefault();if(this.results_showing)return this.result_select(evt);break;case 27:return this.results_showing&&this.results_hide(),!0;case 9:case 38:case 40:case 16:case 91:case 17:break;default:return this.results_search()}},AbstractChosen.prototype.generate_field_id=function(){var new_id;return new_id=this.generate_random_id(),this.form_field.id=new_id,new_id},AbstractChosen.prototype.generate_random_char=function(){var chars,newchar,rand;return chars="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ",rand=Math.floor(Math.random()*chars.length),newchar=chars.substring(rand,rand+1)},AbstractChosen}(),root.AbstractChosen=AbstractChosen}.call(this),function(){var $,Chosen,get_side_border_padding,root,__hasProp=Object.prototype.hasOwnProperty,__extends=function(child,parent){function ctor(){this.constructor=child}for(var key in parent)__hasProp.call(parent,key)&&(child[key]=parent[key]);return ctor.prototype=parent.prototype,child.prototype=new ctor,child.__super__=parent.prototype,child};root=this,$=jQuery,$.fn.extend({chosen:function(options){return!$.browser.msie||$.browser.version!=="6.0"&&$.browser.version!=="7.0"?this.each(function(input_field){var $this;$this=$(this);if(!$this.hasClass("chzn-done"))return $this.data("chosen",new Chosen(this,options))}):this}}),Chosen=function(_super){function Chosen(){Chosen.__super__.constructor.apply(this,arguments)}return __extends(Chosen,_super),Chosen.prototype.setup=function(){return this.form_field_jq=$(this.form_field),this.current_value=this.form_field_jq.val(),this.is_rtl=this.form_field_jq.hasClass("chzn-rtl")},Chosen.prototype.finish_setup=function(){return this.form_field_jq.addClass("chzn-done")},Chosen.prototype.set_up_html=function(){var container_div,dd_top,dd_width,sf_width;return this.container_id=this.form_field.id.length?this.form_field.id.replace(/[^\w]/g,"_"):this.generate_field_id(),this.container_id+="_chzn",this.f_width=this.form_field_jq.outerWidth(),container_div=$("
    ",{id:this.container_id,"class":"chzn-container"+(this.is_rtl?" chzn-rtl":""),style:"width: "+this.f_width+"px;"}),this.is_multiple?container_div.html('
      '):container_div.html(''+this.default_text+'
        '),this.form_field_jq.hide().after(container_div),this.container=$("#"+this.container_id),this.container.addClass("chzn-container-"+(this.is_multiple?"multi":"single")),this.dropdown=this.container.find("div.chzn-drop").first(),dd_top=this.container.height(),dd_width=this.f_width-get_side_border_padding(this.dropdown),this.dropdown.css({width:dd_width+"px",top:dd_top+"px"}),this.search_field=this.container.find("input").first(),this.search_results=this.container.find("ul.chzn-results").first(),this.search_field_scale(),this.search_no_results=this.container.find("li.no-results").first(),this.is_multiple?(this.search_choices=this.container.find("ul.chzn-choices").first(),this.search_container=this.container.find("li.search-field").first()):(this.search_container=this.container.find("div.chzn-search").first(),this.selected_item=this.container.find(".chzn-single").first(),sf_width=dd_width-get_side_border_padding(this.search_container)-get_side_border_padding(this.search_field),this.search_field.css({width:sf_width+"px"})),this.results_build(),this.set_tab_index(),this.form_field_jq.trigger("liszt:ready",{chosen:this})},Chosen.prototype.register_observers=function(){var _this=this;return this.container.mousedown(function(evt){return _this.container_mousedown(evt)}),this.container.mouseup(function(evt){return _this.container_mouseup(evt)}),this.container.mouseenter(function(evt){return _this.mouse_enter(evt)}),this.container.mouseleave(function(evt){return _this.mouse_leave(evt)}),this.search_results.mouseup(function(evt){return _this.search_results_mouseup(evt)}),this.search_results.mouseover(function(evt){return _this.search_results_mouseover(evt)}),this.search_results.mouseout(function(evt){return _this.search_results_mouseout(evt)}),this.form_field_jq.bind("liszt:updated",function(evt){return _this.results_update_field(evt)}),this.form_field_jq.bind("liszt:activate",function(evt){return _this.activate_field(evt)}),this.form_field_jq.bind("liszt:open",function(evt){return _this.container_mousedown(evt)}),this.search_field.blur(function(evt){return _this.input_blur(evt)}),this.search_field.keyup(function(evt){return _this.keyup_checker(evt)}),this.search_field.keydown(function(evt){return _this.keydown_checker(evt)}),this.is_multiple?(this.search_choices.click(function(evt){return _this.choices_click(evt)}),this.search_field.focus(function(evt){return _this.input_focus(evt)})):this.container.click(function(evt){return evt.preventDefault()})},Chosen.prototype.search_field_disabled=function(){this.is_disabled=this.form_field_jq[0].disabled;if(this.is_disabled)return this.container.addClass("chzn-disabled"),this.search_field[0].disabled=!0,this.is_multiple||this.selected_item.unbind("focus",this.activate_action),this.close_field();this.container.removeClass("chzn-disabled"),this.search_field[0].disabled=!1;if(!this.is_multiple)return this.selected_item.bind("focus",this.activate_action)},Chosen.prototype.container_mousedown=function(evt){var target_closelink;if(!this.is_disabled)return target_closelink=evt!=null?$(evt.target).hasClass("search-choice-close"):!1,evt&&evt.type==="mousedown"&&!this.results_showing&&evt.stopPropagation(),!this.pending_destroy_click&&!target_closelink?(this.active_field?!this.is_multiple&&evt&&($(evt.target)[0]===this.selected_item[0]||$(evt.target).parents("a.chzn-single").length)&&(evt.preventDefault(),this.results_toggle()):(this.is_multiple&&this.search_field.val(""),$(document).click(this.click_test_action),this.results_show()),this.activate_field()):this.pending_destroy_click=!1},Chosen.prototype.container_mouseup=function(evt){if(evt.target.nodeName==="ABBR"&&!this.is_disabled)return this.results_reset(evt)},Chosen.prototype.blur_test=function(evt){if(!this.active_field&&this.container.hasClass("chzn-container-active"))return this.close_field()},Chosen.prototype.close_field=function(){return $(document).unbind("click",this.click_test_action),this.is_multiple||(this.selected_item.attr("tabindex",this.search_field.attr("tabindex")),this.search_field.attr("tabindex",-1)),this.active_field=!1,this.results_hide(),this.container.removeClass("chzn-container-active"),this.winnow_results_clear(),this.clear_backstroke(),this.show_search_field_default(),this.search_field_scale()},Chosen.prototype.activate_field=function(){return!this.is_multiple&&!this.active_field&&(this.search_field.attr("tabindex",this.selected_item.attr("tabindex")),this.selected_item.attr("tabindex",-1)),this.container.addClass("chzn-container-active"),this.active_field=!0,this.search_field.val(this.search_field.val()),this.search_field.focus()},Chosen.prototype.test_active_click=function(evt){return $(evt.target).parents("#"+this.container_id).length?this.active_field=!0:this.close_field()},Chosen.prototype.results_build=function(){var content,data,_i,_len,_ref;this.parsing=!0,this.results_data=root.SelectParser.select_to_array(this.form_field),this.is_multiple&&this.choices>0?(this.search_choices.find("li.search-choice").remove(),this.choices=0):this.is_multiple||(this.selected_item.addClass("chzn-default").find("span").text(this.default_text),this.disable_search||this.form_field.options.length<=this.disable_search_threshold?this.container.addClass("chzn-container-single-nosearch"):this.container.removeClass("chzn-container-single-nosearch")),content="",_ref=this.results_data;for(_i=0,_len=_ref.length;_i<_len;_i++)data=_ref[_i],data.group?content+=this.result_add_group(data):data.empty||(content+=this.result_add_option(data),data.selected&&this.is_multiple?this.choice_build(data):data.selected&&!this.is_multiple&&(this.selected_item.removeClass("chzn-default").find("span").text(data.text),this.allow_single_deselect&&this.single_deselect_control_build()));return this.search_field_disabled(),this.show_search_field_default(),this.search_field_scale(),this.search_results.html(content),this.parsing=!1},Chosen.prototype.result_add_group=function(group){return group.disabled?"":(group.dom_id=this.container_id+"_g_"+group.array_index,'
      • '+$("
        ").text(group.label).html()+"
      • ")},Chosen.prototype.result_do_highlight=function(el){var high_bottom,high_top,maxHeight,visible_bottom,visible_top;if(el.length){this.result_clear_highlight(),this.result_highlight=el,this.result_highlight.addClass("highlighted"),maxHeight=parseInt(this.search_results.css("maxHeight"),10),visible_top=this.search_results.scrollTop(),visible_bottom=maxHeight+visible_top,high_top=this.result_highlight.position().top+this.search_results.scrollTop(),high_bottom=high_top+this.result_highlight.outerHeight();if(high_bottom>=visible_bottom)return this.search_results.scrollTop(high_bottom-maxHeight>0?high_bottom-maxHeight:0);if(high_top'+item.html+''),link=$("#"+choice_id).find("a").first(),link.click(function(evt){return _this.choice_destroy_link_click(evt)}))},Chosen.prototype.choice_destroy_link_click=function(evt){return evt.preventDefault(),this.is_disabled?evt.stopPropagation:(this.pending_destroy_click=!0,this.choice_destroy($(evt.target)))},Chosen.prototype.choice_destroy=function(link){return this.choices-=1,this.show_search_field_default(),this.is_multiple&&this.choices>0&&this.search_field.val().length<1&&this.results_hide(),this.result_deselect(link.attr("rel")),link.parents("li").first().remove()},Chosen.prototype.results_reset=function(){this.form_field.options[0].selected=!0,this.selected_item.find("span").text(this.default_text),this.is_multiple||this.selected_item.addClass("chzn-default"),this.show_search_field_default(),this.results_reset_cleanup(),this.form_field_jq.trigger("change");if(this.active_field)return this.results_hide()},Chosen.prototype.results_reset_cleanup=function(){return this.selected_item.find("abbr").remove()},Chosen.prototype.result_select=function(evt){var high,high_id,item,position;if(this.result_highlight)return high=this.result_highlight,high_id=high.attr("id"),this.result_clear_highlight(),this.is_multiple?this.result_deactivate(high):(this.search_results.find(".result-selected").removeClass("result-selected"),this.result_single_selected=high,this.selected_item.removeClass("chzn-default")),high.addClass("result-selected"),position=high_id.substr(high_id.lastIndexOf("_")+1),item=this.results_data[position],item.selected=!0,this.form_field.options[item.options_index].selected=!0,this.is_multiple?this.choice_build(item):(this.selected_item.find("span").first().text(item.text),this.allow_single_deselect&&this.single_deselect_control_build()),(!evt.metaKey||!this.is_multiple)&&this.results_hide(),this.search_field.val(""),(this.is_multiple||this.form_field_jq.val()!==this.current_value)&&this.form_field_jq.trigger("change",{selected:this.form_field.options[item.options_index].value}),this.current_value=this.form_field_jq.val(),this.search_field_scale()},Chosen.prototype.result_activate=function(el){return el.addClass("active-result")},Chosen.prototype.result_deactivate=function(el){return el.removeClass("active-result")},Chosen.prototype.result_deselect=function(pos){var result,result_data;return result_data=this.results_data[pos],result_data.selected=!1,this.form_field.options[result_data.options_index].selected=!1,result=$("#"+this.container_id+"_o_"+pos),result.removeClass("result-selected").addClass("active-result").show(),this.result_clear_highlight(),this.winnow_results(),this.form_field_jq.trigger("change",{deselected:this.form_field.options[result_data.options_index].value}),this.search_field_scale()},Chosen.prototype.single_deselect_control_build=function(){if(this.allow_single_deselect&&this.selected_item.find("abbr").length<1)return this.selected_item.find("span").first().after('')},Chosen.prototype.winnow_results=function(){var found,option,part,parts,regex,regexAnchor,result,result_id,results,searchText,startpos,text,zregex,_i,_j,_len,_len2,_ref;this.no_results_clear(),results=0,searchText=this.search_field.val()===this.default_text?"":$("
        ").text($.trim(this.search_field.val())).html(),regexAnchor=this.search_contains?"":"^",regex=new RegExp(regexAnchor+searchText.replace(/[-[\]{}()*+?.,\\^$|#\s]/g,"\\$&"),"i"),zregex=new RegExp(searchText.replace(/[-[\]{}()*+?.,\\^$|#\s]/g,"\\$&"),"i"),_ref=this.results_data;for(_i=0,_len=_ref.length;_i<_len;_i++){option=_ref[_i];if(!option.disabled&&!option.empty)if(option.group)$("#"+option.dom_id).css("display","none");else if(!this.is_multiple||!option.selected){found=!1,result_id=option.dom_id,result=$("#"+result_id);if(regex.test(option.html))found=!0,results+=1;else if(option.html.indexOf(" ")>=0||option.html.indexOf("[")===0){parts=option.html.replace(/\[|\]/g,"").split(" ");if(parts.length)for(_j=0,_len2=parts.length;_j<_len2;_j++)part=parts[_j],regex.test(part)&&(found=!0,results+=1)}found?(searchText.length?(startpos=option.html.search(zregex),text=option.html.substr(0,startpos+searchText.length)+""+option.html.substr(startpos+searchText.length),text=text.substr(0,startpos)+""+text.substr(startpos)):text=option.html,result.html(text),this.result_activate(result),option.group_array_index!=null&&$("#"+this.results_data[option.group_array_index].dom_id).css("display","list-item")):(this.result_highlight&&result_id===this.result_highlight.attr("id")&&this.result_clear_highlight(),this.result_deactivate(result))}}return results<1&&searchText.length?this.no_results(searchText):this.winnow_results_set_highlight()},Chosen.prototype.winnow_results_clear=function(){var li,lis,_i,_len,_results;this.search_field.val(""),lis=this.search_results.find("li"),_results=[];for(_i=0,_len=lis.length;_i<_len;_i++)li=lis[_i],li=$(li),li.hasClass("group-result")?_results.push(li.css("display","auto")):!this.is_multiple||!li.hasClass("result-selected")?_results.push(this.result_activate(li)):_results.push(void 0);return _results},Chosen.prototype.winnow_results_set_highlight=function(){var do_high,selected_results;if(!this.result_highlight){selected_results=this.is_multiple?[]:this.search_results.find(".result-selected.active-result"),do_high=selected_results.length?selected_results.first():this.search_results.find(".active-result").first();if(do_high!=null)return this.result_do_highlight(do_high)}},Chosen.prototype.no_results=function(terms){var no_results_html;return no_results_html=$('
      • '+this.results_none_found+' ""
      • '),no_results_html.find("span").first().html(terms),this.search_results.append(no_results_html)},Chosen.prototype.no_results_clear=function(){return this.search_results.find(".no-results").remove()},Chosen.prototype.keydown_arrow=function(){var first_active,next_sib;this.result_highlight?this.results_showing&&(next_sib=this.result_highlight.nextAll("li.active-result").first(),next_sib&&this.result_do_highlight(next_sib)):(first_active=this.search_results.find("li.active-result").first(),first_active&&this.result_do_highlight($(first_active)));if(!this.results_showing)return this.results_show()},Chosen.prototype.keyup_arrow=function(){var prev_sibs;if(!this.results_showing&&!this.is_multiple)return this.results_show();if(this.result_highlight)return prev_sibs=this.result_highlight.prevAll("li.active-result"),prev_sibs.length?this.result_do_highlight(prev_sibs.first()):(this.choices>0&&this.results_hide(),this.result_clear_highlight())},Chosen.prototype.keydown_backstroke=function(){return this.pending_backstroke?(this.choice_destroy(this.pending_backstroke.find("a").first()),this.clear_backstroke()):(this.pending_backstroke=this.search_container.siblings("li.search-choice").last(),this.single_backstroke_delete?this.keydown_backstroke():this.pending_backstroke.addClass("search-choice-focus"))},Chosen.prototype.clear_backstroke=function(){return this.pending_backstroke&&this.pending_backstroke.removeClass("search-choice-focus"),this.pending_backstroke=null},Chosen.prototype.keydown_checker=function(evt){var stroke,_ref;stroke=(_ref=evt.which)!=null?_ref:evt.keyCode,this.search_field_scale(),stroke!==8&&this.pending_backstroke&&this.clear_backstroke();switch(stroke){case 8:this.backstroke_length=this.search_field.val().length;break;case 9:this.results_showing&&!this.is_multiple&&this.result_select(evt),this.mouse_on_container=!1;break;case 13:evt.preventDefault();break;case 38:evt.preventDefault(),this.keyup_arrow();break;case 40:this.keydown_arrow()}},Chosen.prototype.search_field_scale=function(){var dd_top,div,h,style,style_block,styles,w,_i,_len;if(this.is_multiple){h=0,w=0,style_block="position:absolute; left: -1000px; top: -1000px; display:none;",styles=["font-size","font-style","font-weight","font-family","line-height","text-transform","letter-spacing"];for(_i=0,_len=styles.length;_i<_len;_i++)style=styles[_i],style_block+=style+":"+this.search_field.css(style)+";";return div=$("
        ",{style:style_block}),div.text(this.search_field.val()),$("body").append(div),w=div.width()+25,div.remove(),w>this.f_width-10&&(w=this.f_width-10),this.search_field.css({width:w+"px"}),dd_top=this.container.height(),this.dropdown.css({top:dd_top+"px"})}},Chosen.prototype.generate_random_id=function(){var string;string="sel"+this.generate_random_char()+this.generate_random_char()+this.generate_random_char();while($("#"+string).length>0)string+=this.generate_random_char();return string},Chosen}(AbstractChosen),get_side_border_padding=function(elmt){var side_border_padding;return side_border_padding=elmt.outerWidth()-elmt.width()},root.get_side_border_padding=get_side_border_padding}.call(this); \ No newline at end of file From 7cff2d8b1dcfb0a8b5dc06bbf1c5298f9f8c7153 Mon Sep 17 00:00:00 2001 From: Sheila Miguez Date: Wed, 15 Jul 2015 16:43:44 -0500 Subject: [PATCH 607/751] add to schedule admin --- symposion/schedule/admin.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/symposion/schedule/admin.py b/symposion/schedule/admin.py index 50f252c3..68508503 100644 --- a/symposion/schedule/admin.py +++ b/symposion/schedule/admin.py @@ -5,11 +5,18 @@ from symposion.schedule.models import Schedule, Day, Room, SlotKind, Slot, SlotR admin.site.register(Schedule) admin.site.register(Day) -admin.site.register(Room) -admin.site.register(SlotKind) +admin.site.register( + Room, + list_display=("name", "order", "schedule"), + list_filter=("schedule",) +) +admin.site.register( + SlotKind, + list_display=("label", "schedule"), +) admin.site.register( Slot, - list_display=("day", "start", "end", "kind") + list_display=("day", "start", "end", "kind", "content") ) admin.site.register( SlotRoom, @@ -17,4 +24,7 @@ admin.site.register( ) admin.site.register(Session) admin.site.register(SessionRole) -admin.site.register(Presentation) +admin.site.register( + Presentation, + list_filter=("section", "cancelled", "slot") +) From 6f3115aee8a97b2e19c194040e4967d1912661b5 Mon Sep 17 00:00:00 2001 From: Hiroshi Miura Date: Sun, 19 Jul 2015 14:33:03 +0900 Subject: [PATCH 608/751] reorder signup fields current implementation is not work on Django 1.7+ anymore. This modification activate feature in Django 1.7+ and drop support for Django 1.6 and before. Signed-off-by: Hiroshi Miura --- symposion/forms.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/symposion/forms.py b/symposion/forms.py index 7799e06a..dcf96e3d 100644 --- a/symposion/forms.py +++ b/symposion/forms.py @@ -1,3 +1,5 @@ +from collections import OrderedDict + from django import forms import account.forms @@ -12,7 +14,7 @@ class SignupForm(account.forms.SignupForm): def __init__(self, *args, **kwargs): super(SignupForm, self).__init__(*args, **kwargs) del self.fields["username"] - self.fields.keyOrder = [ + key_order = [ "email", "email_confirm", "first_name", @@ -20,6 +22,7 @@ class SignupForm(account.forms.SignupForm): "password", "password_confirm" ] + self.fields = reorder_fields(self.fields, key_order) def clean_email_confirm(self): email = self.cleaned_data.get("email") @@ -29,3 +32,18 @@ class SignupForm(account.forms.SignupForm): raise forms.ValidationError( "Email address must match previously typed email address") return email_confirm + + +def reorder_fields(fields, order): + """Reorder form fields by order, removing items not in order. + + >>> reorder_fields( + ... OrderedDict([('a', 1), ('b', 2), ('c', 3)]), + ... ['b', 'c', 'a']) + OrderedDict([('b', 2), ('c', 3), ('a', 1)]) + """ + for key, v in fields.items(): + if key not in order: + del fields[key] + + return OrderedDict(sorted(fields.items(), key=lambda k: order.index(k[0]))) From fa66316f8aca59ecdf70ea10adcdfe20fcc11690 Mon Sep 17 00:00:00 2001 From: Hiroshi Miura Date: Mon, 20 Jul 2015 09:39:11 +0900 Subject: [PATCH 609/751] form key ordering: detect django version - Django 1.6 and before use SortedDict instead of OrderedDict It detect version and return proper dict for key ordering. - field deletion is done by reorder funcion. Signed-off-by: Hiroshi Miura --- symposion/forms.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/symposion/forms.py b/symposion/forms.py index dcf96e3d..e207a0fd 100644 --- a/symposion/forms.py +++ b/symposion/forms.py @@ -1,6 +1,7 @@ from collections import OrderedDict from django import forms +from django import VERSION as django_VERSION import account.forms @@ -13,7 +14,6 @@ class SignupForm(account.forms.SignupForm): def __init__(self, *args, **kwargs): super(SignupForm, self).__init__(*args, **kwargs) - del self.fields["username"] key_order = [ "email", "email_confirm", @@ -46,4 +46,10 @@ def reorder_fields(fields, order): if key not in order: del fields[key] - return OrderedDict(sorted(fields.items(), key=lambda k: order.index(k[0]))) + if django_VERSION < (1, 7, 0): + # fields is SortedDict + fields.keyOrder.sort(key=lambda k: order.index(k[0])) + return fields + else: + # fields is OrderedDict + return OrderedDict(sorted(fields.items(), key=lambda k: order.index(k[0]))) From c22bcf9cf46469d63e3c3d5d32cb59074d069c83 Mon Sep 17 00:00:00 2001 From: Hiroshi Miura Date: Mon, 20 Jul 2015 12:03:54 +0900 Subject: [PATCH 610/751] fix OrderedDict import error in Py2.6 Signed-off-by: Hiroshi Miura --- symposion/forms.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/symposion/forms.py b/symposion/forms.py index e207a0fd..09fc5d4f 100644 --- a/symposion/forms.py +++ b/symposion/forms.py @@ -1,7 +1,9 @@ -from collections import OrderedDict +try: + from collections import OrderedDict +except ImportError: + OrderedDict = None from django import forms -from django import VERSION as django_VERSION import account.forms @@ -46,7 +48,7 @@ def reorder_fields(fields, order): if key not in order: del fields[key] - if django_VERSION < (1, 7, 0): + if not OrderedDict or hasattr(fields, "keyOrder"): # fields is SortedDict fields.keyOrder.sort(key=lambda k: order.index(k[0])) return fields From b3e7b1be8c2ce9445c0dca924c27143de839cd01 Mon Sep 17 00:00:00 2001 From: Hiroshi Miura Date: Sun, 26 Jul 2015 14:21:02 +0900 Subject: [PATCH 611/751] add more addtitional speakers constraints Signed-off-by: Hiroshi Miura --- symposion/proposals/models.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/symposion/proposals/models.py b/symposion/proposals/models.py index 5cb4baa6..7dea87cd 100644 --- a/symposion/proposals/models.py +++ b/symposion/proposals/models.py @@ -9,6 +9,7 @@ from django.utils.timezone import now from django.contrib.auth.models import User from django.core.exceptions import ObjectDoesNotExist +from django.core.exceptions import ValidationError import reversion @@ -106,8 +107,15 @@ class ProposalBase(models.Model): editable=False, ) speaker = models.ForeignKey(Speaker, related_name="proposals") + + def additional_speaker_validator(self, a_speaker): + if a_speaker.speaker.email == self.speaker.email: + raise ValidationError(_("%s is same as primary speaker.") % a_speaker.speaker.email) + if a_speaker in [self.additional_speakers]: + raise ValidationError(_("%s has already been in speakers.") % a_speaker.speaker.email) + additional_speakers = models.ManyToManyField(Speaker, through="AdditionalSpeaker", - blank=True) + blank=True, validators=[additional_speaker_validator]) cancelled = models.BooleanField(default=False) def can_edit(self): @@ -147,7 +155,6 @@ class ProposalBase(models.Model): "kind": self.kind.name, } - reversion.register(ProposalBase) @@ -170,6 +177,14 @@ class AdditionalSpeaker(models.Model): class Meta: unique_together = ("speaker", "proposalbase") + def __unicode__(self): + if self.status is self.SPEAKING_STATUS_PENDING: + return _(u"pending speaker (%s)") % self.speaker.email + elif self.status is self.SPEAKING_STATUS_DECLINED: + return _(u"declined speaker (%s)") % self.speaker.email + else: + return self.speaker.name + def uuid_filename(instance, filename): ext = filename.split(".")[-1] From 55814a57b3f078877769f1ea2f166f6c5e3fbac8 Mon Sep 17 00:00:00 2001 From: Hiroshi Miura Date: Sat, 1 Aug 2015 12:51:47 +0900 Subject: [PATCH 612/751] admin: inline edit section Signed-off-by: Hiroshi Miura --- symposion/conference/admin.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/symposion/conference/admin.py b/symposion/conference/admin.py index c8e57441..f7145303 100644 --- a/symposion/conference/admin.py +++ b/symposion/conference/admin.py @@ -3,7 +3,18 @@ from django.contrib import admin from symposion.conference.models import Conference, Section -admin.site.register(Conference, list_display=("title", "start_date", "end_date")) +class SectionInline(admin.TabularInline): + model = Section + prepopulated_fields = {"slug": ("name",)} + extra = 1 + + +class ConferenceAdmin(admin.ModelAdmin): + list_display = ("title", "start_date", "end_date") + inlines = [SectionInline, ] + + +admin.site.register(Conference, ConferenceAdmin) admin.site.register( Section, prepopulated_fields={"slug": ("name",)}, From 32e8c27df99f82c01acacd1922429be10fcaee5d Mon Sep 17 00:00:00 2001 From: Hiroshi Miura Date: Sat, 1 Aug 2015 12:37:58 +0900 Subject: [PATCH 613/751] admin: inline edit day and slotroom Signed-off-by: Hiroshi Miura --- symposion/schedule/admin.py | 51 +++++++++++++++++++++++++++---------- 1 file changed, 38 insertions(+), 13 deletions(-) diff --git a/symposion/schedule/admin.py b/symposion/schedule/admin.py index 50f252c3..4a8c376e 100644 --- a/symposion/schedule/admin.py +++ b/symposion/schedule/admin.py @@ -3,18 +3,43 @@ from django.contrib import admin from symposion.schedule.models import Schedule, Day, Room, SlotKind, Slot, SlotRoom, Presentation, Session, SessionRole -admin.site.register(Schedule) -admin.site.register(Day) -admin.site.register(Room) -admin.site.register(SlotKind) -admin.site.register( - Slot, - list_display=("day", "start", "end", "kind") -) -admin.site.register( - SlotRoom, - list_display=("slot", "room") -) +class DayInline(admin.StackedInline): + model = Day + extra = 2 + + +class SlotKindInline(admin.StackedInline): + model = SlotKind + + +class ScheduleAdmin(admin.ModelAdmin): + model = Schedule + inlines = [DayInline, SlotKindInline, ] + + +class SlotRoomInline(admin.TabularInline): + model = SlotRoom + extra = 1 + + +class SlotAdmin(admin.ModelAdmin): + list_filter = ("day", "kind") + list_display = ("day", "start", "end", "kind", "content") + inlines = [SlotRoomInline, ] + + +class RoomAdmin(admin.ModelAdmin): + inlines = [SlotRoomInline, ] + + +class PresentationAdmin(admin.ModelAdmin): + model = Presentation + list_filter = ("section", "cancelled", "slot") + + +admin.site.register(Schedule, ScheduleAdmin) +admin.site.register(Room, RoomAdmin) +admin.site.register(Slot, SlotAdmin) admin.site.register(Session) admin.site.register(SessionRole) -admin.site.register(Presentation) +admin.site.register(Presentation, PresentationAdmin) From acd5e9595a780b3f48031c04109d168d58a961d5 Mon Sep 17 00:00:00 2001 From: Hiroshi Miura Date: Sat, 1 Aug 2015 12:42:00 +0900 Subject: [PATCH 614/751] slot name includes rooms Signed-off-by: Hiroshi Miura --- symposion/schedule/models.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/symposion/schedule/models.py b/symposion/schedule/models.py index 5b2a9413..a0f88e81 100644 --- a/symposion/schedule/models.py +++ b/symposion/schedule/models.py @@ -125,7 +125,8 @@ class Slot(models.Model): return Room.objects.filter(pk__in=self.slotroom_set.values("room")) def __unicode__(self): - return u"%s %s (%s - %s)" % (self.day, self.kind, self.start, self.end) + roomlist = ' '.join(map(lambda r: r.__unicode__(), self.rooms)) + return u"%s %s (%s - %s) %s" % (self.day, self.kind, self.start, self.end, roomlist) class Meta: ordering = ["day", "start", "end"] From a95825ede8d4039cb406c7a7acd2d94abde1f29a Mon Sep 17 00:00:00 2001 From: Hiroshi Miura Date: Sat, 18 Jul 2015 16:09:17 +0900 Subject: [PATCH 615/751] python3 compatibility - Things are suggested in python3 porting guide. https://docs.djangoproject.com/en/1.8/topics/python3/ 1. adding ```from django.utils.encoding import python_2_unicode_compatible``` 2. ``` __str__``` instead of ```__unicode__``` https://docs.djangoproject.com/en/1.8/topics/python3/#str-and-unicode-methods 3. Adding ```from __future__ import unicode_literals``` at the top of your Python modules https://docs.djangoproject.com/en/1.8/topics/python3/#unicode-literals 4. Removing the `u` prefix before unicode strings; https://docs.djangoproject.com/en/1.8/topics/python3/#unicode-literals - also closed #66 Signed-off-by: Hiroshi Miura --- symposion/boxes/models.py | 5 +++- symposion/cms/models.py | 4 ++- symposion/conference/apps.py | 1 + symposion/conference/models.py | 10 ++++--- symposion/markdown_parser.py | 1 + symposion/proposals/actions.py | 1 + symposion/proposals/apps.py | 1 + symposion/proposals/forms.py | 1 + symposion/proposals/models.py | 15 ++++++++--- symposion/proposals/views.py | 1 + symposion/reviews/apps.py | 1 + symposion/reviews/forms.py | 1 + symposion/reviews/models.py | 5 ++-- symposion/reviews/utils.py | 3 +++ symposion/schedule/admin.py | 1 + symposion/schedule/apps.py | 1 + symposion/schedule/forms.py | 1 + symposion/schedule/helpers.py | 1 + symposion/schedule/models.py | 47 ++++++++++++++++++++------------- symposion/schedule/timetable.py | 1 + symposion/schedule/urls.py | 1 + symposion/schedule/views.py | 1 + symposion/speakers/admin.py | 1 + symposion/speakers/apps.py | 1 + symposion/speakers/forms.py | 1 + symposion/speakers/models.py | 7 +++-- symposion/speakers/urls.py | 1 + symposion/speakers/views.py | 1 + symposion/sponsorship/admin.py | 1 + symposion/sponsorship/forms.py | 1 + symposion/sponsorship/models.py | 21 ++++++++++----- symposion/sponsorship/views.py | 1 + symposion/teams/forms.py | 1 + symposion/teams/models.py | 5 +++- symposion/teams/views.py | 1 + symposion/views.py | 1 + 36 files changed, 110 insertions(+), 38 deletions(-) diff --git a/symposion/boxes/models.py b/symposion/boxes/models.py index 4eb2a019..d9e57bd7 100644 --- a/symposion/boxes/models.py +++ b/symposion/boxes/models.py @@ -1,11 +1,14 @@ +from __future__ import unicode_literals from django.db import models from django.contrib.auth.models import User +from django.utils.encoding import python_2_unicode_compatible import reversion from markitup.fields import MarkupField +@python_2_unicode_compatible class Box(models.Model): label = models.CharField(max_length=100, db_index=True) @@ -14,7 +17,7 @@ class Box(models.Model): created_by = models.ForeignKey(User, related_name="boxes") last_updated_by = models.ForeignKey(User, related_name="updated_boxes") - def __unicode__(self): + def __str__(self): return self.label class Meta: diff --git a/symposion/cms/models.py b/symposion/cms/models.py index 25c1caea..1a5980a3 100644 --- a/symposion/cms/models.py +++ b/symposion/cms/models.py @@ -6,6 +6,7 @@ from django.conf import settings from django.core.urlresolvers import reverse from django.core.exceptions import ValidationError from django.db import models +from django.utils.encoding import python_2_unicode_compatible from django.utils.translation import ugettext_lazy as _ from markitup.fields import MarkupField @@ -17,6 +18,7 @@ import reversion from .managers import PublishedPageManager +@python_2_unicode_compatible class Page(models.Model): STATUS_CHOICES = ( @@ -35,7 +37,7 @@ class Page(models.Model): published = PublishedPageManager() - def __unicode__(self): + def __str__(self): return self.title @models.permalink diff --git a/symposion/conference/apps.py b/symposion/conference/apps.py index d7759eeb..b9e64c13 100644 --- a/symposion/conference/apps.py +++ b/symposion/conference/apps.py @@ -1,3 +1,4 @@ +from __future__ import unicode_literals from django.apps import AppConfig diff --git a/symposion/conference/models.py b/symposion/conference/models.py index 582035b5..01b59503 100644 --- a/symposion/conference/models.py +++ b/symposion/conference/models.py @@ -1,5 +1,7 @@ +from __future__ import unicode_literals from django.db import models from django.utils.translation import ugettext_lazy as _ +from django.utils.encoding import python_2_unicode_compatible from timezones.fields import TimeZoneField @@ -7,6 +9,7 @@ from timezones.fields import TimeZoneField CONFERENCE_CACHE = {} +@python_2_unicode_compatible class Conference(models.Model): """ the full conference for a specific year, e.g. US PyCon 2012. @@ -21,7 +24,7 @@ class Conference(models.Model): # timezone the conference is in timezone = TimeZoneField(_("timezone"), blank=True) - def __unicode__(self): + def __str__(self): return self.title def save(self, *args, **kwargs): @@ -42,6 +45,7 @@ class Conference(models.Model): verbose_name_plural = _("conferences") +@python_2_unicode_compatible class Section(models.Model): """ a section of the conference such as "Tutorials", "Workshops", @@ -58,8 +62,8 @@ class Section(models.Model): start_date = models.DateField(_("start date"), null=True, blank=True) end_date = models.DateField(_("end date"), null=True, blank=True) - def __unicode__(self): - return u"%s %s" % (self.conference, self.name) + def __str__(self): + return "%s %s" % (self.conference, self.name) class Meta(object): verbose_name = _("section") diff --git a/symposion/markdown_parser.py b/symposion/markdown_parser.py index 178708f6..f512a854 100644 --- a/symposion/markdown_parser.py +++ b/symposion/markdown_parser.py @@ -1,3 +1,4 @@ +from __future__ import unicode_literals from html5lib import html5parser, sanitizer import markdown diff --git a/symposion/proposals/actions.py b/symposion/proposals/actions.py index d059da81..5c747219 100644 --- a/symposion/proposals/actions.py +++ b/symposion/proposals/actions.py @@ -1,3 +1,4 @@ +from __future__ import unicode_literals import csv from django.http import HttpResponse diff --git a/symposion/proposals/apps.py b/symposion/proposals/apps.py index 9a317525..e0adc66d 100644 --- a/symposion/proposals/apps.py +++ b/symposion/proposals/apps.py @@ -1,3 +1,4 @@ +from __future__ import unicode_literals from django.apps import AppConfig diff --git a/symposion/proposals/forms.py b/symposion/proposals/forms.py index 91c7e004..ff857a3c 100644 --- a/symposion/proposals/forms.py +++ b/symposion/proposals/forms.py @@ -1,3 +1,4 @@ +from __future__ import unicode_literals from django import forms from django.db.models import Q diff --git a/symposion/proposals/models.py b/symposion/proposals/models.py index 7dea87cd..ab68bcec 100644 --- a/symposion/proposals/models.py +++ b/symposion/proposals/models.py @@ -1,9 +1,11 @@ +from __future__ import unicode_literals import os import uuid from django.core.urlresolvers import reverse from django.db import models from django.db.models import Q +from django.utils.encoding import python_2_unicode_compatible from django.utils.translation import ugettext_lazy as _ from django.utils.timezone import now @@ -21,6 +23,7 @@ from symposion.conference.models import Section from symposion.speakers.models import Speaker +@python_2_unicode_compatible class ProposalSection(models.Model): """ configuration of proposal submissions for a specific Section. @@ -55,10 +58,11 @@ class ProposalSection(models.Model): return False return True - def __unicode__(self): + def __str__(self): return self.section.name +@python_2_unicode_compatible class ProposalKind(models.Model): """ e.g. talk vs panel vs tutorial vs poster @@ -72,10 +76,11 @@ class ProposalKind(models.Model): name = models.CharField(_("Name"), max_length=100) slug = models.SlugField() - def __unicode__(self): + def __str__(self): return self.name +@python_2_unicode_compatible class ProposalBase(models.Model): objects = InheritanceManager() @@ -155,9 +160,13 @@ class ProposalBase(models.Model): "kind": self.kind.name, } + def __str__(self): + return self.title + reversion.register(ProposalBase) +@python_2_unicode_compatible class AdditionalSpeaker(models.Model): SPEAKING_STATUS_PENDING = 1 @@ -177,7 +186,7 @@ class AdditionalSpeaker(models.Model): class Meta: unique_together = ("speaker", "proposalbase") - def __unicode__(self): + def __str__(self): if self.status is self.SPEAKING_STATUS_PENDING: return _(u"pending speaker (%s)") % self.speaker.email elif self.status is self.SPEAKING_STATUS_DECLINED: diff --git a/symposion/proposals/views.py b/symposion/proposals/views.py index 46c3c1c6..28ce6f67 100644 --- a/symposion/proposals/views.py +++ b/symposion/proposals/views.py @@ -1,3 +1,4 @@ +from __future__ import unicode_literals import hashlib import random import sys diff --git a/symposion/reviews/apps.py b/symposion/reviews/apps.py index 683b0c0f..38d419e7 100644 --- a/symposion/reviews/apps.py +++ b/symposion/reviews/apps.py @@ -1,3 +1,4 @@ +from __future__ import unicode_literals from django.apps import AppConfig diff --git a/symposion/reviews/forms.py b/symposion/reviews/forms.py index a705333c..9d49a257 100644 --- a/symposion/reviews/forms.py +++ b/symposion/reviews/forms.py @@ -1,3 +1,4 @@ +from __future__ import unicode_literals from django import forms from markitup.widgets import MarkItUpWidget diff --git a/symposion/reviews/models.py b/symposion/reviews/models.py index 1537ae98..35ebd925 100644 --- a/symposion/reviews/models.py +++ b/symposion/reviews/models.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +from __future__ import unicode_literals from datetime import datetime from decimal import Decimal @@ -27,8 +28,8 @@ class ProposalScoreExpression(object): class Votes(object): PLUS_ONE = "+1" PLUS_ZERO = "+0" - MINUS_ZERO = u"−0" - MINUS_ONE = u"−1" + MINUS_ZERO = "−0" + MINUS_ONE = "−1" CHOICES = [ (PLUS_ONE, u"+1 — Good proposal and I will argue for it to be accepted."), diff --git a/symposion/reviews/utils.py b/symposion/reviews/utils.py index 80e87e49..1d45e95a 100644 --- a/symposion/reviews/utils.py +++ b/symposion/reviews/utils.py @@ -1,3 +1,6 @@ +from __future__ import unicode_literals + + def has_permission(user, proposal, speaker=False, reviewer=False): """ Returns whether or not ther user has permission to review this proposal, diff --git a/symposion/schedule/admin.py b/symposion/schedule/admin.py index 4a8c376e..2a2dda7d 100644 --- a/symposion/schedule/admin.py +++ b/symposion/schedule/admin.py @@ -1,3 +1,4 @@ +from __future__ import unicode_literals from django.contrib import admin from symposion.schedule.models import Schedule, Day, Room, SlotKind, Slot, SlotRoom, Presentation, Session, SessionRole diff --git a/symposion/schedule/apps.py b/symposion/schedule/apps.py index 03773bdc..d1c0ecb0 100644 --- a/symposion/schedule/apps.py +++ b/symposion/schedule/apps.py @@ -1,3 +1,4 @@ +from __future__ import unicode_literals from django.apps import AppConfig diff --git a/symposion/schedule/forms.py b/symposion/schedule/forms.py index 6a01cbfe..426160a3 100644 --- a/symposion/schedule/forms.py +++ b/symposion/schedule/forms.py @@ -1,3 +1,4 @@ +from __future__ import unicode_literals import csv import time diff --git a/symposion/schedule/helpers.py b/symposion/schedule/helpers.py index c5fa8953..68e72e39 100644 --- a/symposion/schedule/helpers.py +++ b/symposion/schedule/helpers.py @@ -2,6 +2,7 @@ This file contains functions that are useful to humans at the shell for manipulating the database in more natural ways. """ +from __future__ import unicode_literals from django.db import transaction from .models import Schedule, Day, Room, Slot, SlotKind, SlotRoom diff --git a/symposion/schedule/models.py b/symposion/schedule/models.py index a0f88e81..8792b47f 100644 --- a/symposion/schedule/models.py +++ b/symposion/schedule/models.py @@ -1,8 +1,10 @@ +from __future__ import unicode_literals import datetime from django.core.exceptions import ObjectDoesNotExist from django.contrib.auth.models import User from django.db import models +from django.utils.encoding import python_2_unicode_compatible from markitup.fields import MarkupField @@ -11,42 +13,46 @@ from symposion.conference.models import Section from symposion.speakers.models import Speaker +@python_2_unicode_compatible class Schedule(models.Model): section = models.OneToOneField(Section) published = models.BooleanField(default=True) hidden = models.BooleanField("Hide schedule from overall conference view", default=False) - def __unicode__(self): - return u"%s Schedule" % self.section + def __str__(self): + return "%s Schedule" % self.section class Meta: ordering = ["section"] +@python_2_unicode_compatible class Day(models.Model): schedule = models.ForeignKey(Schedule) date = models.DateField() - def __unicode__(self): - return u"%s" % self.date + def __str__(self): + return "%s" % self.date class Meta: unique_together = [("schedule", "date")] ordering = ["date"] +@python_2_unicode_compatible class Room(models.Model): schedule = models.ForeignKey(Schedule) name = models.CharField(max_length=65) order = models.PositiveIntegerField() - def __unicode__(self): + def __str__(self): return self.name +@python_2_unicode_compatible class SlotKind(models.Model): """ A slot kind represents what kind a slot is. For example, a slot can be a @@ -56,10 +62,11 @@ class SlotKind(models.Model): schedule = models.ForeignKey(Schedule) label = models.CharField(max_length=50) - def __unicode__(self): + def __str__(self): return self.label +@python_2_unicode_compatible class Slot(models.Model): day = models.ForeignKey(Day) @@ -124,14 +131,15 @@ class Slot(models.Model): def rooms(self): return Room.objects.filter(pk__in=self.slotroom_set.values("room")) - def __unicode__(self): + def __str__(self): roomlist = ' '.join(map(lambda r: r.__unicode__(), self.rooms)) - return u"%s %s (%s - %s) %s" % (self.day, self.kind, self.start, self.end, roomlist) + return "%s %s (%s - %s) %s" % (self.day, self.kind, self.start, self.end, roomlist) class Meta: ordering = ["day", "start", "end"] +@python_2_unicode_compatible class SlotRoom(models.Model): """ Links a slot with a room. @@ -140,14 +148,15 @@ class SlotRoom(models.Model): slot = models.ForeignKey(Slot) room = models.ForeignKey(Room) - def __unicode__(self): - return u"%s %s" % (self.room, self.slot) + def __str__(self): + return "%s %s" % (self.room, self.slot) class Meta: unique_together = [("slot", "room")] ordering = ["slot", "room__order"] +@python_2_unicode_compatible class Presentation(models.Model): slot = models.OneToOneField(Slot, null=True, blank=True, related_name="content_ptr") @@ -177,13 +186,14 @@ class Presentation(models.Model): if speaker.user: yield speaker - def __unicode__(self): - return u"#%s %s (%s)" % (self.number, self.title, self.speaker) + def __str__(self): + return "#%s %s (%s)" % (self.number, self.title, self.speaker) class Meta: ordering = ["slot"] +@python_2_unicode_compatible class Session(models.Model): day = models.ForeignKey(Day, related_name="sessions") @@ -206,18 +216,19 @@ class Session(models.Model): else: return None - def __unicode__(self): + def __str__(self): start = self.start() end = self.end() if start and end: - return u"%s: %s - %s" % ( + return "%s: %s - %s" % ( self.day.date.strftime("%a"), start.strftime("%X"), end.strftime("%X") ) - return u"" + return "" +@python_2_unicode_compatible class SessionRole(models.Model): SESSION_ROLE_CHAIR = 1 @@ -238,6 +249,6 @@ class SessionRole(models.Model): class Meta: unique_together = [("session", "user", "role")] - def __unicode__(self): - return u"%s %s: %s" % (self.user, self.session, - self.SESSION_ROLE_TYPES[self.role - 1][1]) + def __str__(self): + return "%s %s: %s" % (self.user, self.session, + self.SESSION_ROLE_TYPES[self.role - 1][1]) diff --git a/symposion/schedule/timetable.py b/symposion/schedule/timetable.py index 1756db6c..cc77a7d8 100644 --- a/symposion/schedule/timetable.py +++ b/symposion/schedule/timetable.py @@ -1,3 +1,4 @@ +from __future__ import unicode_literals import itertools from django.db.models import Count, Min diff --git a/symposion/schedule/urls.py b/symposion/schedule/urls.py index b0e07189..8ac3bdc2 100644 --- a/symposion/schedule/urls.py +++ b/symposion/schedule/urls.py @@ -1,3 +1,4 @@ +from __future__ import unicode_literals from django.conf.urls import url, patterns diff --git a/symposion/schedule/views.py b/symposion/schedule/views.py index 1307e5e5..898fcc36 100644 --- a/symposion/schedule/views.py +++ b/symposion/schedule/views.py @@ -1,3 +1,4 @@ +from __future__ import unicode_literals import json from django.core.urlresolvers import reverse diff --git a/symposion/speakers/admin.py b/symposion/speakers/admin.py index 479c9626..e79370a3 100644 --- a/symposion/speakers/admin.py +++ b/symposion/speakers/admin.py @@ -1,3 +1,4 @@ +from __future__ import unicode_literals from django.contrib import admin from symposion.speakers.models import Speaker diff --git a/symposion/speakers/apps.py b/symposion/speakers/apps.py index b4abf5d5..afcad95a 100644 --- a/symposion/speakers/apps.py +++ b/symposion/speakers/apps.py @@ -1,3 +1,4 @@ +from __future__ import unicode_literals from django.apps import AppConfig diff --git a/symposion/speakers/forms.py b/symposion/speakers/forms.py index 14ecb9a9..fd42945c 100644 --- a/symposion/speakers/forms.py +++ b/symposion/speakers/forms.py @@ -1,3 +1,4 @@ +from __future__ import unicode_literals from django import forms from markitup.widgets import MarkItUpWidget diff --git a/symposion/speakers/models.py b/symposion/speakers/models.py index 0887ef45..0e272db8 100644 --- a/symposion/speakers/models.py +++ b/symposion/speakers/models.py @@ -1,4 +1,6 @@ +from __future__ import unicode_literals import datetime +from django.utils.encoding import python_2_unicode_compatible from django.db import models from django.core.urlresolvers import reverse @@ -8,6 +10,7 @@ from django.contrib.auth.models import User from markitup.fields import MarkupField +@python_2_unicode_compatible class Speaker(models.Model): SESSION_COUNT_CHOICES = [ @@ -34,11 +37,11 @@ class Speaker(models.Model): class Meta: ordering = ['name'] - def __unicode__(self): + def __str__(self): if self.user: return self.name else: - return u"?" + return "?" def get_absolute_url(self): return reverse("speaker_edit") diff --git a/symposion/speakers/urls.py b/symposion/speakers/urls.py index fadead36..941f9497 100644 --- a/symposion/speakers/urls.py +++ b/symposion/speakers/urls.py @@ -1,3 +1,4 @@ +from __future__ import unicode_literals from django.conf.urls import patterns, url diff --git a/symposion/speakers/views.py b/symposion/speakers/views.py index fbd4c57c..12e88607 100644 --- a/symposion/speakers/views.py +++ b/symposion/speakers/views.py @@ -1,3 +1,4 @@ +from __future__ import unicode_literals from django.core.exceptions import ObjectDoesNotExist from django.http import Http404 from django.shortcuts import render, redirect, get_object_or_404 diff --git a/symposion/sponsorship/admin.py b/symposion/sponsorship/admin.py index 77a84b8d..c78fc8b3 100644 --- a/symposion/sponsorship/admin.py +++ b/symposion/sponsorship/admin.py @@ -1,3 +1,4 @@ +from __future__ import unicode_literals from django.contrib import admin from django.utils.html import escape from django.utils.safestring import mark_safe diff --git a/symposion/sponsorship/forms.py b/symposion/sponsorship/forms.py index 33b52f42..4fd379e6 100644 --- a/symposion/sponsorship/forms.py +++ b/symposion/sponsorship/forms.py @@ -1,3 +1,4 @@ +from __future__ import unicode_literals from django import forms from django.forms.models import inlineformset_factory, BaseInlineFormSet diff --git a/symposion/sponsorship/models.py b/symposion/sponsorship/models.py index d4161176..eb9aa7c0 100644 --- a/symposion/sponsorship/models.py +++ b/symposion/sponsorship/models.py @@ -1,3 +1,4 @@ +from __future__ import unicode_literals import datetime from django.conf import settings @@ -5,6 +6,7 @@ from django.core.exceptions import ValidationError from django.core.urlresolvers import reverse from django.db import models from django.db.models.signals import post_init, post_save +from django.utils.encoding import python_2_unicode_compatible from django.utils.translation import ugettext_lazy as _ from django.contrib.auth.models import User @@ -14,6 +16,7 @@ from symposion.conference.models import Conference from symposion.sponsorship.managers import SponsorManager +@python_2_unicode_compatible class SponsorLevel(models.Model): conference = models.ForeignKey(Conference, verbose_name=_("conference")) @@ -27,13 +30,14 @@ class SponsorLevel(models.Model): verbose_name = _("sponsor level") verbose_name_plural = _("sponsor levels") - def __unicode__(self): + def __str__(self): return self.name def sponsors(self): return self.sponsor_set.filter(active=True).order_by("added") +@python_2_unicode_compatible class Sponsor(models.Model): applicant = models.ForeignKey(User, related_name="sponsorships", verbose_name=_("applicant"), @@ -55,7 +59,7 @@ class Sponsor(models.Model): objects = SponsorManager() - def __unicode__(self): + def __str__(self): return self.name class Meta: @@ -166,6 +170,7 @@ CONTENT_TYPE_CHOICES = [ ] +@python_2_unicode_compatible class Benefit(models.Model): name = models.CharField(_("name"), max_length=100) @@ -175,10 +180,11 @@ class Benefit(models.Model): content_type = models.CharField(_("content type"), choices=CONTENT_TYPE_CHOICES, max_length=20, default="simple") - def __unicode__(self): + def __str__(self): return self.name +@python_2_unicode_compatible class BenefitLevel(models.Model): benefit = models.ForeignKey(Benefit, related_name="benefit_levels", verbose_name=_("benefit")) @@ -191,10 +197,11 @@ class BenefitLevel(models.Model): class Meta: ordering = ["level"] - def __unicode__(self): - return u"%s - %s" % (self.level, self.benefit) + def __str__(self): + return "%s - %s" % (self.level, self.benefit) +@python_2_unicode_compatible class SponsorBenefit(models.Model): sponsor = models.ForeignKey(Sponsor, related_name="sponsor_benefits", verbose_name=_("sponsor")) @@ -213,8 +220,8 @@ class SponsorBenefit(models.Model): class Meta: ordering = ["-active"] - def __unicode__(self): - return u"%s - %s" % (self.sponsor, self.benefit) + def __str__(self): + return "%s - %s" % (self.sponsor, self.benefit) def clean(self): num_words = len(self.text.split()) diff --git a/symposion/sponsorship/views.py b/symposion/sponsorship/views.py index bf74ec3a..68dcf5a1 100644 --- a/symposion/sponsorship/views.py +++ b/symposion/sponsorship/views.py @@ -1,3 +1,4 @@ +from __future__ import unicode_literals from cStringIO import StringIO import itertools import logging diff --git a/symposion/teams/forms.py b/symposion/teams/forms.py index a9080626..579c08cf 100644 --- a/symposion/teams/forms.py +++ b/symposion/teams/forms.py @@ -1,3 +1,4 @@ +from __future__ import unicode_literals from django import forms from django.utils.html import escape diff --git a/symposion/teams/models.py b/symposion/teams/models.py index 8c344cc4..6e783d2e 100644 --- a/symposion/teams/models.py +++ b/symposion/teams/models.py @@ -1,3 +1,4 @@ +from __future__ import unicode_literals import datetime from django.db import models @@ -5,6 +6,7 @@ from django.db import models import reversion from django.contrib.auth.models import Permission, User +from django.utils.encoding import python_2_unicode_compatible TEAM_ACCESS_CHOICES = [ @@ -14,6 +16,7 @@ TEAM_ACCESS_CHOICES = [ ] +@python_2_unicode_compatible class Team(models.Model): slug = models.SlugField(unique=True) @@ -34,7 +37,7 @@ class Team(models.Model): def get_absolute_url(self): return ("team_detail", [self.slug]) - def __unicode__(self): + def __str__(self): return self.name def get_state_for_user(self, user): diff --git a/symposion/teams/views.py b/symposion/teams/views.py index f45a0cba..83e26d3c 100644 --- a/symposion/teams/views.py +++ b/symposion/teams/views.py @@ -1,3 +1,4 @@ +from __future__ import unicode_literals from django.http import Http404, HttpResponseNotAllowed from django.shortcuts import render, redirect, get_object_or_404 diff --git a/symposion/views.py b/symposion/views.py index 68e40559..3b5882d4 100644 --- a/symposion/views.py +++ b/symposion/views.py @@ -1,3 +1,4 @@ +from __future__ import unicode_literals import hashlib import random From 3207621058b82f0ada9439999d66a8b6bab4fc55 Mon Sep 17 00:00:00 2001 From: Hiroshi Miura Date: Fri, 26 Jun 2015 12:46:09 +0900 Subject: [PATCH 616/751] i18n all part - forms for labels and helps - views for success or warning messages - apps and models verbose_names for admin Signed-off-by: Hiroshi Miura --- symposion/boxes/models.py | 11 +-- symposion/cms/models.py | 4 + symposion/conference/apps.py | 3 +- symposion/conference/models.py | 18 ++--- symposion/forms.py | 9 ++- symposion/proposals/actions.py | 7 +- symposion/proposals/apps.py | 3 +- symposion/proposals/forms.py | 5 +- symposion/proposals/models.py | 45 ++++++----- symposion/proposals/views.py | 2 +- symposion/reviews/apps.py | 3 +- symposion/reviews/forms.py | 4 +- symposion/reviews/models.py | 131 ++++++++++++++++++-------------- symposion/schedule/models.py | 93 ++++++++++++++--------- symposion/speakers/apps.py | 3 +- symposion/speakers/models.py | 29 ++++--- symposion/speakers/views.py | 9 ++- symposion/sponsorship/admin.py | 6 +- symposion/sponsorship/apps.py | 4 +- symposion/sponsorship/forms.py | 3 +- symposion/sponsorship/models.py | 78 ++++++++++--------- symposion/sponsorship/views.py | 10 +-- symposion/teams/forms.py | 16 ++-- symposion/teams/models.py | 55 +++++++++----- symposion/teams/views.py | 17 +++-- 25 files changed, 332 insertions(+), 236 deletions(-) diff --git a/symposion/boxes/models.py b/symposion/boxes/models.py index 4eb2a019..3c13edd1 100644 --- a/symposion/boxes/models.py +++ b/symposion/boxes/models.py @@ -1,5 +1,6 @@ from django.db import models from django.contrib.auth.models import User +from django.utils.translation import ugettext_lazy as _ import reversion @@ -8,17 +9,17 @@ from markitup.fields import MarkupField class Box(models.Model): - label = models.CharField(max_length=100, db_index=True) + label = models.CharField(max_length=100, db_index=True, verbose_name=_("Label")) content = MarkupField(blank=True) - created_by = models.ForeignKey(User, related_name="boxes") - last_updated_by = models.ForeignKey(User, related_name="updated_boxes") + created_by = models.ForeignKey(User, related_name="boxes", verbose_name=_("Created by")) + last_updated_by = models.ForeignKey(User, related_name="updated_boxes", verbose_name=_("Last updated by")) def __unicode__(self): return self.label class Meta: - verbose_name_plural = "boxes" - + verbose_name = _("Box") + verbose_name_plural = _("Boxes") reversion.register(Box) diff --git a/symposion/cms/models.py b/symposion/cms/models.py index 25c1caea..a1955402 100644 --- a/symposion/cms/models.py +++ b/symposion/cms/models.py @@ -38,6 +38,10 @@ class Page(models.Model): def __unicode__(self): return self.title + class Meta: + verbose_name = _("page") + verbose_name_plural = _("pages") + @models.permalink def get_absolute_url(self): return ("cms_page", [self.path]) diff --git a/symposion/conference/apps.py b/symposion/conference/apps.py index d7759eeb..7a330359 100644 --- a/symposion/conference/apps.py +++ b/symposion/conference/apps.py @@ -1,7 +1,8 @@ from django.apps import AppConfig +from django.utils.translation import ugettext_lazy as _ class ConferenceConfig(AppConfig): name = "symposion.conference" label = "symposion_conference" - verbose_name = "Symposion Conference" + verbose_name = _("Symposion Conference") diff --git a/symposion/conference/models.py b/symposion/conference/models.py index 582035b5..44e09627 100644 --- a/symposion/conference/models.py +++ b/symposion/conference/models.py @@ -12,14 +12,14 @@ class Conference(models.Model): the full conference for a specific year, e.g. US PyCon 2012. """ - title = models.CharField(_("title"), max_length=100) + title = models.CharField(_("Title"), max_length=100) # when the conference runs - start_date = models.DateField(_("start date"), null=True, blank=True) - end_date = models.DateField(_("end date"), null=True, blank=True) + start_date = models.DateField(_("Start date"), null=True, blank=True) + end_date = models.DateField(_("End date"), null=True, blank=True) # timezone the conference is in - timezone = TimeZoneField(_("timezone"), blank=True) + timezone = TimeZoneField(_("Timezone"), blank=True) def __unicode__(self): return self.title @@ -49,14 +49,14 @@ class Section(models.Model): scheduling process. """ - conference = models.ForeignKey(Conference, verbose_name=_("conference")) + conference = models.ForeignKey(Conference, verbose_name=_("Conference")) - name = models.CharField(_("name"), max_length=100) - slug = models.SlugField() + name = models.CharField(_("Name"), max_length=100) + slug = models.SlugField(verbose_name=_("Slug")) # when the section runs - start_date = models.DateField(_("start date"), null=True, blank=True) - end_date = models.DateField(_("end date"), null=True, blank=True) + start_date = models.DateField(_("Start date"), null=True, blank=True) + end_date = models.DateField(_("End date"), null=True, blank=True) def __unicode__(self): return u"%s %s" % (self.conference, self.name) diff --git a/symposion/forms.py b/symposion/forms.py index 09fc5d4f..bdaf89ae 100644 --- a/symposion/forms.py +++ b/symposion/forms.py @@ -6,13 +6,14 @@ except ImportError: from django import forms import account.forms +from django.utils.translation import ugettext_lazy as _ class SignupForm(account.forms.SignupForm): - first_name = forms.CharField() - last_name = forms.CharField() - email_confirm = forms.EmailField(label="Confirm Email") + first_name = forms.CharField(label=_("First name")) + last_name = forms.CharField(label=_("Last name")) + email_confirm = forms.EmailField(label=_("Confirm Email")) def __init__(self, *args, **kwargs): super(SignupForm, self).__init__(*args, **kwargs) @@ -32,7 +33,7 @@ class SignupForm(account.forms.SignupForm): if email: if email != email_confirm: raise forms.ValidationError( - "Email address must match previously typed email address") + _("Email address must match previously typed email address")) return email_confirm diff --git a/symposion/proposals/actions.py b/symposion/proposals/actions.py index d059da81..1edb6528 100644 --- a/symposion/proposals/actions.py +++ b/symposion/proposals/actions.py @@ -1,10 +1,11 @@ import csv from django.http import HttpResponse +from django.utils.translation import ugettext_lazy as _ -def export_as_csv_action(description="Export selected objects as CSV file", - fields=None, exclude=None, header=True): +def export_as_csv_action(description=None, fields=None, exclude=None, + header=True): """ This function returns an export csv action 'fields' and 'exclude' work like in Django ModelForm @@ -31,5 +32,7 @@ def export_as_csv_action(description="Export selected objects as CSV file", writer.writerow( [unicode(getattr(obj, field)).encode("utf-8", "replace") for field in field_names]) return response + if description is None: + description = _("Export selected objects as CSV file") export_as_csv.short_description = description return export_as_csv diff --git a/symposion/proposals/apps.py b/symposion/proposals/apps.py index 9a317525..c35c024c 100644 --- a/symposion/proposals/apps.py +++ b/symposion/proposals/apps.py @@ -1,7 +1,8 @@ from django.apps import AppConfig +from django.utils.translation import ugettext_lazy as _ class ProposalsConfig(AppConfig): name = "symposion.proposals" label = "symposion_proposals" - verbose_name = "Symposion Proposals" + verbose_name = _("Symposion Proposals") diff --git a/symposion/proposals/forms.py b/symposion/proposals/forms.py index 91c7e004..fada20be 100644 --- a/symposion/proposals/forms.py +++ b/symposion/proposals/forms.py @@ -1,5 +1,6 @@ from django import forms from django.db.models import Q +from django.utils.translation import ugettext_lazy as _ from symposion.proposals.models import SupportingDocument # from markitup.widgets import MarkItUpWidget @@ -11,7 +12,7 @@ from symposion.proposals.models import SupportingDocument class AddSpeakerForm(forms.Form): email = forms.EmailField( - label="Email address of new speaker (use their email address, not yours)" + label=_("Email address of new speaker (use their email address, not yours)") ) def __init__(self, *args, **kwargs): @@ -26,7 +27,7 @@ class AddSpeakerForm(forms.Form): ).exists() if exists: raise forms.ValidationError( - "This email address has already been invited to your talk proposal" + _("This email address has already been invited to your talk proposal") ) return value diff --git a/symposion/proposals/models.py b/symposion/proposals/models.py index 7dea87cd..a8b4f8e0 100644 --- a/symposion/proposals/models.py +++ b/symposion/proposals/models.py @@ -31,12 +31,12 @@ class ProposalSection(models.Model): * closed is NULL or False """ - section = models.OneToOneField(Section) + section = models.OneToOneField(Section, verbose_name=_("Section")) - start = models.DateTimeField(null=True, blank=True) - end = models.DateTimeField(null=True, blank=True) - closed = models.NullBooleanField() - published = models.NullBooleanField() + start = models.DateTimeField(null=True, blank=True, verbose_name=_("Start")) + end = models.DateTimeField(null=True, blank=True, verbose_name=_("End")) + closed = models.NullBooleanField(verbose_name=_("Closed")) + published = models.NullBooleanField(verbose_name=_("Published")) @classmethod def available(cls): @@ -67,10 +67,10 @@ class ProposalKind(models.Model): to distinguish the section as well as the kind. """ - section = models.ForeignKey(Section, related_name="proposal_kinds") + section = models.ForeignKey(Section, related_name="proposal_kinds", verbose_name=_("Section")) name = models.CharField(_("Name"), max_length=100) - slug = models.SlugField() + slug = models.SlugField(verbose_name=_("Slug")) def __unicode__(self): return self.name @@ -80,9 +80,9 @@ class ProposalBase(models.Model): objects = InheritanceManager() - kind = models.ForeignKey(ProposalKind) + kind = models.ForeignKey(ProposalKind, verbose_name=_("Kind")) - title = models.CharField(max_length=100) + title = models.CharField(max_length=100, verbose_name=_("Title")) description = models.TextField( _("Brief Description"), max_length=400, # @@@ need to enforce 400 in UI @@ -96,6 +96,7 @@ class ProposalBase(models.Model): "target='_blank'>Markdown.") ) additional_notes = MarkupField( + _("Addtional Notes"), blank=True, help_text=_("Anything else you'd like the program committee to know when making their " "selection: your past experience, etc. This is not made public. Edit using " @@ -105,8 +106,9 @@ class ProposalBase(models.Model): submitted = models.DateTimeField( default=now, editable=False, + verbose_name=_("Submitted") ) - speaker = models.ForeignKey(Speaker, related_name="proposals") + speaker = models.ForeignKey(Speaker, related_name="proposals", verbose_name=_("Speaker")) def additional_speaker_validator(self, a_speaker): if a_speaker.speaker.email == self.speaker.email: @@ -115,8 +117,9 @@ class ProposalBase(models.Model): raise ValidationError(_("%s has already been in speakers.") % a_speaker.speaker.email) additional_speakers = models.ManyToManyField(Speaker, through="AdditionalSpeaker", - blank=True, validators=[additional_speaker_validator]) - cancelled = models.BooleanField(default=False) + blank=True, verbose_name=_("Addtional speakers"), + validators=[additional_speaker_validator]) + cancelled = models.BooleanField(default=False, verbose_name=_("Cancelled")) def can_edit(self): return True @@ -170,12 +173,14 @@ class AdditionalSpeaker(models.Model): (SPEAKING_STATUS_DECLINED, _("Declined")), ] - speaker = models.ForeignKey(Speaker) - proposalbase = models.ForeignKey(ProposalBase) - status = models.IntegerField(choices=SPEAKING_STATUS, default=SPEAKING_STATUS_PENDING) + speaker = models.ForeignKey(Speaker, verbose_name=_("Speaker")) + proposalbase = models.ForeignKey(ProposalBase, verbose_name=_("Proposalbase")) + status = models.IntegerField(choices=SPEAKING_STATUS, default=SPEAKING_STATUS_PENDING, verbose_name=_("Status")) class Meta: unique_together = ("speaker", "proposalbase") + verbose_name = _("Addtional speaker") + verbose_name_plural = _("Additional speakers") def __unicode__(self): if self.status is self.SPEAKING_STATUS_PENDING: @@ -194,14 +199,14 @@ def uuid_filename(instance, filename): class SupportingDocument(models.Model): - proposal = models.ForeignKey(ProposalBase, related_name="supporting_documents") + proposal = models.ForeignKey(ProposalBase, related_name="supporting_documents", verbose_name=_("Proposal")) - uploaded_by = models.ForeignKey(User) + uploaded_by = models.ForeignKey(User, verbose_name=_("Uploaded by")) - created_at = models.DateTimeField(default=now) + created_at = models.DateTimeField(default=now, verbose_name=_("Created at")) - file = models.FileField(upload_to=uuid_filename) - description = models.CharField(max_length=140) + file = models.FileField(upload_to=uuid_filename, verbose_name=_("File")) + description = models.CharField(max_length=140, verbose_name=_("Description")) def download_url(self): return reverse("proposal_document_download", diff --git a/symposion/proposals/views.py b/symposion/proposals/views.py index 46c3c1c6..1d07b23d 100644 --- a/symposion/proposals/views.py +++ b/symposion/proposals/views.py @@ -87,7 +87,7 @@ def proposal_submit_kind(request, kind_slug): proposal.speaker = speaker_profile proposal.save() form.save_m2m() - messages.success(request, "Proposal submitted.") + messages.success(request, _("Proposal submitted.")) if "add-speakers" in request.POST: return redirect("proposal_speaker_manage", proposal.pk) return redirect("dashboard") diff --git a/symposion/reviews/apps.py b/symposion/reviews/apps.py index 683b0c0f..80fab5c3 100644 --- a/symposion/reviews/apps.py +++ b/symposion/reviews/apps.py @@ -1,7 +1,8 @@ from django.apps import AppConfig +from django.utils.translation import ugettext_lazy as _ class ReviewsConfig(AppConfig): name = "symposion.reviews" label = "symposion_reviews" - verbose_name = "Symposion Reviews" + verbose_name = _("Symposion Reviews") diff --git a/symposion/reviews/forms.py b/symposion/reviews/forms.py index a705333c..f49cdab8 100644 --- a/symposion/reviews/forms.py +++ b/symposion/reviews/forms.py @@ -1,4 +1,5 @@ from django import forms +from django.utils.translation import ugettext_lazy as _ from markitup.widgets import MarkItUpWidget @@ -35,6 +36,7 @@ class SpeakerCommentForm(forms.ModelForm): class BulkPresentationForm(forms.Form): talk_ids = forms.CharField( + label=_("Talk ids"), max_length=500, - help_text="Provide a comma seperated list of talk ids to accept." + help_text=_("Provide a comma seperated list of talk ids to accept.") ) diff --git a/symposion/reviews/models.py b/symposion/reviews/models.py index 1537ae98..48602639 100644 --- a/symposion/reviews/models.py +++ b/symposion/reviews/models.py @@ -7,6 +7,7 @@ from django.db.models import Q from django.db.models.signals import post_save from django.contrib.auth.models import User +from django.utils.translation import ugettext_lazy as _ from markitup.fields import MarkupField @@ -31,10 +32,10 @@ class Votes(object): MINUS_ONE = u"−1" CHOICES = [ - (PLUS_ONE, u"+1 — Good proposal and I will argue for it to be accepted."), - (PLUS_ZERO, u"+0 — OK proposal, but I will not argue for it to be accepted."), - (MINUS_ZERO, u"−0 — Weak proposal, but I will not argue strongly against acceptance."), - (MINUS_ONE, u"−1 — Serious issues and I will argue to reject this proposal."), + (PLUS_ONE, _("+1 — Good proposal and I will argue for it to be accepted.")), + (PLUS_ZERO, _("+0 — OK proposal, but I will not argue for it to be accepted.")), + (MINUS_ZERO, _("−0 — Weak proposal, but I will not argue strongly against acceptance.")), + (MINUS_ONE, _("−1 — Serious issues and I will argue to reject this proposal.")), ] VOTES = Votes() @@ -47,18 +48,18 @@ class ReviewAssignment(models.Model): NUM_REVIEWERS = 3 ORIGIN_CHOICES = [ - (AUTO_ASSIGNED_INITIAL, "auto-assigned, initial"), - (OPT_IN, "opted-in"), - (AUTO_ASSIGNED_LATER, "auto-assigned, later"), + (AUTO_ASSIGNED_INITIAL, _("auto-assigned, initial")), + (OPT_IN, _("opted-in")), + (AUTO_ASSIGNED_LATER, _("auto-assigned, later")), ] - proposal = models.ForeignKey(ProposalBase) - user = models.ForeignKey(User) + proposal = models.ForeignKey(ProposalBase, verbose_name=_("Proposal")) + user = models.ForeignKey(User, verbose_name=_("User")) - origin = models.IntegerField(choices=ORIGIN_CHOICES) + origin = models.IntegerField(choices=ORIGIN_CHOICES, verbose_name=_("Origin")) - assigned_at = models.DateTimeField(default=datetime.now) - opted_out = models.BooleanField(default=False) + assigned_at = models.DateTimeField(default=datetime.now, verbose_name=_("Assigned at")) + opted_out = models.BooleanField(default=False, verbose_name=_("Opted out")) @classmethod def create_assignments(cls, proposal, origin=AUTO_ASSIGNED_INITIAL): @@ -92,27 +93,29 @@ class ReviewAssignment(models.Model): class ProposalMessage(models.Model): - proposal = models.ForeignKey(ProposalBase, related_name="messages") - user = models.ForeignKey(User) + proposal = models.ForeignKey(ProposalBase, related_name="messages", verbose_name=_("Proposal")) + user = models.ForeignKey(User, verbose_name=_("User")) - message = MarkupField() - submitted_at = models.DateTimeField(default=datetime.now, editable=False) + message = MarkupField(verbose_name=_("Message")) + submitted_at = models.DateTimeField(default=datetime.now, editable=False, verbose_name=_("Submitted at")) class Meta: ordering = ["submitted_at"] + verbose_name = _("proposal message") + verbose_name_plural = _("proposal messages") class Review(models.Model): VOTES = VOTES - proposal = models.ForeignKey(ProposalBase, related_name="reviews") - user = models.ForeignKey(User) + proposal = models.ForeignKey(ProposalBase, related_name="reviews", verbose_name=_("Proposal")) + user = models.ForeignKey(User, verbose_name=_("User")) # No way to encode "-0" vs. "+0" into an IntegerField, and I don't feel # like some complicated encoding system. - vote = models.CharField(max_length=2, blank=True, choices=VOTES.CHOICES) - comment = MarkupField() - submitted_at = models.DateTimeField(default=datetime.now, editable=False) + vote = models.CharField(max_length=2, blank=True, choices=VOTES.CHOICES, verbose_name=_("Vote")) + comment = MarkupField(verbose_name=_("Comment")) + submitted_at = models.DateTimeField(default=datetime.now, editable=False, verbose_name=_("Submitted at")) def save(self, **kwargs): if self.vote: @@ -180,20 +183,26 @@ class Review(models.Model): def section(self): return self.proposal.kind.section.slug + class Meta: + verbose_name = _("review") + verbose_name_plural = _("reviews") + class LatestVote(models.Model): VOTES = VOTES - proposal = models.ForeignKey(ProposalBase, related_name="votes") - user = models.ForeignKey(User) + proposal = models.ForeignKey(ProposalBase, related_name="votes", verbose_name=_("Proposal")) + user = models.ForeignKey(User, verbose_name=_("User")) # No way to encode "-0" vs. "+0" into an IntegerField, and I don't feel # like some complicated encoding system. - vote = models.CharField(max_length=2, choices=VOTES.CHOICES) - submitted_at = models.DateTimeField(default=datetime.now, editable=False) + vote = models.CharField(max_length=2, choices=VOTES.CHOICES, verbose_name=_("Vote")) + submitted_at = models.DateTimeField(default=datetime.now, editable=False, verbose_name=_("Submitted at")) class Meta: unique_together = [("proposal", "user")] + verbose_name = _("latest vote") + verbose_name_plural = _("latest votes") def css_class(self): return { @@ -205,25 +214,25 @@ class LatestVote(models.Model): class ProposalResult(models.Model): - proposal = models.OneToOneField(ProposalBase, related_name="result") - score = models.DecimalField(max_digits=5, decimal_places=2, default=Decimal("0.00")) - comment_count = models.PositiveIntegerField(default=0) - vote_count = models.PositiveIntegerField(default=0) - plus_one = models.PositiveIntegerField(default=0) - plus_zero = models.PositiveIntegerField(default=0) - minus_zero = models.PositiveIntegerField(default=0) - minus_one = models.PositiveIntegerField(default=0) + proposal = models.OneToOneField(ProposalBase, related_name="result", verbose_name=_("Proposal")) + score = models.DecimalField(max_digits=5, decimal_places=2, default=Decimal("0.00"), verbose_name=_("Score")) + comment_count = models.PositiveIntegerField(default=0, verbose_name=_("Comment count")) + vote_count = models.PositiveIntegerField(default=0, verbose_name=_("Vote count")) + plus_one = models.PositiveIntegerField(default=0, verbose_name=_("Plus one")) + plus_zero = models.PositiveIntegerField(default=0, verbose_name=_("Plus zero")) + minus_zero = models.PositiveIntegerField(default=0, verbose_name=_("Minus zero")) + minus_one = models.PositiveIntegerField(default=0, verbose_name=_("Minus one")) accepted = models.NullBooleanField(choices=[ (True, "accepted"), (False, "rejected"), (None, "undecided"), - ], default=None) + ], default=None, verbose_name=_("Accepted")) status = models.CharField(max_length=20, choices=[ - ("accepted", "accepted"), - ("rejected", "rejected"), - ("undecided", "undecided"), - ("standby", "standby"), - ], default="undecided") + ("accepted", _("accepted")), + ("rejected", _("rejected")), + ("undecided", _("undecided")), + ("standby", _("standby")), + ], default="undecided", verbose_name=_("Status")) @classmethod def full_calculate(cls): @@ -279,35 +288,47 @@ class ProposalResult(models.Model): model = self.__class__ model._default_manager.filter(pk=self.pk).update(score=ProposalScoreExpression()) + class Meta: + verbose_name = _("proposal_result") + verbose_name_plural = _("proposal_results") + class Comment(models.Model): - proposal = models.ForeignKey(ProposalBase, related_name="comments") - commenter = models.ForeignKey(User) - text = MarkupField() + proposal = models.ForeignKey(ProposalBase, related_name="comments", verbose_name=_("Proposal")) + commenter = models.ForeignKey(User, verbose_name=_("Commenter")) + text = MarkupField(verbose_name=_("Text")) # Or perhaps more accurately, can the user see this comment. - public = models.BooleanField(choices=[(True, "public"), (False, "private")], default=False) - commented_at = models.DateTimeField(default=datetime.now) + public = models.BooleanField(choices=[(True, _("public")), (False, _("private"))], default=False, verbose_name=_("Public")) + commented_at = models.DateTimeField(default=datetime.now, verbose_name=_("Commented at")) + + class Meta: + verbose_name = _("comment") + verbose_name_plural = _("comments") class NotificationTemplate(models.Model): - label = models.CharField(max_length=100) - from_address = models.EmailField() - subject = models.CharField(max_length=100) - body = models.TextField() + label = models.CharField(max_length=100, verbose_name=_("Label")) + from_address = models.EmailField(verbose_name=_("From address")) + subject = models.CharField(max_length=100, verbose_name=_("Subject")) + body = models.TextField(verbose_name=_("Body")) + + class Meta: + verbose_name = _("notification template") + verbose_name_plural = _("notification templates") class ResultNotification(models.Model): - proposal = models.ForeignKey(ProposalBase, related_name="notifications") + proposal = models.ForeignKey(ProposalBase, related_name="notifications", verbose_name=_("Proposal")) template = models.ForeignKey(NotificationTemplate, null=True, blank=True, - on_delete=models.SET_NULL) - timestamp = models.DateTimeField(default=datetime.now) - to_address = models.EmailField() - from_address = models.EmailField() - subject = models.CharField(max_length=100) - body = models.TextField() + on_delete=models.SET_NULL, verbose_name=_("Template")) + timestamp = models.DateTimeField(default=datetime.now, verbose_name=_("Timestamp")) + to_address = models.EmailField(verbose_name=_("To address")) + from_address = models.EmailField(verbose_name=_("From address")) + subject = models.CharField(max_length=100, verbose_name=_("Subject")) + body = models.TextField(verbose_name=_("Body")) def recipients(self): for speaker in self.proposal.speakers(): diff --git a/symposion/schedule/models.py b/symposion/schedule/models.py index a0f88e81..efa2d24c 100644 --- a/symposion/schedule/models.py +++ b/symposion/schedule/models.py @@ -3,6 +3,7 @@ import datetime from django.core.exceptions import ObjectDoesNotExist from django.contrib.auth.models import User from django.db import models +from django.utils.translation import ugettext_lazy as _ from markitup.fields import MarkupField @@ -13,21 +14,23 @@ from symposion.speakers.models import Speaker class Schedule(models.Model): - section = models.OneToOneField(Section) - published = models.BooleanField(default=True) - hidden = models.BooleanField("Hide schedule from overall conference view", default=False) + section = models.OneToOneField(Section, verbose_name=_("Section")) + published = models.BooleanField(default=True, verbose_name=_("Published")) + hidden = models.BooleanField(_("Hide schedule from overall conference view"), default=False) def __unicode__(self): return u"%s Schedule" % self.section class Meta: ordering = ["section"] + verbose_name = _('Schedule') + verbose_name_plural = _('Schedules') class Day(models.Model): - schedule = models.ForeignKey(Schedule) - date = models.DateField() + schedule = models.ForeignKey(Schedule, verbose_name=_("Schedule")) + date = models.DateField(verbose_name=_("Date")) def __unicode__(self): return u"%s" % self.date @@ -35,17 +38,23 @@ class Day(models.Model): class Meta: unique_together = [("schedule", "date")] ordering = ["date"] + verbose_name = _("date") + verbose_name_plural = _("dates") class Room(models.Model): - schedule = models.ForeignKey(Schedule) - name = models.CharField(max_length=65) - order = models.PositiveIntegerField() + schedule = models.ForeignKey(Schedule, verbose_name=_("Schedule")) + name = models.CharField(max_length=65, verbose_name=_("Name")) + order = models.PositiveIntegerField(verbose_name=_("Order")) def __unicode__(self): return self.name + class Meta: + verbose_name = _("Room") + verbose_name_plural = _("Rooms") + class SlotKind(models.Model): """ @@ -53,20 +62,24 @@ class SlotKind(models.Model): break, lunch, or X-minute talk. """ - schedule = models.ForeignKey(Schedule) - label = models.CharField(max_length=50) + schedule = models.ForeignKey(Schedule, verbose_name=_("schedule")) + label = models.CharField(max_length=50, verbose_name=_("Label")) def __unicode__(self): return self.label + class Meta: + verbose_name = _("Slot kind") + verbose_name_plural = _("Slot kinds") + class Slot(models.Model): - day = models.ForeignKey(Day) - kind = models.ForeignKey(SlotKind) - start = models.TimeField() - end = models.TimeField() - content_override = MarkupField(blank=True) + day = models.ForeignKey(Day, verbose_name=_("Day")) + kind = models.ForeignKey(SlotKind, verbose_name=_("Kind")) + start = models.TimeField(verbose_name=_("Start")) + end = models.TimeField(verbose_name=_("End")) + content_override = MarkupField(blank=True, verbose_name=_("Content override")) def assign(self, content): """ @@ -130,6 +143,8 @@ class Slot(models.Model): class Meta: ordering = ["day", "start", "end"] + verbose_name = _("slot") + verbose_name_plural = _("slots") class SlotRoom(models.Model): @@ -137,8 +152,8 @@ class SlotRoom(models.Model): Links a slot with a room. """ - slot = models.ForeignKey(Slot) - room = models.ForeignKey(Room) + slot = models.ForeignKey(Slot, verbose_name=_("Slot")) + room = models.ForeignKey(Room, verbose_name=_("Room")) def __unicode__(self): return u"%s %s" % (self.room, self.slot) @@ -146,20 +161,22 @@ class SlotRoom(models.Model): class Meta: unique_together = [("slot", "room")] ordering = ["slot", "room__order"] + verbose_name = _("Slot room") + verbose_name_plural = _("Slot rooms") class Presentation(models.Model): - slot = models.OneToOneField(Slot, null=True, blank=True, related_name="content_ptr") - title = models.CharField(max_length=100) - description = MarkupField() - abstract = MarkupField() - speaker = models.ForeignKey(Speaker, related_name="presentations") + slot = models.OneToOneField(Slot, null=True, blank=True, related_name="content_ptr", verbose_name=_("Slot")) + title = models.CharField(max_length=100, verbose_name=_("Title")) + description = MarkupField(verbose_name=_("Description")) + abstract = MarkupField(verbose_name=_("Abstract")) + speaker = models.ForeignKey(Speaker, related_name="presentations", verbose_name=_("Speaker")) additional_speakers = models.ManyToManyField(Speaker, related_name="copresentations", - blank=True) - cancelled = models.BooleanField(default=False) - proposal_base = models.OneToOneField(ProposalBase, related_name="presentation") - section = models.ForeignKey(Section, related_name="presentations") + blank=True, verbose_name=_("Additional speakers")) + cancelled = models.BooleanField(default=False, verbose_name=_("Cancelled")) + proposal_base = models.OneToOneField(ProposalBase, related_name="presentation", verbose_name=_("Proposal base")) + section = models.ForeignKey(Section, related_name="presentations", verbose_name=_("Section")) @property def number(self): @@ -182,12 +199,14 @@ class Presentation(models.Model): class Meta: ordering = ["slot"] + verbose_name = _("presentation") + verbose_name_plural = _("presentations") class Session(models.Model): - day = models.ForeignKey(Day, related_name="sessions") - slots = models.ManyToManyField(Slot, related_name="sessions") + day = models.ForeignKey(Day, related_name="sessions", verbose_name=_("Day")) + slots = models.ManyToManyField(Slot, related_name="sessions", verbose_name=_("Slots")) def sorted_slots(self): return self.slots.order_by("start") @@ -217,6 +236,10 @@ class Session(models.Model): ) return u"" + class Meta: + verbose_name = _("Session") + verbose_name_plural = _("Sessions") + class SessionRole(models.Model): @@ -224,19 +247,21 @@ class SessionRole(models.Model): SESSION_ROLE_RUNNER = 2 SESSION_ROLE_TYPES = [ - (SESSION_ROLE_CHAIR, "Session Chair"), - (SESSION_ROLE_RUNNER, "Session Runner"), + (SESSION_ROLE_CHAIR, _("Session Chair")), + (SESSION_ROLE_RUNNER, _("Session Runner")), ] - session = models.ForeignKey(Session) - user = models.ForeignKey(User) - role = models.IntegerField(choices=SESSION_ROLE_TYPES) - status = models.NullBooleanField() + session = models.ForeignKey(Session, verbose_name=_("Session")) + user = models.ForeignKey(User, verbose_name=_("User")) + role = models.IntegerField(choices=SESSION_ROLE_TYPES, verbose_name=_("Role")) + status = models.NullBooleanField(verbose_name=_("Status")) submitted = models.DateTimeField(default=datetime.datetime.now) class Meta: unique_together = [("session", "user", "role")] + verbose_name = _("Session role") + verbose_name_plural = _("Session roles") def __unicode__(self): return u"%s %s: %s" % (self.user, self.session, diff --git a/symposion/speakers/apps.py b/symposion/speakers/apps.py index b4abf5d5..c772a12a 100644 --- a/symposion/speakers/apps.py +++ b/symposion/speakers/apps.py @@ -1,7 +1,8 @@ from django.apps import AppConfig +from django.utils.translation import ugettext_lazy as _ class SpeakersConfig(AppConfig): name = "symposion.speakers" label = "symposion_speakers" - verbose_name = "Symposion Speakers" + verbose_name = _("Symposion Speakers") diff --git a/symposion/speakers/models.py b/symposion/speakers/models.py index 0887ef45..48121f2c 100644 --- a/symposion/speakers/models.py +++ b/symposion/speakers/models.py @@ -1,4 +1,5 @@ import datetime +from django.utils.translation import ugettext_lazy as _ from django.db import models from django.core.urlresolvers import reverse @@ -15,24 +16,28 @@ class Speaker(models.Model): (2, "Two") ] - user = models.OneToOneField(User, null=True, related_name="speaker_profile") - name = models.CharField(max_length=100, help_text=("As you would like it to appear in the " - "conference program.")) - biography = MarkupField(blank=True, help_text=("A little bit about you. Edit using " - "" - "Markdown.")) - photo = models.ImageField(upload_to="speaker_photos", blank=True) - annotation = models.TextField() # staff only - invite_email = models.CharField(max_length=200, unique=True, null=True, db_index=True) - invite_token = models.CharField(max_length=40, db_index=True) + user = models.OneToOneField(User, null=True, related_name="speaker_profile", verbose_name=_("User")) + name = models.CharField(verbose_name=_("Name"), max_length=100, + help_text=_("As you would like it to appear in the" + " conference program.")) + biography = MarkupField(blank=True, help_text=_("A little bit about you. Edit using " + "" + "Markdown."), verbose_name=_("Biography")) + photo = models.ImageField(upload_to="speaker_photos", blank=True, verbose_name=_("Photo")) + annotation = models.TextField(verbose_name=_("Annotation")) # staff only + invite_email = models.CharField(max_length=200, unique=True, null=True, db_index=True, verbose_name=_("Invite_email")) + invite_token = models.CharField(max_length=40, db_index=True, verbose_name=_("Invite token")) created = models.DateTimeField( default=datetime.datetime.now, - editable=False + editable=False, + verbose_name=_("Created") ) class Meta: ordering = ['name'] + verbose_name = _("Speaker") + verbose_name_plural = _("Speakers") def __unicode__(self): if self.user: diff --git a/symposion/speakers/views.py b/symposion/speakers/views.py index fbd4c57c..6d48c1dd 100644 --- a/symposion/speakers/views.py +++ b/symposion/speakers/views.py @@ -5,6 +5,7 @@ from django.shortcuts import render, redirect, get_object_or_404 from django.contrib import messages from django.contrib.auth.decorators import login_required from django.contrib.auth.models import User +from django.utils.translation import ugettext_lazy as _ from symposion.proposals.models import ProposalBase from symposion.speakers.forms import SpeakerForm @@ -33,7 +34,7 @@ def speaker_create(request): if not found: speaker.invite_email = None speaker.save() - messages.success(request, "Speaker profile created.") + messages.success(request, _("Speaker profile created.")) return redirect("dashboard") else: form = SpeakerForm(initial={"name": request.user.get_full_name()}) @@ -61,7 +62,7 @@ def speaker_create_staff(request, pk): speaker = form.save(commit=False) speaker.user = user speaker.save() - messages.success(request, "Speaker profile created.") + messages.success(request, _("Speaker profile created.")) return redirect("user_list") else: form = SpeakerForm(initial={"name": user.get_full_name()}) @@ -88,8 +89,8 @@ def speaker_create_token(request, token): ).update( speaker=existing_speaker ) - messages.info(request, ("You have been associated with all pending " - "talk proposals")) + messages.info(request, _("You have been associated with all pending " + "talk proposals")) return redirect("dashboard") else: if not request.user.is_authenticated(): diff --git a/symposion/sponsorship/admin.py b/symposion/sponsorship/admin.py index 77a84b8d..0e38ad04 100644 --- a/symposion/sponsorship/admin.py +++ b/symposion/sponsorship/admin.py @@ -61,9 +61,9 @@ class SponsorAdmin(admin.ModelAdmin): # @@@ kinda ugly but using choices= on NullBooleanField is broken form = super(SponsorAdmin, self).get_form(*args, **kwargs) form.base_fields["active"].widget.choices = [ - (u"1", "unreviewed"), - (u"2", "approved"), - (u"3", "rejected") + ("1", _("unreviewed")), + ("2", _("approved")), + ("3", _("rejected")) ] return form diff --git a/symposion/sponsorship/apps.py b/symposion/sponsorship/apps.py index 80bf19d1..92ee49cd 100644 --- a/symposion/sponsorship/apps.py +++ b/symposion/sponsorship/apps.py @@ -1,7 +1,9 @@ +from __future__ import unicode_literals from django.apps import AppConfig +from django.utils.translation import ugettext_lazy as _ class SponsorshipConfig(AppConfig): name = "symposion.sponsorship" label = "symposion_sponsorship" - verbose_name = "Symposion Sponsorship" + verbose_name = _("Symposion Sponsorship") diff --git a/symposion/sponsorship/forms.py b/symposion/sponsorship/forms.py index 33b52f42..336f7ff5 100644 --- a/symposion/sponsorship/forms.py +++ b/symposion/sponsorship/forms.py @@ -2,6 +2,7 @@ from django import forms from django.forms.models import inlineformset_factory, BaseInlineFormSet from django.contrib.admin.widgets import AdminFileWidget +from django.utils.translation import ugettext_lazy as _ from symposion.sponsorship.models import Sponsor, SponsorBenefit @@ -61,7 +62,7 @@ class SponsorBenefitsInlineFormSet(BaseInlineFormSet): # provide word limit as help_text if form.instance.benefit.type == "text" and form.instance.max_words: - form.fields[field].help_text = u"maximum %s words" % form.instance.max_words + form.fields[field].help_text = _("maximum %s words") % form.instance.max_words # use admin file widget that shows currently uploaded file if field == "upload": diff --git a/symposion/sponsorship/models.py b/symposion/sponsorship/models.py index d4161176..13bace02 100644 --- a/symposion/sponsorship/models.py +++ b/symposion/sponsorship/models.py @@ -16,16 +16,16 @@ from symposion.sponsorship.managers import SponsorManager class SponsorLevel(models.Model): - conference = models.ForeignKey(Conference, verbose_name=_("conference")) - name = models.CharField(_("name"), max_length=100) - order = models.IntegerField(_("order"), default=0) - cost = models.PositiveIntegerField(_("cost")) - description = models.TextField(_("description"), blank=True, help_text=_("This is private.")) + conference = models.ForeignKey(Conference, verbose_name=_("Conference")) + name = models.CharField(_("Name"), max_length=100) + order = models.IntegerField(_("Order"), default=0) + cost = models.PositiveIntegerField(_("Cost")) + description = models.TextField(_("Description"), blank=True, help_text=_("This is private.")) class Meta: ordering = ["conference", "order"] - verbose_name = _("sponsor level") - verbose_name_plural = _("sponsor levels") + verbose_name = _("Sponsor level") + verbose_name_plural = _("Sponsor levels") def __unicode__(self): return self.name @@ -36,22 +36,22 @@ class SponsorLevel(models.Model): class Sponsor(models.Model): - applicant = models.ForeignKey(User, related_name="sponsorships", verbose_name=_("applicant"), + applicant = models.ForeignKey(User, related_name="sponsorships", verbose_name=_("Applicant"), null=True) name = models.CharField(_("Sponsor Name"), max_length=100) display_url = models.URLField(_("display URL"), blank=True) - external_url = models.URLField(_("external URL")) - annotation = models.TextField(_("annotation"), blank=True) + external_url = models.URLField(_("External URL")) + annotation = models.TextField(_("Annotation"), blank=True) contact_name = models.CharField(_("Contact Name"), max_length=100) - contact_email = models.EmailField(_(u"Contact Email")) + contact_email = models.EmailField(_("Contact Email")) level = models.ForeignKey(SponsorLevel, verbose_name=_("level")) added = models.DateTimeField(_("added"), default=datetime.datetime.now) active = models.BooleanField(_("active"), default=False) # Denormalization (this assumes only one logo) sponsor_logo = models.ForeignKey("SponsorBenefit", related_name="+", null=True, blank=True, - editable=False) + editable=False, verbose_name=_("Sponsor logo")) objects = SponsorManager() @@ -59,8 +59,8 @@ class Sponsor(models.Model): return self.name class Meta: - verbose_name = _("sponsor") - verbose_name_plural = _("sponsors") + verbose_name = _("Sponsor") + verbose_name_plural = _("Sponsors") def get_absolute_url(self): if self.active: @@ -151,12 +151,12 @@ post_save.connect(_check_level_change, sender=Sponsor) BENEFIT_TYPE_CHOICES = [ - ("text", "Text"), - ("richtext", "Rich Text"), - ("file", "File"), - ("weblogo", "Web Logo"), - ("simple", "Simple"), - ("option", "Option") + ("text", _("Text")), + ("file", _("File")), + ("richtext", _("Rich Text")), + ("weblogo", _("Web Logo")), + ("simple", _("Simple")), + ("option", _("Option")) ] CONTENT_TYPE_CHOICES = [ @@ -168,10 +168,10 @@ CONTENT_TYPE_CHOICES = [ class Benefit(models.Model): - name = models.CharField(_("name"), max_length=100) - description = models.TextField(_("description"), blank=True) - type = models.CharField(_("type"), choices=BENEFIT_TYPE_CHOICES, - max_length=10, default="simple") + name = models.CharField(_("Name"), max_length=100) + description = models.TextField(_("Description"), blank=True) + type = models.CharField(_("Type"), choices=BENEFIT_TYPE_CHOICES, max_length=10, + default="simple") content_type = models.CharField(_("content type"), choices=CONTENT_TYPE_CHOICES, max_length=20, default="simple") @@ -181,15 +181,17 @@ class Benefit(models.Model): class BenefitLevel(models.Model): - benefit = models.ForeignKey(Benefit, related_name="benefit_levels", verbose_name=_("benefit")) - level = models.ForeignKey(SponsorLevel, related_name="benefit_levels", verbose_name=_("level")) + benefit = models.ForeignKey(Benefit, related_name="benefit_levels", verbose_name=_("Benefit")) + level = models.ForeignKey(SponsorLevel, related_name="benefit_levels", verbose_name=_("Level")) # default limits for this benefit at given level - max_words = models.PositiveIntegerField(_("max words"), blank=True, null=True) - other_limits = models.CharField(_("other limits"), max_length=200, blank=True) + max_words = models.PositiveIntegerField(_("Max words"), blank=True, null=True) + other_limits = models.CharField(_("Other limits"), max_length=200, blank=True) class Meta: ordering = ["level"] + verbose_name = _("Benefit level") + verbose_name_plural = _("Benefit levels") def __unicode__(self): return u"%s - %s" % (self.level, self.benefit) @@ -197,21 +199,23 @@ class BenefitLevel(models.Model): class SponsorBenefit(models.Model): - sponsor = models.ForeignKey(Sponsor, related_name="sponsor_benefits", verbose_name=_("sponsor")) - benefit = models.ForeignKey(Benefit, related_name="sponsor_benefits", verbose_name=_("benefit")) - active = models.BooleanField(default=True) + sponsor = models.ForeignKey(Sponsor, related_name="sponsor_benefits", verbose_name=_("Sponsor")) + benefit = models.ForeignKey(Benefit, related_name="sponsor_benefits", verbose_name=_("Benefit")) + active = models.BooleanField(default=True, verbose_name=_("Active")) # Limits: will initially be set to defaults from corresponding BenefitLevel - max_words = models.PositiveIntegerField(_("max words"), blank=True, null=True) - other_limits = models.CharField(_("other limits"), max_length=200, blank=True) + max_words = models.PositiveIntegerField(_("Max words"), blank=True, null=True) + other_limits = models.CharField(_("Other limits"), max_length=200, blank=True) # Data: zero or one of these fields will be used, depending on the # type of the Benefit (text, file, or simple) - text = models.TextField(_("text"), blank=True) - upload = models.FileField(_("file"), blank=True, upload_to="sponsor_files") + text = models.TextField(_("Text"), blank=True) + upload = models.FileField(_("File"), blank=True, upload_to="sponsor_files") class Meta: ordering = ["-active"] + verbose_name = _("Sponsor benefit") + verbose_name_plural = _("Sponsor benefits") def __unicode__(self): return u"%s - %s" % (self.sponsor, self.benefit) @@ -220,8 +224,8 @@ class SponsorBenefit(models.Model): num_words = len(self.text.split()) if self.max_words and num_words > self.max_words: raise ValidationError( - "Sponsorship level only allows for %s words, you provided %d." % ( - self.max_words, num_words)) + _("Sponsorship level only allows for %(word)s words, you provided %(num)d.") % { + "word": self.max_words, "num": num_words}) def data_fields(self): """ diff --git a/symposion/sponsorship/views.py b/symposion/sponsorship/views.py index bf74ec3a..ae5597e7 100644 --- a/symposion/sponsorship/views.py +++ b/symposion/sponsorship/views.py @@ -6,16 +6,14 @@ import time from zipfile import ZipFile, ZipInfo from django.conf import settings - +from django.contrib import messages +from django.contrib.admin.views.decorators import staff_member_required +from django.contrib.auth.decorators import login_required 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 Benefit, Sponsor, SponsorBenefit, \ @@ -90,7 +88,7 @@ def sponsor_detail(request, pk): form.save() formset.save() - messages.success(request, "Sponsorship details have been updated") + messages.success(request, _("Sponsorship details have been updated")) return redirect("dashboard") else: diff --git a/symposion/teams/forms.py b/symposion/teams/forms.py index a9080626..5ba8ccae 100644 --- a/symposion/teams/forms.py +++ b/symposion/teams/forms.py @@ -2,6 +2,7 @@ from django import forms from django.utils.html import escape from django.utils.safestring import mark_safe +from django.utils.translation import ugettext_lazy as _ from django.contrib.auth.models import User @@ -10,8 +11,9 @@ from symposion.teams.models import Membership class TeamInvitationForm(forms.Form): - email = forms.EmailField(help_text=("email address must be that of an account on this " - "conference site")) + email = forms.EmailField(label=_("Email"), + help_text=_("email address must be that of an account on this " + "conference site")) def __init__(self, *args, **kwargs): self.team = kwargs.pop("team") @@ -22,7 +24,7 @@ class TeamInvitationForm(forms.Form): email = cleaned_data.get("email") if email is None: - raise forms.ValidationError("valid email address required") + raise forms.ValidationError(_("valid email address required")) try: user = User.objects.get(email=email) @@ -30,16 +32,16 @@ class TeamInvitationForm(forms.Form): # eventually we can invite them but for now assume they are # already on the site raise forms.ValidationError( - mark_safe("no account with email address %s found on this conference " - "site" % escape(email))) + mark_safe(_("no account with email address %s found on this conference " + "site") % escape(email))) state = self.team.get_state_for_user(user) if state in ["member", "manager"]: - raise forms.ValidationError("user already in team") + raise forms.ValidationError(_("user already in team")) if state in ["invited"]: - raise forms.ValidationError("user already invited to team") + raise forms.ValidationError(_("user already invited to team")) self.user = user self.state = state diff --git a/symposion/teams/models.py b/symposion/teams/models.py index 8c344cc4..fbbc4c45 100644 --- a/symposion/teams/models.py +++ b/symposion/teams/models.py @@ -5,30 +5,36 @@ from django.db import models import reversion from django.contrib.auth.models import Permission, User +from django.utils.translation import ugettext_lazy as _ TEAM_ACCESS_CHOICES = [ - ("open", "open"), - ("application", "by application"), - ("invitation", "by invitation") + ("open", _("open")), + ("application", _("by application")), + ("invitation", _("by invitation")) ] class Team(models.Model): - slug = models.SlugField(unique=True) - name = models.CharField(max_length=100) - description = models.TextField(blank=True) - access = models.CharField(max_length=20, choices=TEAM_ACCESS_CHOICES) + slug = models.SlugField(unique=True, verbose_name=_("Slug")) + name = models.CharField(max_length=100, verbose_name=_("Name")) + description = models.TextField(blank=True, verbose_name=_("Description")) + access = models.CharField(max_length=20, choices=TEAM_ACCESS_CHOICES, + verbose_name=_("Access")) # member permissions - permissions = models.ManyToManyField(Permission, blank=True, related_name="member_teams") + permissions = models.ManyToManyField(Permission, blank=True, + related_name="member_teams", + verbose_name=_("Permissions")) # manager permissions manager_permissions = models.ManyToManyField(Permission, blank=True, - related_name="manager_teams") + related_name="manager_teams", + verbose_name=_("Manager permissions")) - created = models.DateTimeField(default=datetime.datetime.now, editable=False) + created = models.DateTimeField(default=datetime.datetime.now, + editable=False, verbose_name=_("Created")) @models.permalink def get_absolute_url(self): @@ -55,23 +61,32 @@ class Team(models.Model): def managers(self): return self.memberships.filter(state="manager") + class Meta: + verbose_name = _('Team') + verbose_name_plural = _('Teams') MEMBERSHIP_STATE_CHOICES = [ - ("applied", "applied"), - ("invited", "invited"), - ("declined", "declined"), - ("rejected", "rejected"), - ("member", "member"), - ("manager", "manager"), + ("applied", _("applied")), + ("invited", _("invited")), + ("declined", _("declined")), + ("rejected", _("rejected")), + ("member", _("member")), + ("manager", _("manager")), ] class Membership(models.Model): - user = models.ForeignKey(User, related_name="memberships") - team = models.ForeignKey(Team, related_name="memberships") - state = models.CharField(max_length=20, choices=MEMBERSHIP_STATE_CHOICES) - message = models.TextField(blank=True) + user = models.ForeignKey(User, related_name="memberships", + verbose_name=_("User")) + team = models.ForeignKey(Team, related_name="memberships", + verbose_name=_("Team")) + state = models.CharField(max_length=20, choices=MEMBERSHIP_STATE_CHOICES, + verbose_name=_("State")) + message = models.TextField(blank=True, verbose_name=_("Message")) + class Meta: + verbose_name = _("Membership") + verbose_name_plural = _("Memberships") reversion.register(Membership) diff --git a/symposion/teams/views.py b/symposion/teams/views.py index f45a0cba..a843be1c 100644 --- a/symposion/teams/views.py +++ b/symposion/teams/views.py @@ -5,6 +5,7 @@ from django.contrib.auth.decorators import login_required from django.contrib import messages from symposion.utils.mail import send_email +from django.utils.translation import ugettext_lazy as _ from symposion.teams.forms import TeamInvitationForm from symposion.teams.models import Team, Membership @@ -66,7 +67,7 @@ def team_detail(request, slug): if form.is_valid(): form.invite() send_email([form.user.email], "teams_user_invited", context={"team": team}) - messages.success(request, "Invitation created.") + messages.success(request, _("Invitation created.")) return redirect("team_detail", slug=slug) else: form = TeamInvitationForm(team=team) @@ -94,7 +95,7 @@ def team_join(request, slug): membership, created = Membership.objects.get_or_create(team=team, user=request.user) membership.state = "member" membership.save() - messages.success(request, "Joined team.") + messages.success(request, _("Joined team.")) return redirect("team_detail", slug=slug) else: return redirect("team_detail", slug=slug) @@ -110,7 +111,7 @@ def team_leave(request, slug): if can_leave(team, request.user) and request.method == "POST": membership = Membership.objects.get(team=team, user=request.user) membership.delete() - messages.success(request, "Left team.") + messages.success(request, _("Left team.")) return redirect("dashboard") else: return redirect("team_detail", slug=slug) @@ -132,7 +133,7 @@ def team_apply(request, slug): "team": team, "user": request.user }) - messages.success(request, "Applied to join team.") + messages.success(request, _("Applied to join team.")) return redirect("team_detail", slug=slug) else: return redirect("team_detail", slug=slug) @@ -148,7 +149,7 @@ def team_promote(request, pk): if membership.state == "member": membership.state = "manager" membership.save() - messages.success(request, "Promoted to manager.") + messages.success(request, _("Promoted to manager.")) return redirect("team_detail", slug=membership.team.slug) @@ -162,7 +163,7 @@ def team_demote(request, pk): if membership.state == "manager": membership.state = "member" membership.save() - messages.success(request, "Demoted from manager.") + messages.success(request, _("Demoted from manager.")) return redirect("team_detail", slug=membership.team.slug) @@ -176,7 +177,7 @@ def team_accept(request, pk): if membership.state == "applied": membership.state = "member" membership.save() - messages.success(request, "Accepted application.") + messages.success(request, _("Accepted application.")) return redirect("team_detail", slug=membership.team.slug) @@ -190,5 +191,5 @@ def team_reject(request, pk): if membership.state == "applied": membership.state = "rejected" membership.save() - messages.success(request, "Rejected application.") + messages.success(request, _("Rejected application.")) return redirect("team_detail", slug=membership.team.slug) From 1b51ef2ad8bd05113cdd986b58bc9a04695e54f3 Mon Sep 17 00:00:00 2001 From: Tzu-ping Chung Date: Tue, 4 Aug 2015 13:34:44 +0800 Subject: [PATCH 617/751] Replace django-timezones w/ django-timezone-field django-timezones does not support Python 3. django-timezone-field is a revived fork that does. For some unknown reason django-timezone-field's TimeZoneField does not like positional arguments, so I changed to first argument to a kwarg "verbose_name". --- requirements/base.txt | 2 +- symposion/conference/models.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements/base.txt b/requirements/base.txt index 8cd92478..b60e073d 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -5,7 +5,7 @@ django-model-utils==2.2 django-reversion==1.8.5 django-sitetree==1.2.1 django-taggit==0.12.2 -django-timezones==0.2 +django-timezone-field==1.2 django-user-accounts==1.0 easy-thumbnails==2.2 html5lib==0.999 diff --git a/symposion/conference/models.py b/symposion/conference/models.py index 582035b5..95a9e504 100644 --- a/symposion/conference/models.py +++ b/symposion/conference/models.py @@ -1,7 +1,7 @@ from django.db import models from django.utils.translation import ugettext_lazy as _ -from timezones.fields import TimeZoneField +from timezone_field import TimeZoneField CONFERENCE_CACHE = {} @@ -19,7 +19,7 @@ class Conference(models.Model): end_date = models.DateField(_("end date"), null=True, blank=True) # timezone the conference is in - timezone = TimeZoneField(_("timezone"), blank=True) + timezone = TimeZoneField(blank=True, verbose_name=_("timezone")) def __unicode__(self): return self.title From e0482190be34cd57b6c150ba899a373c3990559f Mon Sep 17 00:00:00 2001 From: Anna Ossowski Date: Thu, 6 Aug 2015 18:20:46 +0200 Subject: [PATCH 618/751] Update README.rst --- README.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.rst b/README.rst index ebb3c6de..794b2ec6 100644 --- a/README.rst +++ b/README.rst @@ -1,6 +1,9 @@ Symposion --------- +.. image:: http://slack.pinaxproject.com/badge.svg + :target: http://slack.pinaxproject.com/ + .. image:: https://img.shields.io/travis/pinax/symposion.svg :target: https://travis-ci.org/pinax/symposion From 5c4b92cf5ab74c7c283b21d5a7a3418e352a182a Mon Sep 17 00:00:00 2001 From: Anna Ossowski Date: Wed, 19 Aug 2015 17:42:58 +0200 Subject: [PATCH 619/751] Update README.rst --- README.rst | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 794b2ec6..595a8a6f 100644 --- a/README.rst +++ b/README.rst @@ -21,12 +21,21 @@ Symposion -A conference management solution from Eldarion. +symposion is a conference management solution from Eldarion. -Built with the generous support of the Python Software Foundation. +It was built with the generous support of the Python Software Foundation. See http://eldarion.com/symposion/ for commercial support, customization and hosting +Pinax is an open-source platform built on the Django Web Framework. It is an ecosystem of reusable Django apps, themes, and starter project templates. +This collection can be found at http://pinaxproject.com. + +In order to foster a kind, inclusive, and harassment-free community, the Pinax Project has a code of conduct, which can be found here http://pinaxproject.com/pinax/code_of_conduct/. + +The Pinax documentation is available at http://pinaxproject.com/pinax/. + +For updates and news regarding the Pinax Project, please follow us on Twitter at @pinaxproject and check out our blog http://blog.pinaxproject.com. + Quickstart ========== From b5cf9f1541f24fd90f5a0144ca760699484ebcd4 Mon Sep 17 00:00:00 2001 From: Anna Ossowski Date: Fri, 28 Aug 2015 14:41:11 +0200 Subject: [PATCH 620/751] Update README.rst --- README.rst | 35 ++++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/README.rst b/README.rst index 595a8a6f..d2b22367 100644 --- a/README.rst +++ b/README.rst @@ -20,21 +20,18 @@ Symposion :target: https://pypi.python.org/pypi/symposion/ - -symposion is a conference management solution from Eldarion. - -It was built with the generous support of the Python Software Foundation. - -See http://eldarion.com/symposion/ for commercial support, customization and hosting +Pinax +------ Pinax is an open-source platform built on the Django Web Framework. It is an ecosystem of reusable Django apps, themes, and starter project templates. This collection can be found at http://pinaxproject.com. -In order to foster a kind, inclusive, and harassment-free community, the Pinax Project has a code of conduct, which can be found here http://pinaxproject.com/pinax/code_of_conduct/. -The Pinax documentation is available at http://pinaxproject.com/pinax/. +symposion +---------- + +symposion is a conference management solution from Eldarion. It was built with the generous support of the Python Software Foundation. See http://eldarion.com/symposion/ for commercial support, customization and hosting. -For updates and news regarding the Pinax Project, please follow us on Twitter at @pinaxproject and check out our blog http://blog.pinaxproject.com. Quickstart ========== @@ -48,3 +45,23 @@ customize and manage your Symposion installation. We have built a [basic Django startproject template that includes Symposion][1]. [1]: https://github.com/pinax/pinax-project-symposion + + +Documentation +--------------- + +The Pinax documentation is available at http://pinaxproject.com/pinax/. + + +Code of Conduct +---------------- + +In order to foster a kind, inclusive, and harassment-free community, the Pinax Project has a code of conduct, which can be found here http://pinaxproject.com/pinax/code_of_conduct/. + + +Pinax Project Blog and Twitter +------------------------------- + +For updates and news regarding the Pinax Project, please follow us on Twitter at @pinaxproject and check out our blog http://blog.pinaxproject.com. + + From 6dd80f9ddb6fc135c8c2a6fc36b343066d428516 Mon Sep 17 00:00:00 2001 From: Patrick Altman Date: Sun, 13 Sep 2015 21:34:40 -0500 Subject: [PATCH 621/751] Fix some flake8 issues --- symposion/schedule/admin.py | 1 + symposion/schedule/tests/factories.py | 6 ++++-- symposion/sponsorship/admin.py | 4 +++- symposion/sponsorship/models.py | 10 +++++++--- 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/symposion/schedule/admin.py b/symposion/schedule/admin.py index 41a6e697..69f16be0 100644 --- a/symposion/schedule/admin.py +++ b/symposion/schedule/admin.py @@ -28,6 +28,7 @@ class SlotAdmin(admin.ModelAdmin): list_display = ("day", "start", "end", "kind", "content") inlines = [SlotRoomInline] + class RoomAdmin(admin.ModelAdmin): list_display = ["name", "order", "schedule"] list_filter = ["schedule"] diff --git a/symposion/schedule/tests/factories.py b/symposion/schedule/tests/factories.py index ccddb58c..6e24846b 100644 --- a/symposion/schedule/tests/factories.py +++ b/symposion/schedule/tests/factories.py @@ -2,6 +2,7 @@ import datetime import random import factory + from factory import fuzzy from symposion.schedule.models import Schedule, Day, Slot, SlotKind @@ -11,8 +12,9 @@ from symposion.conference.models import Section, Conference class ConferenceFactory(factory.DjangoModelFactory): title = fuzzy.FuzzyText() start_date = fuzzy.FuzzyDate(datetime.date(2014, 1, 1)) - end_date = fuzzy.FuzzyDate(datetime.date(2014, 1, 1) - + datetime.timedelta(days=random.randint(1, 10))) + end_date = fuzzy.FuzzyDate( + datetime.date(2014, 1, 1) + datetime.timedelta(days=random.randint(1, 10)) + ) # timezone = TimeZoneField("UTC") class Meta: diff --git a/symposion/sponsorship/admin.py b/symposion/sponsorship/admin.py index 39f66f80..15b09439 100644 --- a/symposion/sponsorship/admin.py +++ b/symposion/sponsorship/admin.py @@ -1,10 +1,11 @@ from __future__ import unicode_literals -from django.contrib import admin from django.utils.html import escape from django.utils.safestring import mark_safe from django.utils.translation import ugettext_lazy as _ +from django.contrib import admin + from symposion.sponsorship.models import ( Benefit, BENEFITS, @@ -14,6 +15,7 @@ from symposion.sponsorship.models import ( SponsorLevel, ) + class BenefitLevelInline(admin.TabularInline): model = BenefitLevel extra = 0 diff --git a/symposion/sponsorship/models.py b/symposion/sponsorship/models.py index 20d4de09..30a086ff 100644 --- a/symposion/sponsorship/models.py +++ b/symposion/sponsorship/models.py @@ -314,10 +314,14 @@ class SponsorBenefit(models.Model): return ["text"] return [] + def _is_text_benefit(self): + return self.benefit.type in ["text", "richtext", "simple"] and bool(self.text) + + def _is_upload_benefit(self): + return self.benefit.type in ["file", "weblogo"] and bool(self.upload) + def _is_complete(self): - return self.active and \ - ((self.benefit.type in ('text', 'richtext', 'simple') and bool(self.text)) - or (self.benefit.type in ('file', 'weblogo') and bool(self.upload))) + return self.active and (self._is_text_benefit() or self._is_upload_benefit()) def _denorm_weblogo(sender, instance, created, **kwargs): From ba0bb84ad70b45be40ccaacb353b5bbf46c65b34 Mon Sep 17 00:00:00 2001 From: Patrick Altman Date: Sun, 13 Sep 2015 21:40:28 -0500 Subject: [PATCH 622/751] Use django-boxes instead --- docs/content.rst | 15 ------- symposion/boxes/__init__.py | 3 -- symposion/boxes/admin.py | 12 ------ symposion/boxes/authorization.py | 20 ---------- symposion/boxes/forms.py | 10 ----- symposion/boxes/models.py | 28 ------------- symposion/boxes/templatetags/__init__.py | 0 symposion/boxes/templatetags/boxes_tags.py | 36 ----------------- symposion/boxes/urls.py | 7 ---- symposion/boxes/utils.py | 19 --------- symposion/boxes/views.py | 46 ---------------------- 11 files changed, 196 deletions(-) delete mode 100644 symposion/boxes/__init__.py delete mode 100644 symposion/boxes/admin.py delete mode 100644 symposion/boxes/authorization.py delete mode 100644 symposion/boxes/forms.py delete mode 100644 symposion/boxes/models.py delete mode 100644 symposion/boxes/templatetags/__init__.py delete mode 100644 symposion/boxes/templatetags/boxes_tags.py delete mode 100644 symposion/boxes/urls.py delete mode 100644 symposion/boxes/utils.py delete mode 100644 symposion/boxes/views.py diff --git a/docs/content.rst b/docs/content.rst index 59c0b3ef..e7de2376 100644 --- a/docs/content.rst +++ b/docs/content.rst @@ -23,18 +23,3 @@ specifying: Page content and title can also be edited directly at the url. The ``cms`` app uses the `django-reversion `_ package, thus content is version controlled. - -Boxes App ---------- - -The ``boxes`` app allows for sections of a page to be edited like a wiki. To use this in a template -use the ``boxes_tags`` and specify a box section of a page using the ``boxes`` tag: - -.. code-block:: django - - {% load boxes_tags %} - {% boxes "intro_section" %} - -This template will render an editable content box. When a staff user visits the -page, they will see an ``Edit this content`` button. The ``boxes`` app also uses the -``django-reversion`` package. diff --git a/symposion/boxes/__init__.py b/symposion/boxes/__init__.py deleted file mode 100644 index 8a6e37e9..00000000 --- a/symposion/boxes/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# @@@ Reconcile differences with django-boxes [1] and remove from Symposion - -# [1] http://github.com/eldarion/django-boxes/ diff --git a/symposion/boxes/admin.py b/symposion/boxes/admin.py deleted file mode 100644 index 189db64b..00000000 --- a/symposion/boxes/admin.py +++ /dev/null @@ -1,12 +0,0 @@ -from django.contrib import admin - -import reversion - -from symposion.boxes.models import Box - - -class BoxAdmin(reversion.VersionAdmin): - - pass - -admin.site.register(Box, BoxAdmin) diff --git a/symposion/boxes/authorization.py b/symposion/boxes/authorization.py deleted file mode 100644 index e898b0cf..00000000 --- a/symposion/boxes/authorization.py +++ /dev/null @@ -1,20 +0,0 @@ -from django.conf import settings - -from symposion.boxes.utils import load_path_attr - - -def default_can_edit(request, *args, **kwargs): - """ - This is meant to be overridden in your project per domain specific - requirements. - """ - return request.user.is_staff or request.user.is_superuser - - -def load_can_edit(): - import_path = getattr(settings, "BOXES_CAN_EDIT_CALLABLE", None) - - if import_path is None: - return default_can_edit - - return load_path_attr(import_path) diff --git a/symposion/boxes/forms.py b/symposion/boxes/forms.py deleted file mode 100644 index 7f881acc..00000000 --- a/symposion/boxes/forms.py +++ /dev/null @@ -1,10 +0,0 @@ -from django import forms - -from symposion.boxes.models import Box - - -class BoxForm(forms.ModelForm): - - class Meta: - model = Box - fields = ["content"] diff --git a/symposion/boxes/models.py b/symposion/boxes/models.py deleted file mode 100644 index 65e38d7b..00000000 --- a/symposion/boxes/models.py +++ /dev/null @@ -1,28 +0,0 @@ -from __future__ import unicode_literals -from django.db import models -from django.contrib.auth.models import User -from django.utils.encoding import python_2_unicode_compatible -from django.utils.translation import ugettext_lazy as _ - -import reversion - -from markitup.fields import MarkupField - - -@python_2_unicode_compatible -class Box(models.Model): - - label = models.CharField(max_length=100, db_index=True, verbose_name=_("Label")) - content = MarkupField(blank=True) - - created_by = models.ForeignKey(User, related_name="boxes", verbose_name=_("Created by")) - last_updated_by = models.ForeignKey(User, related_name="updated_boxes", verbose_name=_("Last updated by")) - - def __str__(self): - return self.label - - class Meta: - verbose_name = _("Box") - verbose_name_plural = _("Boxes") - -reversion.register(Box) diff --git a/symposion/boxes/templatetags/__init__.py b/symposion/boxes/templatetags/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/symposion/boxes/templatetags/boxes_tags.py b/symposion/boxes/templatetags/boxes_tags.py deleted file mode 100644 index 53765e56..00000000 --- a/symposion/boxes/templatetags/boxes_tags.py +++ /dev/null @@ -1,36 +0,0 @@ -from django import template -from django.core.urlresolvers import reverse - -from symposion.boxes.models import Box -from symposion.boxes.forms import BoxForm -from symposion.boxes.authorization import load_can_edit - - -register = template.Library() - - -@register.inclusion_tag("boxes/box.html", takes_context=True) -def box(context, label, show_edit=True, *args, **kwargs): - - request = context["request"] - can_edit = load_can_edit()(request, *args, **kwargs) - - try: - box = Box.objects.get(label=label) - except Box.DoesNotExist: - box = None - - if can_edit and show_edit: - form = BoxForm(instance=box, prefix=label) - form_action = reverse("box_edit", args=[label]) - else: - form = None - form_action = None - - return { - "request": request, - "label": label, - "box": box, - "form": form, - "form_action": form_action, - } diff --git a/symposion/boxes/urls.py b/symposion/boxes/urls.py deleted file mode 100644 index 96464b66..00000000 --- a/symposion/boxes/urls.py +++ /dev/null @@ -1,7 +0,0 @@ -from django.conf.urls import patterns, url - - -urlpatterns = patterns( - "symposion.boxes.views", - url(r"^([-\w]+)/edit/$", "box_edit", name="box_edit"), -) diff --git a/symposion/boxes/utils.py b/symposion/boxes/utils.py deleted file mode 100644 index 3d2b4c76..00000000 --- a/symposion/boxes/utils.py +++ /dev/null @@ -1,19 +0,0 @@ -from django.core.exceptions import ImproperlyConfigured -try: - from django.utils.importlib import import_module -except ImportError: - from importlib import import_module - - -def load_path_attr(path): - i = path.rfind(".") - module, attr = path[:i], path[i + 1:] - try: - mod = import_module(module) - except ImportError, e: - raise ImproperlyConfigured("Error importing %s: '%s'" % (module, e)) - try: - attr = getattr(mod, attr) - except AttributeError: - raise ImproperlyConfigured("Module '%s' does not define a '%s'" % (module, attr)) - return attr diff --git a/symposion/boxes/views.py b/symposion/boxes/views.py deleted file mode 100644 index 1e93675d..00000000 --- a/symposion/boxes/views.py +++ /dev/null @@ -1,46 +0,0 @@ -from django.http import HttpResponseForbidden -from django.shortcuts import redirect -from django.views.decorators.http import require_POST - -from symposion.boxes.authorization import load_can_edit -from symposion.boxes.forms import BoxForm -from symposion.boxes.models import Box - - -# @@@ problem with this is that the box_edit.html and box_create.html won't have domain objects in -# context -def get_auth_vars(request): - auth_vars = {} - if request.method == "POST": - keys = [k for k in request.POST.keys() if k.startswith("boxes_auth_")] - for key in keys: - auth_vars[key.replace("boxes_auth_", "")] = request.POST.get(key) - auth_vars["user"] = request.user - return auth_vars - - -@require_POST -def box_edit(request, label): - - if not load_can_edit()(request, **get_auth_vars(request)): - return HttpResponseForbidden() - - next = request.GET.get("next") - - try: - box = Box.objects.get(label=label) - except Box.DoesNotExist: - box = None - - form = BoxForm(request.POST, instance=box, prefix=label) - - if form.is_valid(): - if box is None: - box = form.save(commit=False) - box.label = label - box.created_by = request.user - box.last_updated_by = request.user - box.save() - else: - form.save() - return redirect(next) From a3c89ece12fa72602b0ae84243c4fd68bb360c33 Mon Sep 17 00:00:00 2001 From: Patrick Altman Date: Sun, 13 Sep 2015 21:43:02 -0500 Subject: [PATCH 623/751] Use pinax-pages instead --- docs/content.rst | 25 -------- symposion/cms/__init__.py | 2 - symposion/cms/admin.py | 13 ---- symposion/cms/apps.py | 7 -- symposion/cms/forms.py | 21 ------ symposion/cms/managers.py | 10 --- symposion/cms/models.py | 79 ----------------------- symposion/cms/urls.py | 14 ---- symposion/cms/views.py | 130 -------------------------------------- 9 files changed, 301 deletions(-) delete mode 100644 docs/content.rst delete mode 100644 symposion/cms/__init__.py delete mode 100644 symposion/cms/admin.py delete mode 100644 symposion/cms/apps.py delete mode 100644 symposion/cms/forms.py delete mode 100644 symposion/cms/managers.py delete mode 100644 symposion/cms/models.py delete mode 100644 symposion/cms/urls.py delete mode 100644 symposion/cms/views.py diff --git a/docs/content.rst b/docs/content.rst deleted file mode 100644 index e7de2376..00000000 --- a/docs/content.rst +++ /dev/null @@ -1,25 +0,0 @@ -Content Management -================== - -The content management system allows organizers to create pages and page -sections for a conference. You may want to have an entire page about a job -fair, or may only want to have an editable section at the top of a tutorial -schedule with some instructions for all of the tutorial attendees. - -CMS App -------- - -The ``cms`` app provides functionality for creating wiki pages. These pages can -be created using the django admin. The django admin form has controls for -specifying: - -* title -* markup content -* url path -* tags -* public or draft mode -* publication date - -Page content and title can also be edited directly at the url. The ``cms`` app -uses the `django-reversion `_ package, -thus content is version controlled. diff --git a/symposion/cms/__init__.py b/symposion/cms/__init__.py deleted file mode 100644 index 010e607a..00000000 --- a/symposion/cms/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -# @@@ Consider replacing with pinax-wiki [1] -default_app_config = "symposion.cms.apps.CMSConfig" diff --git a/symposion/cms/admin.py b/symposion/cms/admin.py deleted file mode 100644 index 83b4c176..00000000 --- a/symposion/cms/admin.py +++ /dev/null @@ -1,13 +0,0 @@ -from django.contrib import admin - -import reversion - -from .models import Page - - -class PageAdmin(reversion.VersionAdmin): - - pass - - -admin.site.register(Page, PageAdmin) diff --git a/symposion/cms/apps.py b/symposion/cms/apps.py deleted file mode 100644 index d506db12..00000000 --- a/symposion/cms/apps.py +++ /dev/null @@ -1,7 +0,0 @@ -from django.apps import AppConfig - - -class CMSConfig(AppConfig): - name = "symposion.cms" - label = "symposion_cms" - verbose_name = "Symposion CMS" diff --git a/symposion/cms/forms.py b/symposion/cms/forms.py deleted file mode 100644 index 5eecca31..00000000 --- a/symposion/cms/forms.py +++ /dev/null @@ -1,21 +0,0 @@ -from django import forms - -from markitup.widgets import MarkItUpWidget - -from .models import Page - - -class PageForm(forms.ModelForm): - - class Meta: - model = Page - fields = ["title", "body", "path"] - widgets = { - "body": MarkItUpWidget(), - "path": forms.HiddenInput(), - } - - -class FileUploadForm(forms.Form): - - file = forms.FileField() diff --git a/symposion/cms/managers.py b/symposion/cms/managers.py deleted file mode 100644 index 9ffb6592..00000000 --- a/symposion/cms/managers.py +++ /dev/null @@ -1,10 +0,0 @@ -from django.utils import timezone - -from django.db import models - - -class PublishedPageManager(models.Manager): - - def get_queryset(self): - qs = super(PublishedPageManager, self).get_queryset() - return qs.filter(publish_date__lte=timezone.now()) diff --git a/symposion/cms/models.py b/symposion/cms/models.py deleted file mode 100644 index 9c8ce003..00000000 --- a/symposion/cms/models.py +++ /dev/null @@ -1,79 +0,0 @@ -import datetime -import os -import re - -from django.conf import settings -from django.core.urlresolvers import reverse -from django.core.exceptions import ValidationError -from django.db import models -from django.utils.encoding import python_2_unicode_compatible -from django.utils.translation import ugettext_lazy as _ - -from markitup.fields import MarkupField - -from taggit.managers import TaggableManager - -import reversion - -from .managers import PublishedPageManager - - -@python_2_unicode_compatible -class Page(models.Model): - - STATUS_CHOICES = ( - (1, _("Draft")), - (2, _("Public")), - ) - - title = models.CharField(max_length=100) - path = models.CharField(max_length=100, unique=True) - body = MarkupField() - status = models.IntegerField(choices=STATUS_CHOICES, default=2) - publish_date = models.DateTimeField(default=datetime.datetime.now) - created = models.DateTimeField(editable=False, default=datetime.datetime.now) - updated = models.DateTimeField(editable=False, default=datetime.datetime.now) - tags = TaggableManager(blank=True) - - published = PublishedPageManager() - - def __str__(self): - return self.title - - class Meta: - verbose_name = _("page") - verbose_name_plural = _("pages") - - @models.permalink - def get_absolute_url(self): - return ("cms_page", [self.path]) - - @property - def is_community(self): - return self.path.lower().startswith("community/") - - def save(self, *args, **kwargs): - self.updated = datetime.datetime.now() - super(Page, self).save(*args, **kwargs) - - def clean_fields(self, exclude=None): - super(Page, self).clean_fields(exclude) - if not re.match(settings.SYMPOSION_PAGE_REGEX, self.path): - raise ValidationError( - {"path": [_("Path can only contain letters, numbers and hyphens and end with /")]}) - - -reversion.register(Page) - - -def generate_filename(instance, filename): - return filename - - -class File(models.Model): - - file = models.FileField(upload_to=generate_filename) - created = models.DateTimeField(default=datetime.datetime.now) - - def download_url(self): - return reverse("file_download", args=[self.pk, os.path.basename(self.file.name).lower()]) diff --git a/symposion/cms/urls.py b/symposion/cms/urls.py deleted file mode 100644 index af3ef981..00000000 --- a/symposion/cms/urls.py +++ /dev/null @@ -1,14 +0,0 @@ -from django.conf.urls import url, patterns - - -PAGE_RE = r"(([\w-]{1,})(/[\w-]{1,})*)/" - -urlpatterns = patterns( - "symposion.cms.views", - url(r"^files/$", "file_index", name="file_index"), - url(r"^files/create/$", "file_create", name="file_create"), - url(r"^files/(\d+)/([^/]+)$", "file_download", name="file_download"), - url(r"^files/(\d+)/delete/$", "file_delete", name="file_delete"), - url(r"^(?P%s)_edit/$" % PAGE_RE, "page_edit", name="cms_page_edit"), - url(r"^(?P%s)$" % PAGE_RE, "page", name="cms_page"), -) diff --git a/symposion/cms/views.py b/symposion/cms/views.py deleted file mode 100644 index 2f58381a..00000000 --- a/symposion/cms/views.py +++ /dev/null @@ -1,130 +0,0 @@ -from django.conf import settings -from django.db import transaction -from django.http import Http404, HttpResponse -from django.shortcuts import render, redirect, get_object_or_404 -from django.views import static - -from django.contrib.auth.decorators import login_required -from .models import Page, File -from .forms import PageForm, FileUploadForm - - -def can_edit(page, user): - if page and page.is_community: - return True - else: - return user.has_perm("cms.change_page") - - -def can_upload(user): - if user.is_staff or user.is_superuser: - return True - return False - - -def page(request, path): - - try: - page = Page.published.get(path=path) - except Page.DoesNotExist: - page = None - - editable = can_edit(page, request.user) - - if page is None: - if editable: - return redirect("cms_page_edit", path=path) - else: - raise Http404 - - return render(request, "cms/page_detail.html", { - "page": page, - "editable": editable, - }) - - -@login_required -def page_edit(request, path): - - try: - page = Page.published.get(path=path) - except Page.DoesNotExist: - page = None - - if not can_edit(page, request.user): - raise Http404 - - if request.method == "POST": - form = PageForm(request.POST, instance=page) - if form.is_valid(): - page = form.save(commit=False) - page.path = path - page.save() - return redirect(page) - else: - print form.errors - else: - form = PageForm(instance=page, initial={"path": path}) - - return render(request, "cms/page_edit.html", { - "path": path, - "form": form - }) - - -def file_index(request): - if not can_upload(request.user): - raise Http404 - - ctx = { - "files": File.objects.all(), - } - return render(request, "cms/file_index.html", ctx) - - -def file_create(request): - if not can_upload(request.user): - raise Http404 - - if request.method == "POST": - form = FileUploadForm(request.POST, request.FILES) - if form.is_valid(): - with transaction.commit_on_success(): - kwargs = { - "file": form.cleaned_data["file"], - } - File.objects.create(**kwargs) - return redirect("file_index") - else: - form = FileUploadForm() - - ctx = { - "form": form, - } - return render(request, "cms/file_create.html", ctx) - - -def file_download(request, pk, *args): - file = get_object_or_404(File, pk=pk) - - if getattr(settings, "USE_X_ACCEL_REDIRECT", False): - response = HttpResponse() - response["X-Accel-Redirect"] = file.file.url - # delete content-type to allow Gondor to determine the filetype and - # we definitely don't want Django's default :-) - del response["content-type"] - else: - response = static.serve(request, file.file.name, document_root=settings.MEDIA_ROOT) - - return response - - -def file_delete(request, pk): - if not can_upload(request.user): - raise Http404 - - file = get_object_or_404(File, pk=pk) - if request.method == "POST": - file.delete() - # @@@ message - return redirect("file_index") From c0402575475e481d9d4dfdc64bf6f9e0cf90ad2b Mon Sep 17 00:00:00 2001 From: Hiroshi Miura Date: Sat, 25 Jul 2015 15:28:09 +0900 Subject: [PATCH 624/751] Slot: gen name retrun by __str__() when save() This name also used for itemize slots on admin screen Signed-off-by: Hiroshi Miura --- symposion/schedule/models.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/symposion/schedule/models.py b/symposion/schedule/models.py index 22aad378..487f2063 100644 --- a/symposion/schedule/models.py +++ b/symposion/schedule/models.py @@ -83,6 +83,7 @@ class SlotKind(models.Model): @python_2_unicode_compatible class Slot(models.Model): + name = models.CharField(max_length=100, editable=False) day = models.ForeignKey(Day, verbose_name=_("Day")) kind = models.ForeignKey(SlotKind, verbose_name=_("Kind")) start = models.TimeField(verbose_name=_("Start")) @@ -145,9 +146,13 @@ class Slot(models.Model): def rooms(self): return Room.objects.filter(pk__in=self.slotroom_set.values("room")) - def __str__(self): + def save(self, *args, **kwargs): roomlist = ' '.join(map(lambda r: r.__unicode__(), self.rooms)) - return "%s %s (%s - %s) %s" % (self.day, self.kind, self.start, self.end, roomlist) + self.name = "%s %s (%s - %s) %s" % (self.day, self.kind, self.start, self.end, roomlist) + super(Slot, self).save(*args, **kwargs) + + def __str__(self): + return self.name class Meta: ordering = ["day", "start", "end"] From 8b4282a48e8815b85b9ecbce82463251a7b2ca8c Mon Sep 17 00:00:00 2001 From: Anna Ossowski Date: Wed, 7 Oct 2015 12:17:36 +0200 Subject: [PATCH 625/751] Update README.rst --- README.rst | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/README.rst b/README.rst index d2b22367..8f3cd683 100644 --- a/README.rst +++ b/README.rst @@ -41,10 +41,8 @@ To install Symposion, run: pip install symposion Symposion is a Django app. You will need to create a Django project to -customize and manage your Symposion installation. We have built a [basic -Django startproject template that includes Symposion][1]. - -[1]: https://github.com/pinax/pinax-project-symposion +customize and manage your Symposion installation. We have built a basic +Django startproject template that includes Symposion (https://github.com/pinax/pinax-project-symposion). Documentation From 11f697d13757be5505898ae2f5444c394ab3b5ae Mon Sep 17 00:00:00 2001 From: Patrick Altman Date: Fri, 16 Oct 2015 12:36:58 -0500 Subject: [PATCH 626/751] Massively upgrade symposion * Remove markitup (to be replaced with Ace editor) * Use DUA decorators * Removed custom signup bits * Upgraded dependencies * Added migrations * Namespaced template locations * Removed html5parser/sanitizer (for now) - parsing functionality should be moved out entirely to a hooks * Replaced ProposalScoreExpression object with a function that returns F() expressions --- requirements/base.txt | 23 ++- .../conference/migrations/0001_initial.py | 44 +++++ symposion/conference/migrations/__init__.py | 0 symposion/conference/views.py | 5 +- symposion/forms.py | 58 ------ symposion/markdown_parser.py | 11 +- symposion/proposals/forms.py | 1 - .../proposals/migrations/0001_initial.py | 100 ++++++++++ symposion/proposals/migrations/__init__.py | 0 symposion/proposals/models.py | 20 +- symposion/proposals/views.py | 23 +-- symposion/reviews/forms.py | 5 - symposion/reviews/migrations/0001_initial.py | 143 ++++++++++++++ symposion/reviews/migrations/__init__.py | 0 symposion/reviews/models.py | 40 ++-- symposion/reviews/views.py | 26 +-- symposion/schedule/forms.py | 3 - symposion/schedule/migrations/0001_initial.py | 186 ++++++++++++++++++ symposion/schedule/migrations/__init__.py | 0 symposion/schedule/models.py | 21 +- symposion/schedule/tests/runtests.py | 3 - symposion/schedule/views.py | 23 +-- symposion/speakers/forms.py | 5 - symposion/speakers/migrations/0001_initial.py | 36 ++++ symposion/speakers/migrations/__init__.py | 0 symposion/speakers/models.py | 9 +- symposion/speakers/views.py | 18 +- .../sponsorship/migrations/0001_initial.py | 115 +++++++++++ symposion/sponsorship/migrations/__init__.py | 0 symposion/sponsorship/urls.py | 2 +- symposion/sponsorship/views.py | 9 +- symposion/teams/migrations/0001_initial.py | 56 ++++++ symposion/teams/migrations/__init__.py | 0 symposion/teams/views.py | 8 +- symposion/utils/mail.py | 4 +- symposion/views.py | 46 +---- 36 files changed, 821 insertions(+), 222 deletions(-) create mode 100644 symposion/conference/migrations/0001_initial.py create mode 100644 symposion/conference/migrations/__init__.py delete mode 100644 symposion/forms.py create mode 100644 symposion/proposals/migrations/0001_initial.py create mode 100644 symposion/proposals/migrations/__init__.py create mode 100644 symposion/reviews/migrations/0001_initial.py create mode 100644 symposion/reviews/migrations/__init__.py create mode 100644 symposion/schedule/migrations/0001_initial.py create mode 100644 symposion/schedule/migrations/__init__.py create mode 100644 symposion/speakers/migrations/0001_initial.py create mode 100644 symposion/speakers/migrations/__init__.py create mode 100644 symposion/sponsorship/migrations/0001_initial.py create mode 100644 symposion/sponsorship/migrations/__init__.py create mode 100644 symposion/teams/migrations/0001_initial.py create mode 100644 symposion/teams/migrations/__init__.py diff --git a/requirements/base.txt b/requirements/base.txt index b60e073d..4965ead4 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -1,13 +1,12 @@ -Django>=1.7.1 -django-appconf==0.6 -django-markitup==2.2.2 -django-model-utils==2.2 -django-reversion==1.8.5 -django-sitetree==1.2.1 -django-taggit==0.12.2 -django-timezone-field==1.2 -django-user-accounts==1.0 +Django==1.8.5 +django-appconf==1.0.1 +django-model-utils==2.3.1 +django-reversion==1.9.3 +django-sitetree==1.4.0 +django-taggit==0.17.1 +django-timezone-field==1.3 +django-user-accounts==1.2.0 easy-thumbnails==2.2 -html5lib==0.999 -markdown==2.5.2 -pytz==2014.10 +html5lib==0.9999999 +markdown==2.6.2 +pytz==2015.6 diff --git a/symposion/conference/migrations/0001_initial.py b/symposion/conference/migrations/0001_initial.py new file mode 100644 index 00000000..acc4a962 --- /dev/null +++ b/symposion/conference/migrations/0001_initial.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations +import timezone_field.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Conference', + fields=[ + ('id', models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(max_length=100, verbose_name='Title')), + ('start_date', models.DateField(null=True, blank=True, verbose_name='Start date')), + ('end_date', models.DateField(null=True, blank=True, verbose_name='End date')), + ('timezone', timezone_field.fields.TimeZoneField(blank=True, verbose_name='timezone')), + ], + options={ + 'verbose_name_plural': 'conferences', + 'verbose_name': 'conference', + }, + ), + migrations.CreateModel( + name='Section', + fields=[ + ('id', models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=100, verbose_name='Name')), + ('slug', models.SlugField(verbose_name='Slug')), + ('start_date', models.DateField(null=True, blank=True, verbose_name='Start date')), + ('end_date', models.DateField(null=True, blank=True, verbose_name='End date')), + ('conference', models.ForeignKey(to='symposion_conference.Conference', verbose_name='Conference')), + ], + options={ + 'ordering': ['start_date'], + 'verbose_name_plural': 'sections', + 'verbose_name': 'section', + }, + ), + ] diff --git a/symposion/conference/migrations/__init__.py b/symposion/conference/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/symposion/conference/views.py b/symposion/conference/views.py index 3be21e31..7b107a7e 100644 --- a/symposion/conference/views.py +++ b/symposion/conference/views.py @@ -1,9 +1,10 @@ from django.http import Http404 from django.shortcuts import render -from django.contrib.auth.decorators import login_required from django.contrib.auth.models import User +from account.decorators import login_required + @login_required def user_list(request): @@ -11,6 +12,6 @@ def user_list(request): if not request.user.is_staff: raise Http404() - return render(request, "conference/user_list.html", { + return render(request, "symposion/conference/user_list.html", { "users": User.objects.all(), }) diff --git a/symposion/forms.py b/symposion/forms.py deleted file mode 100644 index bdaf89ae..00000000 --- a/symposion/forms.py +++ /dev/null @@ -1,58 +0,0 @@ -try: - from collections import OrderedDict -except ImportError: - OrderedDict = None - -from django import forms - -import account.forms -from django.utils.translation import ugettext_lazy as _ - - -class SignupForm(account.forms.SignupForm): - - first_name = forms.CharField(label=_("First name")) - last_name = forms.CharField(label=_("Last name")) - email_confirm = forms.EmailField(label=_("Confirm Email")) - - def __init__(self, *args, **kwargs): - super(SignupForm, self).__init__(*args, **kwargs) - key_order = [ - "email", - "email_confirm", - "first_name", - "last_name", - "password", - "password_confirm" - ] - self.fields = reorder_fields(self.fields, key_order) - - def clean_email_confirm(self): - email = self.cleaned_data.get("email") - email_confirm = self.cleaned_data["email_confirm"] - if email: - if email != email_confirm: - raise forms.ValidationError( - _("Email address must match previously typed email address")) - return email_confirm - - -def reorder_fields(fields, order): - """Reorder form fields by order, removing items not in order. - - >>> reorder_fields( - ... OrderedDict([('a', 1), ('b', 2), ('c', 3)]), - ... ['b', 'c', 'a']) - OrderedDict([('b', 2), ('c', 3), ('a', 1)]) - """ - for key, v in fields.items(): - if key not in order: - del fields[key] - - if not OrderedDict or hasattr(fields, "keyOrder"): - # fields is SortedDict - fields.keyOrder.sort(key=lambda k: order.index(k[0])) - return fields - else: - # fields is OrderedDict - return OrderedDict(sorted(fields.items(), key=lambda k: order.index(k[0]))) diff --git a/symposion/markdown_parser.py b/symposion/markdown_parser.py index f512a854..22dc240c 100644 --- a/symposion/markdown_parser.py +++ b/symposion/markdown_parser.py @@ -10,8 +10,9 @@ def parse(text): text = markdown.markdown(text, extensions=["extra"], safe_mode=False) # Sanitize using html5lib - bits = [] - parser = html5parser.HTMLParser(tokenizer=sanitizer.HTMLSanitizer) - for token in parser.parseFragment(text).childNodes: - bits.append(token.toxml()) - return "".join(bits) + # bits = [] + # parser = html5parser.HTMLParser(tokenizer=sanitizer.HTMLSanitizer) + # for token in parser.parseFragment(text).childNodes: + # bits.append(token.toxml()) + # return "".join(bits) + return text diff --git a/symposion/proposals/forms.py b/symposion/proposals/forms.py index 8d48c409..d42689cf 100644 --- a/symposion/proposals/forms.py +++ b/symposion/proposals/forms.py @@ -4,7 +4,6 @@ from django.db.models import Q from django.utils.translation import ugettext_lazy as _ from symposion.proposals.models import SupportingDocument -# from markitup.widgets import MarkItUpWidget # @@@ generic proposal form diff --git a/symposion/proposals/migrations/0001_initial.py b/symposion/proposals/migrations/0001_initial.py new file mode 100644 index 00000000..31de5ed8 --- /dev/null +++ b/symposion/proposals/migrations/0001_initial.py @@ -0,0 +1,100 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations +from django.conf import settings +import django.utils.timezone +import symposion.proposals.models + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('symposion_speakers', '__first__'), + ('symposion_conference', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='AdditionalSpeaker', + fields=[ + ('id', models.AutoField(verbose_name='ID', primary_key=True, serialize=False, auto_created=True)), + ('status', models.IntegerField(verbose_name='Status', default=1, choices=[(1, 'Pending'), (2, 'Accepted'), (3, 'Declined')])), + ], + options={ + 'verbose_name': 'Addtional speaker', + 'verbose_name_plural': 'Additional speakers', + }, + ), + migrations.CreateModel( + name='ProposalBase', + fields=[ + ('id', models.AutoField(verbose_name='ID', primary_key=True, serialize=False, auto_created=True)), + ('title', models.CharField(verbose_name='Title', max_length=100)), + ('description', models.TextField(verbose_name='Brief Description', max_length=400, help_text='If your proposal is accepted this will be made public and printed in the program. Should be one paragraph, maximum 400 characters.')), + ('abstract', models.TextField(verbose_name='Detailed Abstract', help_text="Detailed outline. Will be made public if your proposal is accepted. Edit using Markdown.")), + ('abstract_html', models.TextField(blank=True)), + ('additional_notes', models.TextField(blank=True, verbose_name='Addtional Notes', help_text="Anything else you'd like the program committee to know when making their selection: your past experience, etc. This is not made public. Edit using Markdown.")), + ('additional_notes_html', models.TextField(blank=True)), + ('submitted', models.DateTimeField(editable=False, default=django.utils.timezone.now, verbose_name='Submitted')), + ('cancelled', models.BooleanField(verbose_name='Cancelled', default=False)), + ('additional_speakers', models.ManyToManyField(blank=True, verbose_name='Addtional speakers', through='symposion_proposals.AdditionalSpeaker', to='symposion_speakers.Speaker')), + ], + ), + migrations.CreateModel( + name='ProposalKind', + fields=[ + ('id', models.AutoField(verbose_name='ID', primary_key=True, serialize=False, auto_created=True)), + ('name', models.CharField(verbose_name='Name', max_length=100)), + ('slug', models.SlugField(verbose_name='Slug')), + ('section', models.ForeignKey(to='symposion_conference.Section', verbose_name='Section', related_name='proposal_kinds')), + ], + ), + migrations.CreateModel( + name='ProposalSection', + fields=[ + ('id', models.AutoField(verbose_name='ID', primary_key=True, serialize=False, auto_created=True)), + ('start', models.DateTimeField(blank=True, verbose_name='Start', null=True)), + ('end', models.DateTimeField(blank=True, verbose_name='End', null=True)), + ('closed', models.NullBooleanField(verbose_name='Closed')), + ('published', models.NullBooleanField(verbose_name='Published')), + ('section', models.OneToOneField(to='symposion_conference.Section', verbose_name='Section')), + ], + ), + migrations.CreateModel( + name='SupportingDocument', + fields=[ + ('id', models.AutoField(verbose_name='ID', primary_key=True, serialize=False, auto_created=True)), + ('created_at', models.DateTimeField(verbose_name='Created at', default=django.utils.timezone.now)), + ('file', models.FileField(verbose_name='File', upload_to=symposion.proposals.models.uuid_filename)), + ('description', models.CharField(verbose_name='Description', max_length=140)), + ('proposal', models.ForeignKey(to='symposion_proposals.ProposalBase', verbose_name='Proposal', related_name='supporting_documents')), + ('uploaded_by', models.ForeignKey(to=settings.AUTH_USER_MODEL, verbose_name='Uploaded by')), + ], + ), + migrations.AddField( + model_name='proposalbase', + name='kind', + field=models.ForeignKey(to='symposion_proposals.ProposalKind', verbose_name='Kind'), + ), + migrations.AddField( + model_name='proposalbase', + name='speaker', + field=models.ForeignKey(to='symposion_speakers.Speaker', verbose_name='Speaker', related_name='proposals'), + ), + migrations.AddField( + model_name='additionalspeaker', + name='proposalbase', + field=models.ForeignKey(to='symposion_proposals.ProposalBase', verbose_name='Proposalbase'), + ), + migrations.AddField( + model_name='additionalspeaker', + name='speaker', + field=models.ForeignKey(to='symposion_speakers.Speaker', verbose_name='Speaker'), + ), + migrations.AlterUniqueTogether( + name='additionalspeaker', + unique_together=set([('speaker', 'proposalbase')]), + ), + ] diff --git a/symposion/proposals/migrations/__init__.py b/symposion/proposals/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/symposion/proposals/models.py b/symposion/proposals/models.py index e979f7a8..cf8e56b4 100644 --- a/symposion/proposals/models.py +++ b/symposion/proposals/models.py @@ -15,10 +15,9 @@ from django.core.exceptions import ValidationError import reversion -from markitup.fields import MarkupField - from model_utils.managers import InheritanceManager +from symposion.markdown_parser import parse from symposion.conference.models import Section from symposion.speakers.models import Speaker @@ -94,13 +93,14 @@ class ProposalBase(models.Model): help_text=_("If your proposal is accepted this will be made public and printed in the " "program. Should be one paragraph, maximum 400 characters.") ) - abstract = MarkupField( + abstract = models.TextField( _("Detailed Abstract"), help_text=_("Detailed outline. Will be made public if your proposal is accepted. Edit " "using Markdown.") ) - additional_notes = MarkupField( + abstract_html = models.TextField(blank=True) + additional_notes = models.TextField( _("Addtional Notes"), blank=True, help_text=_("Anything else you'd like the program committee to know when making their " @@ -108,6 +108,7 @@ class ProposalBase(models.Model): "Markdown.") ) + additional_notes_html = models.TextField(blank=True) submitted = models.DateTimeField( default=now, editable=False, @@ -115,6 +116,9 @@ class ProposalBase(models.Model): ) speaker = models.ForeignKey(Speaker, related_name="proposals", verbose_name=_("Speaker")) + # @@@ this validation used to exist as a validators keyword on additional_speakers + # M2M field but that is no longer supported by Django. Should be moved to + # the form level def additional_speaker_validator(self, a_speaker): if a_speaker.speaker.email == self.speaker.email: raise ValidationError(_("%s is same as primary speaker.") % a_speaker.speaker.email) @@ -122,10 +126,14 @@ class ProposalBase(models.Model): raise ValidationError(_("%s has already been in speakers.") % a_speaker.speaker.email) additional_speakers = models.ManyToManyField(Speaker, through="AdditionalSpeaker", - blank=True, verbose_name=_("Addtional speakers"), - validators=[additional_speaker_validator]) + blank=True, verbose_name=_("Addtional speakers")) cancelled = models.BooleanField(default=False, verbose_name=_("Cancelled")) + def save(self, *args, **kwargs): + self.abstract_html = parse(self.abstract) + self.additional_notes_html = parse(self.additional_notes) + return super(ProposalBase, self).save(*args, **kwargs) + def can_edit(self): return True diff --git a/symposion/proposals/views.py b/symposion/proposals/views.py index 3259a3d3..d3ccdd0e 100644 --- a/symposion/proposals/views.py +++ b/symposion/proposals/views.py @@ -13,11 +13,12 @@ from django.views import static from django.contrib import messages from django.contrib.auth.models import User -from django.contrib.auth.decorators import login_required from django.utils.translation import ugettext_lazy as _ +from account.decorators import login_required from account.models import EmailAddress + from symposion.proposals.models import ( ProposalBase, ProposalSection, ProposalKind ) @@ -58,7 +59,7 @@ def proposal_submit(request): for kind in proposal_section.section.proposal_kinds.all(): kinds.append(kind) - return render(request, "proposals/proposal_submit.html", { + return render(request, "symposion/proposals/proposal_submit.html", { "kinds": kinds, }) @@ -95,9 +96,9 @@ def proposal_submit_kind(request, kind_slug): else: form = form_class() - return render(request, "proposals/proposal_submit_kind.html", { + return render(request, "symposion/proposals/proposal_submit_kind.html", { "kind": kind, - "form": form, + "proposal_form": form, }) @@ -178,7 +179,7 @@ def proposal_speaker_manage(request, pk): "speakers": proposal.speakers(), "add_speaker_form": add_speaker_form, } - return render(request, "proposals/proposal_speaker_manage.html", ctx) + return render(request, "symposion/proposals/proposal_speaker_manage.html", ctx) @login_required @@ -195,7 +196,7 @@ def proposal_edit(request, pk): "title": "Proposal editing closed", "body": "Proposal editing is closed for this session type." } - return render(request, "proposals/proposal_error.html", ctx) + return render(request, "symposion/proposals/proposal_error.html", ctx) form_class = get_form(settings.PROPOSAL_FORMS[proposal.kind.slug]) @@ -223,7 +224,7 @@ def proposal_edit(request, pk): else: form = form_class(instance=proposal) - return render(request, "proposals/proposal_edit.html", { + return render(request, "symposion/proposals/proposal_edit.html", { "proposal": proposal, "form": form, }) @@ -276,7 +277,7 @@ def proposal_detail(request, pk): else: message_form = None - return render(request, "proposals/proposal_detail.html", { + return render(request, "symposion/proposals/proposal_detail.html", { "proposal": proposal, "message_form": message_form }) @@ -298,7 +299,7 @@ def proposal_cancel(request, pk): messages.success(request, "%s has been cancelled" % proposal.title) return redirect("dashboard") - return render(request, "proposals/proposal_cancel.html", { + return render(request, "symposion/proposals/proposal_cancel.html", { "proposal": proposal, }) @@ -321,7 +322,7 @@ def proposal_leave(request, pk): ctx = { "proposal": proposal, } - return render(request, "proposals/proposal_leave.html", ctx) + return render(request, "symposion/proposals/proposal_leave.html", ctx) @login_required @@ -372,7 +373,7 @@ def document_create(request, proposal_pk): else: form = SupportingDocumentCreateForm() - return render(request, "proposals/document_create.html", { + return render(request, "symposion/proposals/document_create.html", { "proposal": proposal, "form": form, }) diff --git a/symposion/reviews/forms.py b/symposion/reviews/forms.py index e4660cfb..6e8c5701 100644 --- a/symposion/reviews/forms.py +++ b/symposion/reviews/forms.py @@ -2,8 +2,6 @@ from __future__ import unicode_literals from django import forms from django.utils.translation import ugettext_lazy as _ -from markitup.widgets import MarkItUpWidget - from symposion.reviews.models import Review, Comment, ProposalMessage, VOTES @@ -11,7 +9,6 @@ class ReviewForm(forms.ModelForm): class Meta: model = Review fields = ["vote", "comment"] - widgets = {"comment": MarkItUpWidget()} def __init__(self, *args, **kwargs): super(ReviewForm, self).__init__(*args, **kwargs) @@ -25,14 +22,12 @@ class ReviewCommentForm(forms.ModelForm): class Meta: model = Comment fields = ["text"] - widgets = {"text": MarkItUpWidget()} class SpeakerCommentForm(forms.ModelForm): class Meta: model = ProposalMessage fields = ["message"] - widgets = {"message": MarkItUpWidget()} class BulkPresentationForm(forms.Form): diff --git a/symposion/reviews/migrations/0001_initial.py b/symposion/reviews/migrations/0001_initial.py new file mode 100644 index 00000000..70b87754 --- /dev/null +++ b/symposion/reviews/migrations/0001_initial.py @@ -0,0 +1,143 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations +import django.db.models.deletion +from django.conf import settings +from decimal import Decimal +import datetime + + +class Migration(migrations.Migration): + + dependencies = [ + ('symposion_proposals', '0001_initial'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Comment', + fields=[ + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True, serialize=False)), + ('text', models.TextField(verbose_name='Text')), + ('text_html', models.TextField(blank=True)), + ('public', models.BooleanField(verbose_name='Public', default=False, choices=[(True, 'public'), (False, 'private')])), + ('commented_at', models.DateTimeField(verbose_name='Commented at', default=datetime.datetime.now)), + ('commenter', models.ForeignKey(verbose_name='Commenter', to=settings.AUTH_USER_MODEL)), + ('proposal', models.ForeignKey(to='symposion_proposals.ProposalBase', verbose_name='Proposal', related_name='comments')), + ], + options={ + 'verbose_name_plural': 'comments', + 'verbose_name': 'comment', + }, + ), + migrations.CreateModel( + name='LatestVote', + fields=[ + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True, serialize=False)), + ('vote', models.CharField(choices=[('+1', '+1 \u2014 Good proposal and I will argue for it to be accepted.'), ('+0', '+0 \u2014 OK proposal, but I will not argue for it to be accepted.'), ('\u22120', '\u22120 \u2014 Weak proposal, but I will not argue strongly against acceptance.'), ('\u22121', '\u22121 \u2014 Serious issues and I will argue to reject this proposal.')], verbose_name='Vote', max_length=2)), + ('submitted_at', models.DateTimeField(editable=False, verbose_name='Submitted at', default=datetime.datetime.now)), + ('proposal', models.ForeignKey(to='symposion_proposals.ProposalBase', verbose_name='Proposal', related_name='votes')), + ('user', models.ForeignKey(verbose_name='User', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name_plural': 'latest votes', + 'verbose_name': 'latest vote', + }, + ), + migrations.CreateModel( + name='NotificationTemplate', + fields=[ + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True, serialize=False)), + ('label', models.CharField(verbose_name='Label', max_length=100)), + ('from_address', models.EmailField(verbose_name='From address', max_length=254)), + ('subject', models.CharField(verbose_name='Subject', max_length=100)), + ('body', models.TextField(verbose_name='Body')), + ], + options={ + 'verbose_name_plural': 'notification templates', + 'verbose_name': 'notification template', + }, + ), + migrations.CreateModel( + name='ProposalMessage', + fields=[ + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True, serialize=False)), + ('message', models.TextField(verbose_name='Message')), + ('message_html', models.TextField(blank=True)), + ('submitted_at', models.DateTimeField(editable=False, verbose_name='Submitted at', default=datetime.datetime.now)), + ('proposal', models.ForeignKey(to='symposion_proposals.ProposalBase', verbose_name='Proposal', related_name='messages')), + ('user', models.ForeignKey(verbose_name='User', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name_plural': 'proposal messages', + 'verbose_name': 'proposal message', + 'ordering': ['submitted_at'], + }, + ), + migrations.CreateModel( + name='ProposalResult', + fields=[ + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True, serialize=False)), + ('score', models.DecimalField(decimal_places=2, verbose_name='Score', max_digits=5, default=Decimal('0.00'))), + ('comment_count', models.PositiveIntegerField(verbose_name='Comment count', default=0)), + ('vote_count', models.PositiveIntegerField(verbose_name='Vote count', default=0)), + ('plus_one', models.PositiveIntegerField(verbose_name='Plus one', default=0)), + ('plus_zero', models.PositiveIntegerField(verbose_name='Plus zero', default=0)), + ('minus_zero', models.PositiveIntegerField(verbose_name='Minus zero', default=0)), + ('minus_one', models.PositiveIntegerField(verbose_name='Minus one', default=0)), + ('accepted', models.NullBooleanField(verbose_name='Accepted', default=None, choices=[(True, 'accepted'), (False, 'rejected'), (None, 'undecided')])), + ('status', models.CharField(choices=[('accepted', 'accepted'), ('rejected', 'rejected'), ('undecided', 'undecided'), ('standby', 'standby')], verbose_name='Status', max_length=20, default='undecided')), + ('proposal', models.OneToOneField(to='symposion_proposals.ProposalBase', verbose_name='Proposal', related_name='result')), + ], + options={ + 'verbose_name_plural': 'proposal_results', + 'verbose_name': 'proposal_result', + }, + ), + migrations.CreateModel( + name='ResultNotification', + fields=[ + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True, serialize=False)), + ('timestamp', models.DateTimeField(verbose_name='Timestamp', default=datetime.datetime.now)), + ('to_address', models.EmailField(verbose_name='To address', max_length=254)), + ('from_address', models.EmailField(verbose_name='From address', max_length=254)), + ('subject', models.CharField(verbose_name='Subject', max_length=100)), + ('body', models.TextField(verbose_name='Body')), + ('proposal', models.ForeignKey(to='symposion_proposals.ProposalBase', verbose_name='Proposal', related_name='notifications')), + ('template', models.ForeignKey(to='symposion_reviews.NotificationTemplate', blank=True, verbose_name='Template', null=True, on_delete=django.db.models.deletion.SET_NULL)), + ], + ), + migrations.CreateModel( + name='Review', + fields=[ + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True, serialize=False)), + ('vote', models.CharField(blank=True, verbose_name='Vote', max_length=2, choices=[('+1', '+1 \u2014 Good proposal and I will argue for it to be accepted.'), ('+0', '+0 \u2014 OK proposal, but I will not argue for it to be accepted.'), ('\u22120', '\u22120 \u2014 Weak proposal, but I will not argue strongly against acceptance.'), ('\u22121', '\u22121 \u2014 Serious issues and I will argue to reject this proposal.')])), + ('comment', models.TextField(verbose_name='Comment')), + ('comment_html', models.TextField(blank=True)), + ('submitted_at', models.DateTimeField(editable=False, verbose_name='Submitted at', default=datetime.datetime.now)), + ('proposal', models.ForeignKey(to='symposion_proposals.ProposalBase', verbose_name='Proposal', related_name='reviews')), + ('user', models.ForeignKey(verbose_name='User', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name_plural': 'reviews', + 'verbose_name': 'review', + }, + ), + migrations.CreateModel( + name='ReviewAssignment', + fields=[ + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True, serialize=False)), + ('origin', models.IntegerField(choices=[(0, 'auto-assigned, initial'), (1, 'opted-in'), (2, 'auto-assigned, later')], verbose_name='Origin')), + ('assigned_at', models.DateTimeField(verbose_name='Assigned at', default=datetime.datetime.now)), + ('opted_out', models.BooleanField(verbose_name='Opted out', default=False)), + ('proposal', models.ForeignKey(verbose_name='Proposal', to='symposion_proposals.ProposalBase')), + ('user', models.ForeignKey(verbose_name='User', to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.AlterUniqueTogether( + name='latestvote', + unique_together=set([('proposal', 'user')]), + ), + ] diff --git a/symposion/reviews/migrations/__init__.py b/symposion/reviews/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/symposion/reviews/models.py b/symposion/reviews/models.py index 9957aa1f..b1fa767f 100644 --- a/symposion/reviews/models.py +++ b/symposion/reviews/models.py @@ -4,26 +4,22 @@ from datetime import datetime from decimal import Decimal from django.db import models -from django.db.models import Q +from django.db.models import Q, F from django.db.models.signals import post_save from django.contrib.auth.models import User from django.utils.translation import ugettext_lazy as _ -from markitup.fields import MarkupField - +from symposion.markdown_parser import parse from symposion.proposals.models import ProposalBase from symposion.schedule.models import Presentation -class ProposalScoreExpression(object): - - def as_sql(self, qn, connection=None): - sql = "((3 * plus_one + plus_zero) - (minus_zero + 3 * minus_one))" - return sql, [] - - def prepare_database_save(self, unused): - return self +def score_expression(): + return ( + (3 * F("plus_one") + F("plus_zero")) - + (F("minus_zero") + 3 * F("minus_one")) + ) class Votes(object): @@ -97,9 +93,14 @@ class ProposalMessage(models.Model): proposal = models.ForeignKey(ProposalBase, related_name="messages", verbose_name=_("Proposal")) user = models.ForeignKey(User, verbose_name=_("User")) - message = MarkupField(verbose_name=_("Message")) + message = models.TextField(verbose_name=_("Message")) + message_html = models.TextField(blank=True) submitted_at = models.DateTimeField(default=datetime.now, editable=False, verbose_name=_("Submitted at")) + def save(self, *args, **kwargs): + self.message_html = parse(self.message) + return super(ProposalMessage, self).save(*args, **kwargs) + class Meta: ordering = ["submitted_at"] verbose_name = _("proposal message") @@ -115,10 +116,12 @@ class Review(models.Model): # No way to encode "-0" vs. "+0" into an IntegerField, and I don't feel # like some complicated encoding system. vote = models.CharField(max_length=2, blank=True, choices=VOTES.CHOICES, verbose_name=_("Vote")) - comment = MarkupField(verbose_name=_("Comment")) + comment = models.TextField(verbose_name=_("Comment")) + comment_html = models.TextField(blank=True) submitted_at = models.DateTimeField(default=datetime.now, editable=False, verbose_name=_("Submitted at")) def save(self, **kwargs): + self.comment_html = parse(self.comment) if self.vote: vote, created = LatestVote.objects.get_or_create( proposal=self.proposal, @@ -258,7 +261,7 @@ class ProposalResult(models.Model): vote=VOTES.MINUS_ONE ).count() result.save() - cls._default_manager.filter(pk=result.pk).update(score=ProposalScoreExpression()) + cls._default_manager.filter(pk=result.pk).update(score=score_expression()) def update_vote(self, vote, previous=None, removal=False): mapping = { @@ -287,7 +290,7 @@ class ProposalResult(models.Model): self.comment_count = models.F("comment_count") + 1 self.save() model = self.__class__ - model._default_manager.filter(pk=self.pk).update(score=ProposalScoreExpression()) + model._default_manager.filter(pk=self.pk).update(score=score_expression()) class Meta: verbose_name = _("proposal_result") @@ -297,7 +300,8 @@ class ProposalResult(models.Model): class Comment(models.Model): proposal = models.ForeignKey(ProposalBase, related_name="comments", verbose_name=_("Proposal")) commenter = models.ForeignKey(User, verbose_name=_("Commenter")) - text = MarkupField(verbose_name=_("Text")) + text = models.TextField(verbose_name=_("Text")) + text_html = models.TextField(blank=True) # Or perhaps more accurately, can the user see this comment. public = models.BooleanField(choices=[(True, _("public")), (False, _("private"))], default=False, verbose_name=_("Public")) @@ -307,6 +311,10 @@ class Comment(models.Model): verbose_name = _("comment") verbose_name_plural = _("comments") + def save(self, *args, **kwargs): + self.comment_html = parse(self.comment) + return super(Comment, self).save(*args, **kwargs) + class NotificationTemplate(models.Model): diff --git a/symposion/reviews/views.py b/symposion/reviews/views.py index dd5961bf..6e6e70fa 100644 --- a/symposion/reviews/views.py +++ b/symposion/reviews/views.py @@ -5,11 +5,13 @@ from django.shortcuts import render, redirect, get_object_or_404 from django.template import Context, Template from django.views.decorators.http import require_POST -from django.contrib.auth.decorators import login_required +from account.decorators import login_required + +# @@@ switch to pinax-teams +from symposion.teams.models import Team from symposion.conf import settings from symposion.proposals.models import ProposalBase, ProposalSection -from symposion.teams.models import Team from symposion.utils.mail import send_email from symposion.reviews.forms import ReviewForm, SpeakerCommentForm @@ -21,7 +23,7 @@ from symposion.reviews.models import ( def access_not_permitted(request): - return render(request, "reviews/access_not_permitted.html") + return render(request, "symposion/reviews/access_not_permitted.html") def proposals_generator(request, queryset, user_pk=None, check_speaker=True): @@ -96,7 +98,7 @@ def review_section(request, section_slug, assigned=False, reviewed="all"): "reviewed": reviewed, } - return render(request, "reviews/review_list.html", ctx) + return render(request, "symposion/reviews/review_list.html", ctx) @login_required @@ -120,7 +122,7 @@ def review_list(request, section_slug, user_pk): ctx = { "proposals": proposals, } - return render(request, "reviews/review_list.html", ctx) + return render(request, "symposion/reviews/review_list.html", ctx) @login_required @@ -164,7 +166,7 @@ def review_admin(request, section_slug): "section_slug": section_slug, "reviewers": reviewers(), } - return render(request, "reviews/review_admin.html", ctx) + return render(request, "symposion/reviews/review_admin.html", ctx) # FIXME: This view is too complex according to flake8 @@ -273,7 +275,7 @@ def review_detail(request, pk): reviews = Review.objects.filter(proposal=proposal).order_by("-submitted_at") messages = proposal.messages.order_by("submitted_at") - return render(request, "reviews/review_detail.html", { + return render(request, "symposion/reviews/review_detail.html", { "proposal": proposal, "latest_vote": latest_vote, "reviews": reviews, @@ -353,7 +355,7 @@ def review_status(request, section_slug=None, key=None): else: ctx["proposals"] = proposals - return render(request, "reviews/review_stats.html", ctx) + return render(request, "symposion/reviews/review_stats.html", ctx) @login_required @@ -364,7 +366,7 @@ def review_assignments(request): user=request.user, opted_out=False ) - return render(request, "reviews/review_assignment.html", { + return render(request, "symposion/reviews/review_assignment.html", { "assignments": assignments, }) @@ -398,7 +400,7 @@ def review_bulk_accept(request, section_slug): else: form = BulkPresentationForm() - return render(request, "reviews/review_bulk_accept.html", { + return render(request, "symposion/reviews/review_bulk_accept.html", { "form": form, }) @@ -417,7 +419,7 @@ def result_notification(request, section_slug, status): "proposals": proposals, "notification_templates": notification_templates, } - return render(request, "reviews/result_notification.html", ctx) + return render(request, "symposion/reviews/result_notification.html", ctx) @login_required @@ -455,7 +457,7 @@ def result_notification_prepare(request, section_slug, status): "proposals": proposals, "proposal_pks": ",".join([str(pk) for pk in proposal_pks]), } - return render(request, "reviews/result_notification_prepare.html", ctx) + return render(request, "symposion/reviews/result_notification_prepare.html", ctx) @login_required diff --git a/symposion/schedule/forms.py b/symposion/schedule/forms.py index 426160a3..41695259 100644 --- a/symposion/schedule/forms.py +++ b/symposion/schedule/forms.py @@ -9,8 +9,6 @@ from django.contrib import messages from django.db import IntegrityError, transaction from django.db.models import Q -from markitup.widgets import MarkItUpWidget - from symposion.schedule.models import (Day, Presentation, Room, SlotKind, Slot, SlotRoom) @@ -44,7 +42,6 @@ class SlotEditForm(forms.Form): def build_content_override_field(self): kwargs = { "label": "Content", - "widget": MarkItUpWidget(), "required": False, "initial": self.slot.content_override, } diff --git a/symposion/schedule/migrations/0001_initial.py b/symposion/schedule/migrations/0001_initial.py new file mode 100644 index 00000000..4e3954cf --- /dev/null +++ b/symposion/schedule/migrations/0001_initial.py @@ -0,0 +1,186 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations +import datetime +from django.conf import settings + + +class Migration(migrations.Migration): + + dependencies = [ + ('symposion_speakers', '__first__'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('symposion_conference', '0001_initial'), + ('symposion_proposals', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='Day', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('date', models.DateField(verbose_name='Date')), + ], + options={ + 'ordering': ['date'], + 'verbose_name': 'date', + 'verbose_name_plural': 'dates', + }, + ), + migrations.CreateModel( + name='Presentation', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('title', models.CharField(max_length=100, verbose_name='Title')), + ('description', models.TextField(verbose_name='Description')), + ('description_html', models.TextField(blank=True)), + ('abstract', models.TextField(verbose_name='Abstract')), + ('abstract_html', models.TextField(blank=True)), + ('cancelled', models.BooleanField(default=False, verbose_name='Cancelled')), + ('additional_speakers', models.ManyToManyField(related_name='copresentations', to='symposion_speakers.Speaker', verbose_name='Additional speakers', blank=True)), + ('proposal_base', models.OneToOneField(to='symposion_proposals.ProposalBase', related_name='presentation', verbose_name='Proposal base')), + ('section', models.ForeignKey(to='symposion_conference.Section', related_name='presentations', verbose_name='Section')), + ], + options={ + 'ordering': ['slot'], + 'verbose_name': 'presentation', + 'verbose_name_plural': 'presentations', + }, + ), + migrations.CreateModel( + name='Room', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('name', models.CharField(max_length=65, verbose_name='Name')), + ('order', models.PositiveIntegerField(verbose_name='Order')), + ], + options={ + 'verbose_name': 'Room', + 'verbose_name_plural': 'Rooms', + }, + ), + migrations.CreateModel( + name='Schedule', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('published', models.BooleanField(default=True, verbose_name='Published')), + ('hidden', models.BooleanField(default=False, verbose_name='Hide schedule from overall conference view')), + ('section', models.OneToOneField(to='symposion_conference.Section', verbose_name='Section')), + ], + options={ + 'ordering': ['section'], + 'verbose_name': 'Schedule', + 'verbose_name_plural': 'Schedules', + }, + ), + migrations.CreateModel( + name='Session', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('day', models.ForeignKey(to='symposion_schedule.Day', related_name='sessions', verbose_name='Day')), + ], + options={ + 'verbose_name': 'Session', + 'verbose_name_plural': 'Sessions', + }, + ), + migrations.CreateModel( + name='SessionRole', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('role', models.IntegerField(verbose_name='Role', choices=[(1, 'Session Chair'), (2, 'Session Runner')])), + ('status', models.NullBooleanField(verbose_name='Status')), + ('submitted', models.DateTimeField(default=datetime.datetime.now)), + ('session', models.ForeignKey(to='symposion_schedule.Session', verbose_name='Session')), + ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL, verbose_name='User')), + ], + options={ + 'verbose_name': 'Session role', + 'verbose_name_plural': 'Session roles', + }, + ), + migrations.CreateModel( + name='Slot', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('start', models.TimeField(verbose_name='Start')), + ('end', models.TimeField(verbose_name='End')), + ('content_override', models.TextField(verbose_name='Content override', blank=True)), + ('content_override_html', models.TextField(blank=True)), + ('day', models.ForeignKey(to='symposion_schedule.Day', verbose_name='Day')), + ], + options={ + 'ordering': ['day', 'start', 'end'], + 'verbose_name': 'slot', + 'verbose_name_plural': 'slots', + }, + ), + migrations.CreateModel( + name='SlotKind', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('label', models.CharField(max_length=50, verbose_name='Label')), + ('schedule', models.ForeignKey(to='symposion_schedule.Schedule', verbose_name='schedule')), + ], + options={ + 'verbose_name': 'Slot kind', + 'verbose_name_plural': 'Slot kinds', + }, + ), + migrations.CreateModel( + name='SlotRoom', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('room', models.ForeignKey(to='symposion_schedule.Room', verbose_name='Room')), + ('slot', models.ForeignKey(to='symposion_schedule.Slot', verbose_name='Slot')), + ], + options={ + 'ordering': ['slot', 'room__order'], + 'verbose_name': 'Slot room', + 'verbose_name_plural': 'Slot rooms', + }, + ), + migrations.AddField( + model_name='slot', + name='kind', + field=models.ForeignKey(to='symposion_schedule.SlotKind', verbose_name='Kind'), + ), + migrations.AddField( + model_name='session', + name='slots', + field=models.ManyToManyField(related_name='sessions', verbose_name='Slots', to='symposion_schedule.Slot'), + ), + migrations.AddField( + model_name='room', + name='schedule', + field=models.ForeignKey(to='symposion_schedule.Schedule', verbose_name='Schedule'), + ), + migrations.AddField( + model_name='presentation', + name='slot', + field=models.OneToOneField(to='symposion_schedule.Slot', related_name='content_ptr', blank=True, null=True, verbose_name='Slot'), + ), + migrations.AddField( + model_name='presentation', + name='speaker', + field=models.ForeignKey(to='symposion_speakers.Speaker', related_name='presentations', verbose_name='Speaker'), + ), + migrations.AddField( + model_name='day', + name='schedule', + field=models.ForeignKey(to='symposion_schedule.Schedule', verbose_name='Schedule'), + ), + migrations.AlterUniqueTogether( + name='slotroom', + unique_together=set([('slot', 'room')]), + ), + migrations.AlterUniqueTogether( + name='sessionrole', + unique_together=set([('session', 'user', 'role')]), + ), + migrations.AlterUniqueTogether( + name='day', + unique_together=set([('schedule', 'date')]), + ), + ] diff --git a/symposion/schedule/migrations/__init__.py b/symposion/schedule/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/symposion/schedule/models.py b/symposion/schedule/models.py index 22aad378..3325629e 100644 --- a/symposion/schedule/models.py +++ b/symposion/schedule/models.py @@ -8,8 +8,7 @@ from django.db import models from django.utils.encoding import python_2_unicode_compatible from django.utils.translation import ugettext_lazy as _ -from markitup.fields import MarkupField - +from symposion.markdown_parser import parse from symposion.proposals.models import ProposalBase from symposion.conference.models import Section from symposion.speakers.models import Speaker @@ -87,7 +86,12 @@ class Slot(models.Model): kind = models.ForeignKey(SlotKind, verbose_name=_("Kind")) start = models.TimeField(verbose_name=_("Start")) end = models.TimeField(verbose_name=_("End")) - content_override = MarkupField(blank=True, verbose_name=_("Content override")) + content_override = models.TextField(blank=True, verbose_name=_("Content override")) + content_override_html = models.TextField(blank=True) + + def save(self, *args, **kwargs): + self.content_override_html = parse(self.content_override) + return super(Slot, self).save(*args, **kwargs) def assign(self, content): """ @@ -179,8 +183,10 @@ class Presentation(models.Model): slot = models.OneToOneField(Slot, null=True, blank=True, related_name="content_ptr", verbose_name=_("Slot")) title = models.CharField(max_length=100, verbose_name=_("Title")) - description = MarkupField(verbose_name=_("Description")) - abstract = MarkupField(verbose_name=_("Abstract")) + description = models.TextField(verbose_name=_("Description")) + description_html = models.TextField(blank=True) + abstract = models.TextField(verbose_name=_("Abstract")) + abstract_html = models.TextField(blank=True) speaker = models.ForeignKey(Speaker, related_name="presentations", verbose_name=_("Speaker")) additional_speakers = models.ManyToManyField(Speaker, related_name="copresentations", blank=True, verbose_name=_("Additional speakers")) @@ -188,6 +194,11 @@ class Presentation(models.Model): proposal_base = models.OneToOneField(ProposalBase, related_name="presentation", verbose_name=_("Proposal base")) section = models.ForeignKey(Section, related_name="presentations", verbose_name=_("Section")) + def save(self, *args, **kwargs): + self.description_html = parse(self.description) + self.abstract_html = parse(self.abstract) + return super(Presentation, self).save(*args, **kwargs) + @property def number(self): return self.proposal.number diff --git a/symposion/schedule/tests/runtests.py b/symposion/schedule/tests/runtests.py index dcb46455..b7ef4b1d 100755 --- a/symposion/schedule/tests/runtests.py +++ b/symposion/schedule/tests/runtests.py @@ -22,7 +22,6 @@ try: "django.contrib.contenttypes", "django.contrib.sites", - "markitup", "reversion", "symposion", @@ -34,8 +33,6 @@ try: ], SITE_ID=1, NOSE_ARGS=['-s'], - - MARKITUP_FILTER=('django.contrib.markup.templatetags.markup.textile', {}), ) try: diff --git a/symposion/schedule/views.py b/symposion/schedule/views.py index 898fcc36..cd6ad2cd 100644 --- a/symposion/schedule/views.py +++ b/symposion/schedule/views.py @@ -6,11 +6,12 @@ from django.http import Http404, HttpResponse from django.shortcuts import render, get_object_or_404, redirect from django.template import loader, Context -from django.contrib.auth.decorators import login_required from django.contrib.auth.models import User from django.contrib import messages from django.contrib.sites.models import Site +from account.decorators import login_required + from symposion.schedule.forms import SlotEditForm, ScheduleSectionForm from symposion.schedule.models import Schedule, Day, Slot, Presentation, Session, SessionRole from symposion.schedule.timetable import TimeTable @@ -47,7 +48,7 @@ def schedule_conference(request): ctx = { "sections": sections, } - return render(request, "schedule/schedule_conference.html", ctx) + return render(request, "symposion/schedule/schedule_conference.html", ctx) def schedule_detail(request, slug=None): @@ -63,7 +64,7 @@ def schedule_detail(request, slug=None): "schedule": schedule, "days": days, } - return render(request, "schedule/schedule_detail.html", ctx) + return render(request, "symposion/schedule/schedule_detail.html", ctx) def schedule_list(request, slug=None): @@ -76,7 +77,7 @@ def schedule_list(request, slug=None): "schedule": schedule, "presentations": presentations, } - return render(request, "schedule/schedule_list.html", ctx) + return render(request, "symposion/schedule/schedule_list.html", ctx) def schedule_list_csv(request, slug=None): @@ -92,7 +93,7 @@ def schedule_list_csv(request, slug=None): file_slug = "presentations" response["Content-Disposition"] = 'attachment; filename="%s.csv"' % file_slug - response.write(loader.get_template("schedule/schedule_list.csv").render(Context({ + response.write(loader.get_template("symposion/schedule/schedule_list.csv").render(Context({ "presentations": presentations, }))) @@ -126,7 +127,7 @@ def schedule_edit(request, slug=None): "days": days, "form": form } - return render(request, "schedule/schedule_edit.html", ctx) + return render(request, "symposion/schedule/schedule_edit.html", ctx) @login_required @@ -160,7 +161,7 @@ def schedule_slot_edit(request, slug, slot_pk): "form": form, "slot": slot, } - return render(request, "schedule/_slot_edit.html", ctx) + return render(request, "symposion/schedule/_slot_edit.html", ctx) def schedule_presentation_detail(request, pk): @@ -175,7 +176,7 @@ def schedule_presentation_detail(request, pk): "presentation": presentation, "schedule": schedule, } - return render(request, "schedule/presentation_detail.html", ctx) + return render(request, "symposion/schedule/presentation_detail.html", ctx) def schedule_json(request): @@ -230,7 +231,7 @@ def schedule_json(request): data.append(slot_data) return HttpResponse( - json.dumps({'schedule': data}), + json.dumps({"schedule": data}), content_type="application/json" ) @@ -238,7 +239,7 @@ def schedule_json(request): def session_list(request): sessions = Session.objects.all().order_by('pk') - return render(request, "schedule/session_list.html", { + return render(request, "symposion/schedule/session_list.html", { "sessions": sessions, }) @@ -306,7 +307,7 @@ def session_detail(request, session_id): return redirect("schedule_session_detail", session_id) - return render(request, "schedule/session_detail.html", { + return render(request, "symposion/schedule/session_detail.html", { "session": session, "chair": chair, "chair_denied": chair_denied, diff --git a/symposion/speakers/forms.py b/symposion/speakers/forms.py index fd42945c..3b81efa3 100644 --- a/symposion/speakers/forms.py +++ b/symposion/speakers/forms.py @@ -1,8 +1,6 @@ from __future__ import unicode_literals from django import forms -from markitup.widgets import MarkItUpWidget - from symposion.speakers.models import Speaker @@ -15,6 +13,3 @@ class SpeakerForm(forms.ModelForm): "biography", "photo", ] - widgets = { - "biography": MarkItUpWidget(), - } diff --git a/symposion/speakers/migrations/0001_initial.py b/symposion/speakers/migrations/0001_initial.py new file mode 100644 index 00000000..c3b6f2b1 --- /dev/null +++ b/symposion/speakers/migrations/0001_initial.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations +import datetime +from django.conf import settings + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Speaker', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, verbose_name='ID', serialize=False)), + ('name', models.CharField(verbose_name='Name', help_text='As you would like it to appear in the conference program.', max_length=100)), + ('biography', models.TextField(verbose_name='Biography', blank=True, help_text="A little bit about you. Edit using Markdown.")), + ('biography_html', models.TextField(blank=True)), + ('photo', models.ImageField(verbose_name='Photo', upload_to='speaker_photos', blank=True)), + ('annotation', models.TextField(verbose_name='Annotation')), + ('invite_email', models.CharField(verbose_name='Invite_email', unique=True, db_index=True, max_length=200, null=True)), + ('invite_token', models.CharField(verbose_name='Invite token', db_index=True, max_length=40)), + ('created', models.DateTimeField(editable=False, verbose_name='Created', default=datetime.datetime.now)), + ('user', models.OneToOneField(null=True, related_name='speaker_profile', verbose_name='User', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name': 'Speaker', + 'verbose_name_plural': 'Speakers', + 'ordering': ['name'], + }, + ), + ] diff --git a/symposion/speakers/migrations/__init__.py b/symposion/speakers/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/symposion/speakers/models.py b/symposion/speakers/models.py index e7942b47..a6e22e49 100644 --- a/symposion/speakers/models.py +++ b/symposion/speakers/models.py @@ -9,7 +9,7 @@ from django.utils.translation import ugettext_lazy as _ from django.contrib.auth.models import User -from markitup.fields import MarkupField +from symposion.markdown_parser import parse @python_2_unicode_compatible @@ -24,10 +24,11 @@ class Speaker(models.Model): name = models.CharField(verbose_name=_("Name"), max_length=100, help_text=_("As you would like it to appear in the" " conference program.")) - biography = MarkupField(blank=True, help_text=_("A little bit about you. Edit using " + biography = models.TextField(blank=True, help_text=_("A little bit about you. Edit using " "" "Markdown."), verbose_name=_("Biography")) + biography_html = models.TextField(blank=True) photo = models.ImageField(upload_to="speaker_photos", blank=True, verbose_name=_("Photo")) annotation = models.TextField(verbose_name=_("Annotation")) # staff only invite_email = models.CharField(max_length=200, unique=True, null=True, db_index=True, verbose_name=_("Invite_email")) @@ -43,6 +44,10 @@ class Speaker(models.Model): verbose_name = _("Speaker") verbose_name_plural = _("Speakers") + def save(self, *args, **kwargs): + self.biography_html = parse(self.biography) + return super(Speaker, self).save(*args, **kwargs) + def __str__(self): if self.user: return self.name diff --git a/symposion/speakers/views.py b/symposion/speakers/views.py index 5fa1c45b..f60064e0 100644 --- a/symposion/speakers/views.py +++ b/symposion/speakers/views.py @@ -4,10 +4,11 @@ from django.http import Http404 from django.shortcuts import render, redirect, get_object_or_404 from django.contrib import messages -from django.contrib.auth.decorators import login_required from django.contrib.auth.models import User from django.utils.translation import ugettext_lazy as _ +from account.decorators import login_required + from symposion.proposals.models import ProposalBase from symposion.speakers.forms import SpeakerForm from symposion.speakers.models import Speaker @@ -39,9 +40,8 @@ def speaker_create(request): return redirect("dashboard") else: form = SpeakerForm(initial={"name": request.user.get_full_name()}) - - return render(request, "speakers/speaker_create.html", { - "form": form, + return render(request, "symposion/speakers/speaker_create.html", { + "speaker_form": form, }) @@ -68,8 +68,8 @@ def speaker_create_staff(request, pk): else: form = SpeakerForm(initial={"name": user.get_full_name()}) - return render(request, "speakers/speaker_create.html", { - "form": form, + return render(request, "symposion/speakers/speaker_create.html", { + "speaker_form": form, }) @@ -121,8 +121,8 @@ def speaker_edit(request, pk=None): else: form = SpeakerForm(instance=speaker) - return render(request, "speakers/speaker_edit.html", { - "form": form, + return render(request, "symposion/speakers/speaker_edit.html", { + "speaker_form": form, }) @@ -132,7 +132,7 @@ def speaker_profile(request, pk): if not presentations and not request.user.is_staff: raise Http404() - return render(request, "speakers/speaker_profile.html", { + return render(request, "symposion/speakers/speaker_profile.html", { "speaker": speaker, "presentations": presentations, }) diff --git a/symposion/sponsorship/migrations/0001_initial.py b/symposion/sponsorship/migrations/0001_initial.py new file mode 100644 index 00000000..bf9c3aca --- /dev/null +++ b/symposion/sponsorship/migrations/0001_initial.py @@ -0,0 +1,115 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations +from django.conf import settings +import datetime + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('symposion_conference', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='Benefit', + fields=[ + ('id', models.AutoField(serialize=False, auto_created=True, primary_key=True, verbose_name='ID')), + ('name', models.CharField(verbose_name='Name', max_length=100)), + ('description', models.TextField(blank=True, verbose_name='Description')), + ('type', models.CharField(default='simple', choices=[('text', 'Text'), ('file', 'File'), ('richtext', 'Rich Text'), ('weblogo', 'Web Logo'), ('simple', 'Simple'), ('option', 'Option')], verbose_name='Type', max_length=10)), + ('content_type', models.CharField(default='simple', choices=[('simple', 'Simple'), ('listing_text_af', 'Listing Text (Afrikaans)'), ('listing_text_ar', 'Listing Text (Arabic)'), ('listing_text_ast', 'Listing Text (Asturian)'), ('listing_text_az', 'Listing Text (Azerbaijani)'), ('listing_text_bg', 'Listing Text (Bulgarian)'), ('listing_text_be', 'Listing Text (Belarusian)'), ('listing_text_bn', 'Listing Text (Bengali)'), ('listing_text_br', 'Listing Text (Breton)'), ('listing_text_bs', 'Listing Text (Bosnian)'), ('listing_text_ca', 'Listing Text (Catalan)'), ('listing_text_cs', 'Listing Text (Czech)'), ('listing_text_cy', 'Listing Text (Welsh)'), ('listing_text_da', 'Listing Text (Danish)'), ('listing_text_de', 'Listing Text (German)'), ('listing_text_el', 'Listing Text (Greek)'), ('listing_text_en', 'Listing Text (English)'), ('listing_text_en-au', 'Listing Text (Australian English)'), ('listing_text_en-gb', 'Listing Text (British English)'), ('listing_text_eo', 'Listing Text (Esperanto)'), ('listing_text_es', 'Listing Text (Spanish)'), ('listing_text_es-ar', 'Listing Text (Argentinian Spanish)'), ('listing_text_es-mx', 'Listing Text (Mexican Spanish)'), ('listing_text_es-ni', 'Listing Text (Nicaraguan Spanish)'), ('listing_text_es-ve', 'Listing Text (Venezuelan Spanish)'), ('listing_text_et', 'Listing Text (Estonian)'), ('listing_text_eu', 'Listing Text (Basque)'), ('listing_text_fa', 'Listing Text (Persian)'), ('listing_text_fi', 'Listing Text (Finnish)'), ('listing_text_fr', 'Listing Text (French)'), ('listing_text_fy', 'Listing Text (Frisian)'), ('listing_text_ga', 'Listing Text (Irish)'), ('listing_text_gl', 'Listing Text (Galician)'), ('listing_text_he', 'Listing Text (Hebrew)'), ('listing_text_hi', 'Listing Text (Hindi)'), ('listing_text_hr', 'Listing Text (Croatian)'), ('listing_text_hu', 'Listing Text (Hungarian)'), ('listing_text_ia', 'Listing Text (Interlingua)'), ('listing_text_id', 'Listing Text (Indonesian)'), ('listing_text_io', 'Listing Text (Ido)'), ('listing_text_is', 'Listing Text (Icelandic)'), ('listing_text_it', 'Listing Text (Italian)'), ('listing_text_ja', 'Listing Text (Japanese)'), ('listing_text_ka', 'Listing Text (Georgian)'), ('listing_text_kk', 'Listing Text (Kazakh)'), ('listing_text_km', 'Listing Text (Khmer)'), ('listing_text_kn', 'Listing Text (Kannada)'), ('listing_text_ko', 'Listing Text (Korean)'), ('listing_text_lb', 'Listing Text (Luxembourgish)'), ('listing_text_lt', 'Listing Text (Lithuanian)'), ('listing_text_lv', 'Listing Text (Latvian)'), ('listing_text_mk', 'Listing Text (Macedonian)'), ('listing_text_ml', 'Listing Text (Malayalam)'), ('listing_text_mn', 'Listing Text (Mongolian)'), ('listing_text_mr', 'Listing Text (Marathi)'), ('listing_text_my', 'Listing Text (Burmese)'), ('listing_text_nb', 'Listing Text (Norwegian Bokmal)'), ('listing_text_ne', 'Listing Text (Nepali)'), ('listing_text_nl', 'Listing Text (Dutch)'), ('listing_text_nn', 'Listing Text (Norwegian Nynorsk)'), ('listing_text_os', 'Listing Text (Ossetic)'), ('listing_text_pa', 'Listing Text (Punjabi)'), ('listing_text_pl', 'Listing Text (Polish)'), ('listing_text_pt', 'Listing Text (Portuguese)'), ('listing_text_pt-br', 'Listing Text (Brazilian Portuguese)'), ('listing_text_ro', 'Listing Text (Romanian)'), ('listing_text_ru', 'Listing Text (Russian)'), ('listing_text_sk', 'Listing Text (Slovak)'), ('listing_text_sl', 'Listing Text (Slovenian)'), ('listing_text_sq', 'Listing Text (Albanian)'), ('listing_text_sr', 'Listing Text (Serbian)'), ('listing_text_sr-latn', 'Listing Text (Serbian Latin)'), ('listing_text_sv', 'Listing Text (Swedish)'), ('listing_text_sw', 'Listing Text (Swahili)'), ('listing_text_ta', 'Listing Text (Tamil)'), ('listing_text_te', 'Listing Text (Telugu)'), ('listing_text_th', 'Listing Text (Thai)'), ('listing_text_tr', 'Listing Text (Turkish)'), ('listing_text_tt', 'Listing Text (Tatar)'), ('listing_text_udm', 'Listing Text (Udmurt)'), ('listing_text_uk', 'Listing Text (Ukrainian)'), ('listing_text_ur', 'Listing Text (Urdu)'), ('listing_text_vi', 'Listing Text (Vietnamese)'), ('listing_text_zh-cn', 'Listing Text (Simplified Chinese)'), ('listing_text_zh-hans', 'Listing Text (Simplified Chinese)'), ('listing_text_zh-hant', 'Listing Text (Traditional Chinese)'), ('listing_text_zh-tw', 'Listing Text (Traditional Chinese)')], verbose_name='content type', max_length=20)), + ], + ), + migrations.CreateModel( + name='BenefitLevel', + fields=[ + ('id', models.AutoField(serialize=False, auto_created=True, primary_key=True, verbose_name='ID')), + ('max_words', models.PositiveIntegerField(blank=True, verbose_name='Max words', null=True)), + ('other_limits', models.CharField(blank=True, verbose_name='Other limits', max_length=200)), + ('benefit', models.ForeignKey(to='symposion_sponsorship.Benefit', related_name='benefit_levels', verbose_name='Benefit')), + ], + options={ + 'verbose_name_plural': 'Benefit levels', + 'ordering': ['level'], + 'verbose_name': 'Benefit level', + }, + ), + migrations.CreateModel( + name='Sponsor', + fields=[ + ('id', models.AutoField(serialize=False, auto_created=True, primary_key=True, verbose_name='ID')), + ('name', models.CharField(verbose_name='Sponsor Name', max_length=100)), + ('display_url', models.URLField(blank=True, verbose_name='display URL')), + ('external_url', models.URLField(verbose_name='External URL')), + ('annotation', models.TextField(blank=True, verbose_name='Annotation')), + ('contact_name', models.CharField(verbose_name='Contact Name', max_length=100)), + ('contact_email', models.EmailField(verbose_name='Contact Email', max_length=254)), + ('added', models.DateTimeField(default=datetime.datetime.now, verbose_name='added')), + ('active', models.BooleanField(default=False, verbose_name='active')), + ('web_logo_benefit', models.NullBooleanField(verbose_name='Web logo benefit', help_text='Web logo benefit is complete')), + ('print_logo_benefit', models.NullBooleanField(verbose_name='Print logo benefit', help_text='Print logo benefit is complete')), + ('print_description_benefit', models.NullBooleanField(verbose_name='Print description benefit', help_text='Print description benefit is complete')), + ('company_description_benefit', models.NullBooleanField(verbose_name='Company description benefit', help_text='Company description benefit is complete')), + ('applicant', models.ForeignKey(to=settings.AUTH_USER_MODEL, null=True, related_name='sponsorships', verbose_name='Applicant')), + ], + options={ + 'verbose_name_plural': 'Sponsors', + 'ordering': ['name'], + 'verbose_name': 'Sponsor', + }, + ), + migrations.CreateModel( + name='SponsorBenefit', + fields=[ + ('id', models.AutoField(serialize=False, auto_created=True, primary_key=True, verbose_name='ID')), + ('active', models.BooleanField(default=True, verbose_name='Active')), + ('max_words', models.PositiveIntegerField(blank=True, verbose_name='Max words', null=True)), + ('other_limits', models.CharField(blank=True, verbose_name='Other limits', max_length=200)), + ('text', models.TextField(blank=True, verbose_name='Text')), + ('upload', models.FileField(blank=True, verbose_name='File', upload_to='sponsor_files')), + ('is_complete', models.NullBooleanField(verbose_name='Complete?', help_text='True - benefit complete; False - benefit incomplete; Null - n/a')), + ('benefit', models.ForeignKey(to='symposion_sponsorship.Benefit', related_name='sponsor_benefits', verbose_name='Benefit')), + ('sponsor', models.ForeignKey(to='symposion_sponsorship.Sponsor', related_name='sponsor_benefits', verbose_name='Sponsor')), + ], + options={ + 'verbose_name_plural': 'Sponsor benefits', + 'ordering': ['-active'], + 'verbose_name': 'Sponsor benefit', + }, + ), + migrations.CreateModel( + name='SponsorLevel', + fields=[ + ('id', models.AutoField(serialize=False, auto_created=True, primary_key=True, verbose_name='ID')), + ('name', models.CharField(verbose_name='Name', max_length=100)), + ('order', models.IntegerField(default=0, verbose_name='Order')), + ('cost', models.PositiveIntegerField(verbose_name='Cost')), + ('description', models.TextField(blank=True, verbose_name='Description', help_text='This is private.')), + ('conference', models.ForeignKey(to='symposion_conference.Conference', verbose_name='Conference')), + ], + options={ + 'verbose_name_plural': 'Sponsor levels', + 'ordering': ['conference', 'order'], + 'verbose_name': 'Sponsor level', + }, + ), + migrations.AddField( + model_name='sponsor', + name='level', + field=models.ForeignKey(to='symposion_sponsorship.SponsorLevel', verbose_name='level'), + ), + migrations.AddField( + model_name='sponsor', + name='sponsor_logo', + field=models.ForeignKey(blank=True, to='symposion_sponsorship.SponsorBenefit', null=True, related_name='+', verbose_name='Sponsor logo', editable=False), + ), + migrations.AddField( + model_name='benefitlevel', + name='level', + field=models.ForeignKey(to='symposion_sponsorship.SponsorLevel', related_name='benefit_levels', verbose_name='Level'), + ), + ] diff --git a/symposion/sponsorship/migrations/__init__.py b/symposion/sponsorship/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/symposion/sponsorship/urls.py b/symposion/sponsorship/urls.py index d7027fea..3ab17a72 100644 --- a/symposion/sponsorship/urls.py +++ b/symposion/sponsorship/urls.py @@ -4,7 +4,7 @@ from django.views.generic import TemplateView urlpatterns = patterns( "symposion.sponsorship.views", - url(r"^$", TemplateView.as_view(template_name="sponsorship/list.html"), name="sponsor_list"), + url(r"^$", TemplateView.as_view(template_name="symposion/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"), diff --git a/symposion/sponsorship/views.py b/symposion/sponsorship/views.py index 1fa5cf2b..35b7b16f 100644 --- a/symposion/sponsorship/views.py +++ b/symposion/sponsorship/views.py @@ -9,12 +9,13 @@ from zipfile import ZipFile, ZipInfo from django.conf import settings from django.contrib import messages from django.contrib.admin.views.decorators import staff_member_required -from django.contrib.auth.decorators import login_required 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 account.decorators import login_required + from symposion.sponsorship.forms import SponsorApplicationForm, \ SponsorDetailsForm, SponsorBenefitsFormSet from symposion.sponsorship.models import Benefit, Sponsor, SponsorBenefit, \ @@ -43,7 +44,7 @@ def sponsor_apply(request): else: form = SponsorApplicationForm(user=request.user) - return render_to_response("sponsorship/apply.html", { + return render_to_response("symposion/sponsorship/apply.html", { "form": form, }, context_instance=RequestContext(request)) @@ -63,7 +64,7 @@ def sponsor_add(request): else: form = SponsorApplicationForm(user=request.user) - return render_to_response("sponsorship/add.html", { + return render_to_response("symposion/sponsorship/add.html", { "form": form, }, context_instance=RequestContext(request)) @@ -96,7 +97,7 @@ def sponsor_detail(request, pk): form = SponsorDetailsForm(instance=sponsor) formset = SponsorBenefitsFormSet(**formset_kwargs) - return render_to_response("sponsorship/detail.html", { + return render_to_response("symposion/sponsorship/detail.html", { "sponsor": sponsor, "form": form, "formset": formset, diff --git a/symposion/teams/migrations/0001_initial.py b/symposion/teams/migrations/0001_initial.py new file mode 100644 index 00000000..3bdd4120 --- /dev/null +++ b/symposion/teams/migrations/0001_initial.py @@ -0,0 +1,56 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations +import datetime +from django.conf import settings + + +class Migration(migrations.Migration): + + dependencies = [ + ('auth', '0006_require_contenttypes_0002'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Membership', + fields=[ + ('id', models.AutoField(serialize=False, auto_created=True, primary_key=True, verbose_name='ID')), + ('state', models.CharField(max_length=20, choices=[('applied', 'applied'), ('invited', 'invited'), ('declined', 'declined'), ('rejected', 'rejected'), ('member', 'member'), ('manager', 'manager')], verbose_name='State')), + ('message', models.TextField(blank=True, verbose_name='Message')), + ], + options={ + 'verbose_name_plural': 'Memberships', + 'verbose_name': 'Membership', + }, + ), + migrations.CreateModel( + name='Team', + fields=[ + ('id', models.AutoField(serialize=False, auto_created=True, primary_key=True, verbose_name='ID')), + ('slug', models.SlugField(unique=True, verbose_name='Slug')), + ('name', models.CharField(max_length=100, verbose_name='Name')), + ('description', models.TextField(blank=True, verbose_name='Description')), + ('access', models.CharField(max_length=20, choices=[('open', 'open'), ('application', 'by application'), ('invitation', 'by invitation')], verbose_name='Access')), + ('created', models.DateTimeField(editable=False, default=datetime.datetime.now, verbose_name='Created')), + ('manager_permissions', models.ManyToManyField(related_name='manager_teams', blank=True, to='auth.Permission', verbose_name='Manager permissions')), + ('permissions', models.ManyToManyField(related_name='member_teams', blank=True, to='auth.Permission', verbose_name='Permissions')), + ], + options={ + 'verbose_name_plural': 'Teams', + 'verbose_name': 'Team', + }, + ), + migrations.AddField( + model_name='membership', + name='team', + field=models.ForeignKey(verbose_name='Team', to='teams.Team', related_name='memberships'), + ), + migrations.AddField( + model_name='membership', + name='user', + field=models.ForeignKey(verbose_name='User', to=settings.AUTH_USER_MODEL, related_name='memberships'), + ), + ] diff --git a/symposion/teams/migrations/__init__.py b/symposion/teams/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/symposion/teams/views.py b/symposion/teams/views.py index 3a245a84..78cf7a88 100644 --- a/symposion/teams/views.py +++ b/symposion/teams/views.py @@ -2,12 +2,12 @@ from __future__ import unicode_literals from django.http import Http404, HttpResponseNotAllowed from django.shortcuts import render, redirect, get_object_or_404 -from django.contrib.auth.decorators import login_required from django.contrib import messages - -from symposion.utils.mail import send_email from django.utils.translation import ugettext_lazy as _ +from account.decorators import login_required + +from symposion.utils.mail import send_email from symposion.teams.forms import TeamInvitationForm from symposion.teams.models import Team, Membership @@ -75,7 +75,7 @@ def team_detail(request, slug): else: form = None - return render(request, "teams/team_detail.html", { + return render(request, "symposion/teams/team_detail.html", { "team": team, "state": state, "invite_form": form, diff --git a/symposion/utils/mail.py b/symposion/utils/mail.py index 59cdf09c..e9759d09 100644 --- a/symposion/utils/mail.py +++ b/symposion/utils/mail.py @@ -17,10 +17,10 @@ def send_email(to, kind, **kwargs): ctx.update(kwargs.get("context", {})) subject = "[%s] %s" % ( current_site.name, - render_to_string("emails/%s/subject.txt" % kind, ctx).strip() + render_to_string("symposion/emails/%s/subject.txt" % kind, ctx).strip() ) - message_html = render_to_string("emails/%s/message.html" % kind, ctx) + message_html = render_to_string("symposion/emails/%s/message.html" % kind, ctx) message_plaintext = strip_tags(message_html) from_email = settings.DEFAULT_FROM_EMAIL diff --git a/symposion/views.py b/symposion/views.py index 3b5882d4..1cffbc11 100644 --- a/symposion/views.py +++ b/symposion/views.py @@ -1,53 +1,9 @@ from __future__ import unicode_literals -import hashlib -import random from django.shortcuts import render, redirect -from django.contrib.auth.models import User -from django.contrib.auth.decorators import login_required -import account.views - -import symposion.forms - - -class SignupView(account.views.SignupView): - - form_class = symposion.forms.SignupForm - form_kwargs = { - "prefix": "signup", - } - - def create_user(self, form, commit=True): - user_kwargs = { - "first_name": form.cleaned_data["first_name"], - "last_name": form.cleaned_data["last_name"] - } - return super(SignupView, self).create_user(form, commit=commit, - **user_kwargs) - - def generate_username(self, form): - def random_username(): - h = hashlib.sha1(form.cleaned_data["email"]).hexdigest()[:25] - # don't ask - n = random.randint(1, (10 ** (5 - 1)) - 1) - return "%s%d" % (h, n) - while True: - try: - username = random_username() - User.objects.get(username=username) - except User.DoesNotExist: - break - return username - - -class LoginView(account.views.LoginView): - - form_class = account.forms.LoginEmailForm - form_kwargs = { - "prefix": "login", - } +from account.decorators import login_required @login_required From 031ee3807e847a8f8f481eec2043c019f3589c7f Mon Sep 17 00:00:00 2001 From: Patrick Altman Date: Fri, 16 Oct 2015 19:54:11 -0500 Subject: [PATCH 627/751] Fix typo --- symposion/reviews/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/symposion/reviews/models.py b/symposion/reviews/models.py index b1fa767f..1419e134 100644 --- a/symposion/reviews/models.py +++ b/symposion/reviews/models.py @@ -312,7 +312,7 @@ class Comment(models.Model): verbose_name_plural = _("comments") def save(self, *args, **kwargs): - self.comment_html = parse(self.comment) + self.text_html = parse(self.text) return super(Comment, self).save(*args, **kwargs) From d68e310089da8ed38ac0b36328505bfddd902402 Mon Sep 17 00:00:00 2001 From: Hiroshi Miura Date: Sat, 17 Oct 2015 10:57:18 +0900 Subject: [PATCH 628/751] fix pip8 warnings Signed-off-by: Hiroshi Miura --- symposion/markdown_parser.py | 1 - symposion/speakers/models.py | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/symposion/markdown_parser.py b/symposion/markdown_parser.py index 22dc240c..b3eaa53c 100644 --- a/symposion/markdown_parser.py +++ b/symposion/markdown_parser.py @@ -1,5 +1,4 @@ from __future__ import unicode_literals -from html5lib import html5parser, sanitizer import markdown diff --git a/symposion/speakers/models.py b/symposion/speakers/models.py index a6e22e49..57d113d7 100644 --- a/symposion/speakers/models.py +++ b/symposion/speakers/models.py @@ -25,9 +25,9 @@ class Speaker(models.Model): help_text=_("As you would like it to appear in the" " conference program.")) biography = models.TextField(blank=True, help_text=_("A little bit about you. Edit using " - "" - "Markdown."), verbose_name=_("Biography")) + "" + "Markdown."), verbose_name=_("Biography")) biography_html = models.TextField(blank=True) photo = models.ImageField(upload_to="speaker_photos", blank=True, verbose_name=_("Photo")) annotation = models.TextField(verbose_name=_("Annotation")) # staff only From b50a42f24390b118595f1eb9aa80b4b719f001e3 Mon Sep 17 00:00:00 2001 From: Didier Raboud Date: Mon, 26 Oct 2015 12:40:22 +0100 Subject: [PATCH 629/751] Fix SponsorBenefit __str__ by using self.benefit.type instead of self.benefit_type --- symposion/sponsorship/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/symposion/sponsorship/models.py b/symposion/sponsorship/models.py index 30a086ff..f21deb17 100644 --- a/symposion/sponsorship/models.py +++ b/symposion/sponsorship/models.py @@ -287,7 +287,7 @@ class SponsorBenefit(models.Model): verbose_name_plural = _("Sponsor benefits") def __str__(self): - return "%s - %s (%s)" % (self.sponsor, self.benefit, self.benefit_type) + return "%s - %s (%s)" % (self.sponsor, self.benefit, self.benefit.type) def save(self, *args, **kwargs): # Validate - save() doesn't clean your model by default, so call From 75f9739c5ce0b8a5ae47fca92d2f41fc64a9ced5 Mon Sep 17 00:00:00 2001 From: Anna Ossowski Date: Tue, 27 Oct 2015 20:16:28 +0100 Subject: [PATCH 630/751] Update README.rst --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 8f3cd683..0683ca51 100644 --- a/README.rst +++ b/README.rst @@ -47,7 +47,7 @@ Django startproject template that includes Symposion (https://github.com/pinax/p Documentation --------------- - +The symposion documentation is available at https://symposion.readthedocs.org/en/latest/. The Pinax documentation is available at http://pinaxproject.com/pinax/. From 5b9f4fb71ed74b8c944c332e6b23763188fe636b Mon Sep 17 00:00:00 2001 From: Martey Dodoo Date: Sat, 12 Dec 2015 15:34:58 -0500 Subject: [PATCH 631/751] Loosen Django package requirement. Loosen Django requirement to allow versions greater or equal to 1.8.5, instead of mandating 1.8.5. This makes it easier to use newer releases of Django (e.g. the bugfix and security releases 1.8.6 or 1.8.7) with symposion. --- requirements/base.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/base.txt b/requirements/base.txt index 4965ead4..6bf45324 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -1,4 +1,4 @@ -Django==1.8.5 +Django>=1.8.5 django-appconf==1.0.1 django-model-utils==2.3.1 django-reversion==1.9.3 From ddc3553656096e8cfaa5f4dd6d88783b02c697a4 Mon Sep 17 00:00:00 2001 From: Martey Dodoo Date: Thu, 31 Dec 2015 12:35:22 -0500 Subject: [PATCH 632/751] Remove duplicate Slot.save method. Remove "save" method in Slot model since #107 added another "save" method. This fixes the automated test failures in Travis CI. --- symposion/schedule/models.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/symposion/schedule/models.py b/symposion/schedule/models.py index da1a649e..e471a522 100644 --- a/symposion/schedule/models.py +++ b/symposion/schedule/models.py @@ -90,10 +90,6 @@ class Slot(models.Model): content_override = models.TextField(blank=True, verbose_name=_("Content override")) content_override_html = models.TextField(blank=True) - def save(self, *args, **kwargs): - self.content_override_html = parse(self.content_override) - return super(Slot, self).save(*args, **kwargs) - def assign(self, content): """ Assign the given content to this slot and if a previous slot content From 47c28439d98acb79ad5170a3e7645ece43a095d4 Mon Sep 17 00:00:00 2001 From: Mjumbe Wawatu Ukweli Date: Tue, 12 Jan 2016 20:39:55 -0500 Subject: [PATCH 633/751] Python3 compatibility Update the StringIO import in symposion.sponsorship.views --- symposion/sponsorship/views.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/symposion/sponsorship/views.py b/symposion/sponsorship/views.py index 35b7b16f..3f749900 100644 --- a/symposion/sponsorship/views.py +++ b/symposion/sponsorship/views.py @@ -1,5 +1,9 @@ from __future__ import unicode_literals -from cStringIO import StringIO +try: + from io import StringIO +except: + # Python 2 + from cStringIO import StringIO import itertools import logging import os From 4f59d7a9a165e39012f8084d47da84e5d3a585ca Mon Sep 17 00:00:00 2001 From: Anna Ossowski Date: Thu, 21 Jan 2016 01:16:12 +0100 Subject: [PATCH 634/751] Update README.rst --- README.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.rst b/README.rst index 0683ca51..0c09553f 100644 --- a/README.rst +++ b/README.rst @@ -50,6 +50,15 @@ Documentation The symposion documentation is available at https://symposion.readthedocs.org/en/latest/. The Pinax documentation is available at http://pinaxproject.com/pinax/. +Contribute +---------------- + +See our How to Contribute (http://pinaxproject.com/pinax/how_to_contribute/) section for an overview on how contributing to Pinax works. For concrete contribution ideas, please see our Ways to Contribute/What We Need Help With (http://pinaxproject.com/pinax/ways_to_contribute/) section. + +In case of any questions, we would recommend for you to join our Pinax Slack team (http://slack.pinaxproject.com) and ping us there instead of creating an issue on GitHub. Creating issues on GitHub is of course also valid but we are usually able to help you faster if you ping us in Slack. + +We would also highly recommend for your to read our Open Source and Self-Care blog post (http://blog.pinaxproject.com/2016/01/19/open-source-and-self-care/). + Code of Conduct ---------------- From a487f78f7e17a58c5672b2dcf43363951c6412f1 Mon Sep 17 00:00:00 2001 From: Martey Dodoo Date: Mon, 25 Jan 2016 15:13:26 -0500 Subject: [PATCH 635/751] Make sponsor description default empty string. Set sponsor listing text to an empty string instead of None. If a sponsor doesn't have a benefit named "Sponsor Description", this will prevent the text "None" from displaying in the Django template. --- symposion/sponsorship/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/symposion/sponsorship/models.py b/symposion/sponsorship/models.py index f21deb17..f28bc57e 100644 --- a/symposion/sponsorship/models.py +++ b/symposion/sponsorship/models.py @@ -136,7 +136,7 @@ class Sponsor(models.Model): @property def listing_text(self): if not hasattr(self, "_listing_text"): - self._listing_text = None + self._listing_text = "" # @@@ better than hard-coding a pk but still not good benefits = self.sponsor_benefits.filter(benefit__name="Sponsor Description") if benefits.count(): From 47b65ac3304ebca995cbfef2c48e2e6fe8805d4f Mon Sep 17 00:00:00 2001 From: Anna Ossowski Date: Mon, 1 Feb 2016 20:45:16 +0100 Subject: [PATCH 636/751] Update README.rst --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 0c09553f..e158c8f5 100644 --- a/README.rst +++ b/README.rst @@ -63,7 +63,7 @@ We would also highly recommend for your to read our Open Source and Self-Care bl Code of Conduct ---------------- -In order to foster a kind, inclusive, and harassment-free community, the Pinax Project has a code of conduct, which can be found here http://pinaxproject.com/pinax/code_of_conduct/. +In order to foster a kind, inclusive, and harassment-free community, the Pinax Project has a code of conduct, which can be found here http://pinaxproject.com/pinax/code_of_conduct/. We'd like to ask you to treat everyone as a smart human programmer that shares an interest in Python, Django, and Pinax with you. Pinax Project Blog and Twitter From 6b41b5c4773c6ef28a89a0d23c787bd81ab4554f Mon Sep 17 00:00:00 2001 From: Martey Dodoo Date: Sat, 20 Feb 2016 08:20:09 -0500 Subject: [PATCH 637/751] Don't show unpublished schedule info to non-staff. Add checks to schedule_list, schedule_list_csv, and schedule_presentation_detail views to verify that either schedule is published or that current user is staff before displaying information. See c7592bc33e2626d84ec46fe1e7ba8e05c73ef74c. --- symposion/schedule/views.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/symposion/schedule/views.py b/symposion/schedule/views.py index cd6ad2cd..0ff3903f 100644 --- a/symposion/schedule/views.py +++ b/symposion/schedule/views.py @@ -69,6 +69,8 @@ def schedule_detail(request, slug=None): def schedule_list(request, slug=None): schedule = fetch_schedule(slug) + if not schedule.published and not request.user.is_staff: + raise Http404() presentations = Presentation.objects.filter(section=schedule.section) presentations = presentations.exclude(cancelled=True) @@ -82,6 +84,8 @@ def schedule_list(request, slug=None): def schedule_list_csv(request, slug=None): schedule = fetch_schedule(slug) + if not schedule.published and not request.user.is_staff: + raise Http404() presentations = Presentation.objects.filter(section=schedule.section) presentations = presentations.exclude(cancelled=True).order_by("id") @@ -169,6 +173,8 @@ def schedule_presentation_detail(request, pk): presentation = get_object_or_404(Presentation, pk=pk) if presentation.slot: schedule = presentation.slot.day.schedule + if not schedule.published and not request.user.is_staff: + raise Http404() else: schedule = None From a1cbcdddd5d1f4d3166cc782cf1cb1f067fc2641 Mon Sep 17 00:00:00 2001 From: Patrick Altman Date: Sat, 20 Feb 2016 17:06:09 -0600 Subject: [PATCH 638/751] Add back in parsing the content override into html --- symposion/schedule/models.py | 1 + 1 file changed, 1 insertion(+) diff --git a/symposion/schedule/models.py b/symposion/schedule/models.py index e471a522..859faea1 100644 --- a/symposion/schedule/models.py +++ b/symposion/schedule/models.py @@ -149,6 +149,7 @@ class Slot(models.Model): def save(self, *args, **kwargs): roomlist = ' '.join(map(lambda r: r.__unicode__(), self.rooms)) self.name = "%s %s (%s - %s) %s" % (self.day, self.kind, self.start, self.end, roomlist) + self.content_override_html = parse(self.content_override) super(Slot, self).save(*args, **kwargs) def __str__(self): From 90768e786fbccfbff64a62a533fb80e4fb6bb7e5 Mon Sep 17 00:00:00 2001 From: Anna Ossowski Date: Fri, 26 Feb 2016 23:33:32 +0100 Subject: [PATCH 639/751] Update README.rst --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index e158c8f5..0cc5fccf 100644 --- a/README.rst +++ b/README.rst @@ -53,7 +53,7 @@ The Pinax documentation is available at http://pinaxproject.com/pinax/. Contribute ---------------- -See our How to Contribute (http://pinaxproject.com/pinax/how_to_contribute/) section for an overview on how contributing to Pinax works. For concrete contribution ideas, please see our Ways to Contribute/What We Need Help With (http://pinaxproject.com/pinax/ways_to_contribute/) section. +See this blog post http://blog.pinaxproject.com/2016/02/26/recap-february-pinax-hangout/ including a video, or our How to Contribute (http://pinaxproject.com/pinax/how_to_contribute/) section for an overview on how contributing to Pinax works. For concrete contribution ideas, please see our Ways to Contribute/What We Need Help With (http://pinaxproject.com/pinax/ways_to_contribute/) section. In case of any questions, we would recommend for you to join our Pinax Slack team (http://slack.pinaxproject.com) and ping us there instead of creating an issue on GitHub. Creating issues on GitHub is of course also valid but we are usually able to help you faster if you ping us in Slack. From 8a744925b9b4572dea0bad77a0d762752ba6b1a2 Mon Sep 17 00:00:00 2001 From: Anna Ossowski Date: Fri, 26 Feb 2016 23:34:34 +0100 Subject: [PATCH 640/751] Update README.rst --- README.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.rst b/README.rst index 0cc5fccf..fcd7f46a 100644 --- a/README.rst +++ b/README.rst @@ -30,24 +30,24 @@ This collection can be found at http://pinaxproject.com. symposion ---------- -symposion is a conference management solution from Eldarion. It was built with the generous support of the Python Software Foundation. See http://eldarion.com/symposion/ for commercial support, customization and hosting. +``symposion`` is a conference management solution from Eldarion. It was built with the generous support of the Python Software Foundation. See http://eldarion.com/symposion/ for commercial support, customization and hosting. Quickstart ========== -To install Symposion, run: +To install ``symposion``, run: pip install symposion -Symposion is a Django app. You will need to create a Django project to +``symposion`` is a Django app. You will need to create a Django project to customize and manage your Symposion installation. We have built a basic -Django startproject template that includes Symposion (https://github.com/pinax/pinax-project-symposion). +Django startproject template that includes ``symposion`` (https://github.com/pinax/pinax-project-symposion). Documentation --------------- -The symposion documentation is available at https://symposion.readthedocs.org/en/latest/. +The ``symposion`` documentation is available at https://symposion.readthedocs.org/en/latest/. The Pinax documentation is available at http://pinaxproject.com/pinax/. Contribute From efe413a837a85bf244334081f66f93dc6b28b1c6 Mon Sep 17 00:00:00 2001 From: Patrick Altman Date: Sat, 27 Feb 2016 16:35:06 -0600 Subject: [PATCH 641/751] Bump requirements --- requirements/base.txt | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/requirements/base.txt b/requirements/base.txt index 6bf45324..a5e06485 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -1,12 +1,12 @@ -Django>=1.8.5 +Django>=1.9.2 django-appconf==1.0.1 -django-model-utils==2.3.1 -django-reversion==1.9.3 -django-sitetree==1.4.0 -django-taggit==0.17.1 +django-model-utils==2.4.0 +django-reversion==1.10.1 +django-sitetree==1.5.1 +django-taggit==0.18.0 django-timezone-field==1.3 -django-user-accounts==1.2.0 -easy-thumbnails==2.2 +django-user-accounts==1.3.1 +easy-thumbnails==2.3 html5lib==0.9999999 -markdown==2.6.2 -pytz==2015.6 +markdown==2.6.5 +pytz==2015.7 From 1ab7a304356b01e9ac7fc5cf66924ae7e9147647 Mon Sep 17 00:00:00 2001 From: Patrick Altman Date: Sat, 27 Feb 2016 16:38:27 -0600 Subject: [PATCH 642/751] Update for BI changes in django-reversion --- symposion/proposals/models.py | 3 +-- symposion/teams/admin.py | 4 ++-- symposion/teams/models.py | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/symposion/proposals/models.py b/symposion/proposals/models.py index cf8e56b4..23f65608 100644 --- a/symposion/proposals/models.py +++ b/symposion/proposals/models.py @@ -13,9 +13,8 @@ from django.contrib.auth.models import User from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ValidationError -import reversion - from model_utils.managers import InheritanceManager +from reversion import revisions as reversion from symposion.markdown_parser import parse from symposion.conference.models import Section diff --git a/symposion/teams/admin.py b/symposion/teams/admin.py index 5175e302..1a27c7ee 100644 --- a/symposion/teams/admin.py +++ b/symposion/teams/admin.py @@ -1,6 +1,6 @@ from django.contrib import admin -import reversion +from reversion.admin import VersionAdmin from symposion.teams.models import Team, Membership @@ -8,7 +8,7 @@ admin.site.register(Team, prepopulated_fields={"slug": ("name",)}) -class MembershipAdmin(reversion.VersionAdmin): +class MembershipAdmin(VersionAdmin): list_display = ["team", "user", "state"] list_filter = ["team"] search_fields = ["user__username"] diff --git a/symposion/teams/models.py b/symposion/teams/models.py index ec4d5e45..774dfbd8 100644 --- a/symposion/teams/models.py +++ b/symposion/teams/models.py @@ -8,7 +8,7 @@ from django.utils.translation import ugettext_lazy as _ from django.contrib.auth.models import Permission, User -import reversion +from reversion import revisions as reversion TEAM_ACCESS_CHOICES = [ From 7b77b9aa2948093423e5665c941846ff7bedffa1 Mon Sep 17 00:00:00 2001 From: Patrick Altman Date: Sat, 27 Feb 2016 16:54:42 -0600 Subject: [PATCH 643/751] Drop deprecated url patterns format --- symposion/conference/urls.py | 10 +++--- symposion/proposals/urls.py | 49 +++++++++++++++++------------ symposion/reviews/urls.py | 58 +++++++++++++++++++++-------------- symposion/schedule/urls.py | 51 +++++++++++++++++------------- symposion/speakers/urls.py | 26 +++++++++------- symposion/sponsorship/urls.py | 23 ++++++++------ symposion/teams/urls.py | 34 ++++++++++++-------- 7 files changed, 150 insertions(+), 101 deletions(-) diff --git a/symposion/conference/urls.py b/symposion/conference/urls.py index 43a42a5b..b9080681 100644 --- a/symposion/conference/urls.py +++ b/symposion/conference/urls.py @@ -1,7 +1,7 @@ -from django.conf.urls import patterns, url +from django.conf.urls import url +from .views import user_list -urlpatterns = patterns( - "symposion.conference.views", - url(r"^users/$", "user_list", name="user_list"), -) +urlpatterns = [ + url(r"^users/$", user_list, name="user_list"), +] diff --git a/symposion/proposals/urls.py b/symposion/proposals/urls.py index 2a0a6ffe..03b2993c 100644 --- a/symposion/proposals/urls.py +++ b/symposion/proposals/urls.py @@ -1,21 +1,32 @@ -from django.conf.urls import patterns, url +from django.conf.urls import url - -urlpatterns = patterns( - "symposion.proposals.views", - url(r"^submit/$", "proposal_submit", name="proposal_submit"), - url(r"^submit/([\w\-]+)/$", "proposal_submit_kind", - name="proposal_submit_kind"), - url(r"^(\d+)/$", "proposal_detail", name="proposal_detail"), - url(r"^(\d+)/edit/$", "proposal_edit", name="proposal_edit"), - url(r"^(\d+)/speakers/$", "proposal_speaker_manage", - name="proposal_speaker_manage"), - url(r"^(\d+)/cancel/$", "proposal_cancel", name="proposal_cancel"), - url(r"^(\d+)/leave/$", "proposal_leave", name="proposal_leave"), - url(r"^(\d+)/join/$", "proposal_pending_join", name="proposal_pending_join"), - url(r"^(\d+)/decline/$", "proposal_pending_decline", name="proposal_pending_decline"), - - url(r"^(\d+)/document/create/$", "document_create", name="proposal_document_create"), - url(r"^document/(\d+)/delete/$", "document_delete", name="proposal_document_delete"), - url(r"^document/(\d+)/([^/]+)$", "document_download", name="proposal_document_download"), +from .views import ( + proposal_submit, + proposal_submit_kind, + proposal_detail, + proposal_edit, + proposal_speaker_manage, + proposal_cancel, + proposal_leave, + proposal_pending_join, + proposal_pending_decline, + document_create, + document_delete, + document_download, ) + +urlpatterns = [ + url(r"^submit/$", proposal_submit, name="proposal_submit"), + url(r"^submit/([\w\-]+)/$", proposal_submit_kind, name="proposal_submit_kind"), + url(r"^(\d+)/$", proposal_detail, name="proposal_detail"), + url(r"^(\d+)/edit/$", proposal_edit, name="proposal_edit"), + url(r"^(\d+)/speakers/$", proposal_speaker_manage, name="proposal_speaker_manage"), + url(r"^(\d+)/cancel/$", proposal_cancel, name="proposal_cancel"), + url(r"^(\d+)/leave/$", proposal_leave, name="proposal_leave"), + url(r"^(\d+)/join/$", proposal_pending_join, name="proposal_pending_join"), + url(r"^(\d+)/decline/$", proposal_pending_decline, name="proposal_pending_decline"), + + url(r"^(\d+)/document/create/$", document_create, name="proposal_document_create"), + url(r"^document/(\d+)/delete/$", document_delete, name="proposal_document_delete"), + url(r"^document/(\d+)/([^/]+)$", document_download, name="proposal_document_download"), +] diff --git a/symposion/reviews/urls.py b/symposion/reviews/urls.py index 55196c65..ba100c71 100644 --- a/symposion/reviews/urls.py +++ b/symposion/reviews/urls.py @@ -1,25 +1,37 @@ -from django.conf.urls import patterns, url +from django.conf.urls import url - -urlpatterns = patterns( - "symposion.reviews.views", - url(r"^section/(?P[\w\-]+)/all/$", "review_section", {"reviewed": "all"}, name="review_section"), - url(r"^section/(?P[\w\-]+)/reviewed/$", "review_section", {"reviewed": "reviewed"}, name="user_reviewed"), - url(r"^section/(?P[\w\-]+)/not_reviewed/$", "review_section", {"reviewed": "not_reviewed"}, name="user_not_reviewed"), - url(r"^section/(?P[\w\-]+)/assignments/$", "review_section", {"assigned": True}, name="review_section_assignments"), - url(r"^section/(?P[\w\-]+)/status/$", "review_status", name="review_status"), - url(r"^section/(?P[\w\-]+)/status/(?P\w+)/$", "review_status", name="review_status"), - url(r"^section/(?P[\w\-]+)/list/(?P\d+)/$", "review_list", name="review_list_user"), - url(r"^section/(?P[\w\-]+)/admin/$", "review_admin", name="review_admin"), - url(r"^section/(?P[\w\-]+)/admin/accept/$", "review_bulk_accept", name="review_bulk_accept"), - url(r"^section/(?P[\w\-]+)/notification/(?P\w+)/$", "result_notification", name="result_notification"), - url(r"^section/(?P[\w\-]+)/notification/(?P\w+)/prepare/$", "result_notification_prepare", name="result_notification_prepare"), - url(r"^section/(?P[\w\-]+)/notification/(?P\w+)/send/$", "result_notification_send", name="result_notification_send"), - - url(r"^review/(?P\d+)/$", "review_detail", name="review_detail"), - - url(r"^(?P\d+)/delete/$", "review_delete", name="review_delete"), - url(r"^assignments/$", "review_assignments", name="review_assignments"), - url(r"^assignment/(?P\d+)/opt-out/$", "review_assignment_opt_out", - name="review_assignment_opt_out"), +from .views import ( + review_section, + review_status, + review_list, + review_admin, + review_bulk_accept, + result_notification, + result_notification_prepare, + result_notification_send, + review_detail, + review_delete, + review_assignments, + review_assignment_opt_out, ) + +urlpatterns = [ + url(r"^section/(?P[\w\-]+)/all/$", review_section, {"reviewed": "all"}, name="review_section"), + url(r"^section/(?P[\w\-]+)/reviewed/$", review_section, {"reviewed": "reviewed"}, name="user_reviewed"), + url(r"^section/(?P[\w\-]+)/not_reviewed/$", review_section, {"reviewed": "not_reviewed"}, name="user_not_reviewed"), + url(r"^section/(?P[\w\-]+)/assignments/$", review_section, {"assigned": True}, name="review_section_assignments"), + url(r"^section/(?P[\w\-]+)/status/$", review_status, name="review_status"), + url(r"^section/(?P[\w\-]+)/status/(?P\w+)/$", review_status, name="review_status"), + url(r"^section/(?P[\w\-]+)/list/(?P\d+)/$", review_list, name="review_list_user"), + url(r"^section/(?P[\w\-]+)/admin/$", review_admin, name="review_admin"), + url(r"^section/(?P[\w\-]+)/admin/accept/$", review_bulk_accept, name="review_bulk_accept"), + url(r"^section/(?P[\w\-]+)/notification/(?P\w+)/$", result_notification, name="result_notification"), + url(r"^section/(?P[\w\-]+)/notification/(?P\w+)/prepare/$", result_notification_prepare, name="result_notification_prepare"), + url(r"^section/(?P[\w\-]+)/notification/(?P\w+)/send/$", result_notification_send, name="result_notification_send"), + + url(r"^review/(?P\d+)/$", review_detail, name="review_detail"), + + url(r"^(?P\d+)/delete/$", review_delete, name="review_delete"), + url(r"^assignments/$", review_assignments, name="review_assignments"), + url(r"^assignment/(?P\d+)/opt-out/$", review_assignment_opt_out, name="review_assignment_opt_out") +] diff --git a/symposion/schedule/urls.py b/symposion/schedule/urls.py index 8ac3bdc2..28b05f1f 100644 --- a/symposion/schedule/urls.py +++ b/symposion/schedule/urls.py @@ -1,24 +1,33 @@ from __future__ import unicode_literals -from django.conf.urls import url, patterns +from django.conf.urls import url - -urlpatterns = patterns( - "symposion.schedule.views", - url(r"^$", "schedule_conference", name="schedule_conference"), - url(r"^edit/$", "schedule_edit", name="schedule_edit"), - url(r"^list/$", "schedule_list", name="schedule_list"), - url(r"^presentations.csv$", "schedule_list_csv", name="schedule_list_csv"), - url(r"^presentation/(\d+)/$", "schedule_presentation_detail", - name="schedule_presentation_detail"), - url(r"^([\w\-]+)/$", "schedule_detail", name="schedule_detail"), - url(r"^([\w\-]+)/edit/$", "schedule_edit", name="schedule_edit"), - url(r"^([\w\-]+)/list/$", "schedule_list", name="schedule_list"), - url(r"^([\w\-]+)/presentations.csv$", "schedule_list_csv", - name="schedule_list_csv"), - url(r"^([\w\-]+)/edit/slot/(\d+)/", "schedule_slot_edit", - name="schedule_slot_edit"), - url(r"^conference.json", "schedule_json", name="schedule_json"), - url(r"^sessions/staff.txt$", "session_staff_email", name="schedule_session_staff_email"), - url(r"^sessions/$", "session_list", name="schedule_session_list"), - url(r"^session/(\d+)/$", "session_detail", name="schedule_session_detail"), +from .views import ( + schedule_conference, + schedule_edit, + schedule_list, + schedule_list_csv, + schedule_presentation_detail, + schedule_detail, + schedule_slot_edit, + schedule_json, + session_staff_email, + session_list, + session_detail, ) + +urlpatterns = [ + url(r"^$", schedule_conference, name="schedule_conference"), + url(r"^edit/$", schedule_edit, name="schedule_edit"), + url(r"^list/$", schedule_list, name="schedule_list"), + url(r"^presentations.csv$", schedule_list_csv, name="schedule_list_csv"), + url(r"^presentation/(\d+)/$", schedule_presentation_detail, name="schedule_presentation_detail"), + url(r"^([\w\-]+)/$", schedule_detail, name="schedule_detail"), + url(r"^([\w\-]+)/edit/$", schedule_edit, name="schedule_edit"), + url(r"^([\w\-]+)/list/$", schedule_list, name="schedule_list"), + url(r"^([\w\-]+)/presentations.csv$", schedule_list_csv, name="schedule_list_csv"), + url(r"^([\w\-]+)/edit/slot/(\d+)/", schedule_slot_edit, name="schedule_slot_edit"), + url(r"^conference.json", schedule_json, name="schedule_json"), + url(r"^sessions/staff.txt$", session_staff_email, name="schedule_session_staff_email"), + url(r"^sessions/$", session_list, name="schedule_session_list"), + url(r"^session/(\d+)/$", session_detail, name="schedule_session_detail"), +] diff --git a/symposion/speakers/urls.py b/symposion/speakers/urls.py index 941f9497..dd1a7bd6 100644 --- a/symposion/speakers/urls.py +++ b/symposion/speakers/urls.py @@ -1,14 +1,18 @@ from __future__ import unicode_literals -from django.conf.urls import patterns, url +from django.conf.urls import url - -urlpatterns = patterns( - "symposion.speakers.views", - url(r"^create/$", "speaker_create", name="speaker_create"), - url(r"^create/(\w+)/$", "speaker_create_token", - name="speaker_create_token"), - url(r"^edit/(?:(?P\d+)/)?$", "speaker_edit", name="speaker_edit"), - url(r"^profile/(?P\d+)/$", "speaker_profile", name="speaker_profile"), - url(r"^staff/create/(\d+)/$", "speaker_create_staff", - name="speaker_create_staff"), +from .views import ( + speaker_create, + speaker_create_token, + speaker_edit, + speaker_profile, + speaker_create_staff ) + +urlpatterns = [ + url(r"^create/$", speaker_create, name="speaker_create"), + url(r"^create/(\w+)/$", speaker_create_token, name="speaker_create_token"), + url(r"^edit/(?:(?P\d+)/)?$", speaker_edit, name="speaker_edit"), + url(r"^profile/(?P\d+)/$", speaker_profile, name="speaker_profile"), + url(r"^staff/create/(\d+)/$", speaker_create_staff, name="speaker_create_staff"), +] diff --git a/symposion/sponsorship/urls.py b/symposion/sponsorship/urls.py index 3ab17a72..9fdc1927 100644 --- a/symposion/sponsorship/urls.py +++ b/symposion/sponsorship/urls.py @@ -1,12 +1,17 @@ -from django.conf.urls import patterns, url +from django.conf.urls import url from django.views.generic import TemplateView - -urlpatterns = patterns( - "symposion.sponsorship.views", - url(r"^$", TemplateView.as_view(template_name="symposion/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"), +from .views import ( + sponsor_apply, + sponsor_add, + sponsor_zip_logo_files, + sponsor_detail ) + +urlpatterns = [ + url(r"^$", TemplateView.as_view(template_name="symposion/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/teams/urls.py b/symposion/teams/urls.py index f6ddaf9d..60e441e9 100644 --- a/symposion/teams/urls.py +++ b/symposion/teams/urls.py @@ -1,17 +1,25 @@ -from django.conf.urls import patterns, url +from django.conf.urls import url - -urlpatterns = patterns( - "symposion.teams.views", +from .views import ( + team_detail, + team_join, + team_leave, + team_apply, + team_promote, + team_demote, + team_accept, + team_reject +) +urlpatterns = [ # team specific - url(r"^(?P[\w\-]+)/$", "team_detail", name="team_detail"), - url(r"^(?P[\w\-]+)/join/$", "team_join", name="team_join"), - url(r"^(?P[\w\-]+)/leave/$", "team_leave", name="team_leave"), - url(r"^(?P[\w\-]+)/apply/$", "team_apply", name="team_apply"), + url(r"^(?P[\w\-]+)/$", team_detail, name="team_detail"), + url(r"^(?P[\w\-]+)/join/$", team_join, name="team_join"), + url(r"^(?P[\w\-]+)/leave/$", team_leave, name="team_leave"), + url(r"^(?P[\w\-]+)/apply/$", team_apply, name="team_apply"), # membership specific - url(r"^promote/(?P\d+)/$", "team_promote", name="team_promote"), - url(r"^demote/(?P\d+)/$", "team_demote", name="team_demote"), - url(r"^accept/(?P\d+)/$", "team_accept", name="team_accept"), - url(r"^reject/(?P\d+)/$", "team_reject", name="team_reject"), -) + url(r"^promote/(?P\d+)/$", team_promote, name="team_promote"), + url(r"^demote/(?P\d+)/$", team_demote, name="team_demote"), + url(r"^accept/(?P\d+)/$", team_accept, name="team_accept"), + url(r"^reject/(?P\d+)/$", team_reject, name="team_reject"), +] From 78d13a9ffa45e15cc89c2c98432c38231ee05d6b Mon Sep 17 00:00:00 2001 From: Martey Dodoo Date: Thu, 10 Mar 2016 20:35:41 -0500 Subject: [PATCH 644/751] Fix queryset used in "Not Reviewed" view. The `speaker` field on the `Proposal` model corresponds to a `Speaker`, not a `User`, so the old queryset created a ValueError. --- symposion/reviews/views.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/symposion/reviews/views.py b/symposion/reviews/views.py index 6e6e70fa..83cdc8bf 100644 --- a/symposion/reviews/views.py +++ b/symposion/reviews/views.py @@ -87,7 +87,8 @@ def review_section(request, section_slug, assigned=False, reviewed="all"): queryset = queryset.filter(reviews__user=request.user) reviewed = "user_reviewed" else: - queryset = queryset.exclude(reviews__user=request.user).exclude(speaker=request.user) + queryset = queryset.exclude(reviews__user=request.user).exclude( + speaker__user=request.user) reviewed = "user_not_reviewed" proposals = proposals_generator(request, queryset) From c7ce7fe02068352a9cd4c1c0a6d386dff953890c Mon Sep 17 00:00:00 2001 From: Jeff Triplett Date: Mon, 14 Mar 2016 22:24:14 -0500 Subject: [PATCH 645/751] Adds twitter_username to Speaker This change is based on the PyOhio fork located at: https://github.com/PyCon/pycon/search?utf8=%E2%9C%93&q=twitter_username --- symposion/speakers/admin.py | 2 +- symposion/speakers/forms.py | 7 +++++++ .../0002_speaker_twitter_username.py | 20 +++++++++++++++++++ symposion/speakers/models.py | 5 +++++ 4 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 symposion/speakers/migrations/0002_speaker_twitter_username.py diff --git a/symposion/speakers/admin.py b/symposion/speakers/admin.py index e79370a3..95d4b28a 100644 --- a/symposion/speakers/admin.py +++ b/symposion/speakers/admin.py @@ -5,5 +5,5 @@ from symposion.speakers.models import Speaker admin.site.register(Speaker, - list_display=["name", "email", "created"], + list_display=["name", "email", "created", "twitter_username"], search_fields=["name"]) diff --git a/symposion/speakers/forms.py b/symposion/speakers/forms.py index 3b81efa3..c0acfe38 100644 --- a/symposion/speakers/forms.py +++ b/symposion/speakers/forms.py @@ -12,4 +12,11 @@ class SpeakerForm(forms.ModelForm): "name", "biography", "photo", + "twitter_username", ] + + def clean_twitter_username(self): + value = self.cleaned_data["twitter_username"] + if value.startswith("@"): + value = value[1:] + return value diff --git a/symposion/speakers/migrations/0002_speaker_twitter_username.py b/symposion/speakers/migrations/0002_speaker_twitter_username.py new file mode 100644 index 00000000..9208697b --- /dev/null +++ b/symposion/speakers/migrations/0002_speaker_twitter_username.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2016-03-15 03:16 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('symposion_speakers', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='speaker', + name='twitter_username', + field=models.CharField(blank=True, help_text='Your Twitter account', max_length=15), + ), + ] diff --git a/symposion/speakers/models.py b/symposion/speakers/models.py index 57d113d7..6e6de9d4 100644 --- a/symposion/speakers/models.py +++ b/symposion/speakers/models.py @@ -30,6 +30,11 @@ class Speaker(models.Model): "Markdown."), verbose_name=_("Biography")) biography_html = models.TextField(blank=True) photo = models.ImageField(upload_to="speaker_photos", blank=True, verbose_name=_("Photo")) + twitter_username = models.CharField( + max_length=15, + blank=True, + help_text=_(u"Your Twitter account") + ) annotation = models.TextField(verbose_name=_("Annotation")) # staff only invite_email = models.CharField(max_length=200, unique=True, null=True, db_index=True, verbose_name=_("Invite_email")) invite_token = models.CharField(max_length=40, db_index=True, verbose_name=_("Invite token")) From 6fb529aad25208fd1e4ae75bcfdb545e4b419367 Mon Sep 17 00:00:00 2001 From: Martey Dodoo Date: Tue, 15 Mar 2016 05:47:25 -0400 Subject: [PATCH 646/751] Display content_override in slot admin list view. Fix typo and use `content_override` field (instead of `content`) for a Slot's `list_display`. --- symposion/schedule/admin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/symposion/schedule/admin.py b/symposion/schedule/admin.py index 69f16be0..708ea131 100644 --- a/symposion/schedule/admin.py +++ b/symposion/schedule/admin.py @@ -25,7 +25,7 @@ class SlotRoomInline(admin.TabularInline): class SlotAdmin(admin.ModelAdmin): list_filter = ("day", "kind") - list_display = ("day", "start", "end", "kind", "content") + list_display = ("day", "start", "end", "kind", "content_override") inlines = [SlotRoomInline] From f5558d5f0cbccffefb4e781593fcde7fada910d7 Mon Sep 17 00:00:00 2001 From: Anna Ossowski Date: Tue, 15 Mar 2016 13:21:19 +0100 Subject: [PATCH 647/751] Update README.rst --- README.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index fcd7f46a..2bf8e503 100644 --- a/README.rst +++ b/README.rst @@ -55,15 +55,15 @@ Contribute See this blog post http://blog.pinaxproject.com/2016/02/26/recap-february-pinax-hangout/ including a video, or our How to Contribute (http://pinaxproject.com/pinax/how_to_contribute/) section for an overview on how contributing to Pinax works. For concrete contribution ideas, please see our Ways to Contribute/What We Need Help With (http://pinaxproject.com/pinax/ways_to_contribute/) section. -In case of any questions, we would recommend for you to join our Pinax Slack team (http://slack.pinaxproject.com) and ping us there instead of creating an issue on GitHub. Creating issues on GitHub is of course also valid but we are usually able to help you faster if you ping us in Slack. +In case of any questions, we recommend for you to join our Pinax Slack team (http://slack.pinaxproject.com) and ping us there instead of creating an issue on GitHub. Creating issues on GitHub is of course also valid but we are usually able to help you faster if you ping us in Slack. -We would also highly recommend for your to read our Open Source and Self-Care blog post (http://blog.pinaxproject.com/2016/01/19/open-source-and-self-care/). +We also highly recommend for your to read our Open Source and Self-Care blog post (http://blog.pinaxproject.com/2016/01/19/open-source-and-self-care/). Code of Conduct ---------------- -In order to foster a kind, inclusive, and harassment-free community, the Pinax Project has a code of conduct, which can be found here http://pinaxproject.com/pinax/code_of_conduct/. We'd like to ask you to treat everyone as a smart human programmer that shares an interest in Python, Django, and Pinax with you. +In order to foster a kind, inclusive, and harassment-free community, the Pinax Project has a code of conduct, which can be found here http://pinaxproject.com/pinax/code_of_conduct/. We ask you to treat everyone as a smart human programmer that shares an interest in Python, Django, and Pinax with you. Pinax Project Blog and Twitter From 88fc54994b504365bb2d219b21a2bc48214b589a Mon Sep 17 00:00:00 2001 From: Anna Ossowski Date: Tue, 15 Mar 2016 13:22:29 +0100 Subject: [PATCH 648/751] Update README.rst --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 2bf8e503..95448359 100644 --- a/README.rst +++ b/README.rst @@ -55,9 +55,9 @@ Contribute See this blog post http://blog.pinaxproject.com/2016/02/26/recap-february-pinax-hangout/ including a video, or our How to Contribute (http://pinaxproject.com/pinax/how_to_contribute/) section for an overview on how contributing to Pinax works. For concrete contribution ideas, please see our Ways to Contribute/What We Need Help With (http://pinaxproject.com/pinax/ways_to_contribute/) section. -In case of any questions, we recommend for you to join our Pinax Slack team (http://slack.pinaxproject.com) and ping us there instead of creating an issue on GitHub. Creating issues on GitHub is of course also valid but we are usually able to help you faster if you ping us in Slack. +In case of any questions, we recommend you join our Pinax Slack team (http://slack.pinaxproject.com) and ping us there instead of creating an issue on GitHub. Creating issues on GitHub is of course also valid but we are usually able to help you faster if you ping us in Slack. -We also highly recommend for your to read our Open Source and Self-Care blog post (http://blog.pinaxproject.com/2016/01/19/open-source-and-self-care/). +We also highly recommend reading our Open Source and Self-Care blog post (http://blog.pinaxproject.com/2016/01/19/open-source-and-self-care/). Code of Conduct From 251f9ea2809ae16fa676f04a6029bd29778237cb Mon Sep 17 00:00:00 2001 From: Martey Dodoo Date: Tue, 15 Mar 2016 12:08:58 -0400 Subject: [PATCH 649/751] Show unpublished conference schedule to staff. This is an addendum to #123 to allow the `schedule_conference` view to show unpublished conference schedules to staff members. --- symposion/schedule/views.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/symposion/schedule/views.py b/symposion/schedule/views.py index 0ff3903f..9c6d7c57 100644 --- a/symposion/schedule/views.py +++ b/symposion/schedule/views.py @@ -34,7 +34,10 @@ def fetch_schedule(slug): def schedule_conference(request): - schedules = Schedule.objects.filter(published=True, hidden=False) + if request.user.is_staff: + schedules = Schedule.objects.filter(hidden=False) + else: + schedules = Schedule.objects.filter(published=True, hidden=False) sections = [] for schedule in schedules: From f1f29c6f61dec33b3274a11b2617c06d51ba6b0d Mon Sep 17 00:00:00 2001 From: Scott Bragg Date: Fri, 10 Jun 2016 15:29:09 +1000 Subject: [PATCH 650/751] Replaced +1/+0/-0/-1 voting with +2/+1/-1/-2 (fixes lca2017/symposion/#1) --- symposion/reviews/models.py | 40 ++++++++++++++++---------------- symposion/reviews/views.py | 46 ++++++++++++++++++------------------- 2 files changed, 43 insertions(+), 43 deletions(-) diff --git a/symposion/reviews/models.py b/symposion/reviews/models.py index 1419e134..ecfcde60 100644 --- a/symposion/reviews/models.py +++ b/symposion/reviews/models.py @@ -17,22 +17,22 @@ from symposion.schedule.models import Presentation def score_expression(): return ( - (3 * F("plus_one") + F("plus_zero")) - - (F("minus_zero") + 3 * F("minus_one")) + (2 * F("plus_two") + F("plus_one")) - + (F("minus_one") + 2 * F("minus_two")) ) class Votes(object): + PLUS_TWO = "+2" PLUS_ONE = "+1" - PLUS_ZERO = "+0" - MINUS_ZERO = "−0" MINUS_ONE = "−1" + MINUS_TWO = "−2" CHOICES = [ - (PLUS_ONE, _("+1 — Good proposal and I will argue for it to be accepted.")), - (PLUS_ZERO, _("+0 — OK proposal, but I will not argue for it to be accepted.")), - (MINUS_ZERO, _("−0 — Weak proposal, but I will not argue strongly against acceptance.")), - (MINUS_ONE, _("−1 — Serious issues and I will argue to reject this proposal.")), + (PLUS_TWO, _("+2 — Good proposal and I will argue for it to be accepted.")), + (PLUS_ONE, _("+1 — OK proposal, but I will not argue for it to be accepted.")), + (MINUS_ONE, _("−1 — Weak proposal, but I will not argue strongly against acceptance.")), + (MINUS_TWO, _("−2 — Serious issues and I will argue to reject this proposal.")), ] VOTES = Votes() @@ -222,10 +222,10 @@ class ProposalResult(models.Model): score = models.DecimalField(max_digits=5, decimal_places=2, default=Decimal("0.00"), verbose_name=_("Score")) comment_count = models.PositiveIntegerField(default=0, verbose_name=_("Comment count")) vote_count = models.PositiveIntegerField(default=0, verbose_name=_("Vote count")) + plus_two = models.PositiveIntegerField(default=0, verbose_name=_("Plus two")) plus_one = models.PositiveIntegerField(default=0, verbose_name=_("Plus one")) - plus_zero = models.PositiveIntegerField(default=0, verbose_name=_("Plus zero")) - minus_zero = models.PositiveIntegerField(default=0, verbose_name=_("Minus zero")) minus_one = models.PositiveIntegerField(default=0, verbose_name=_("Minus one")) + minus_two = models.PositiveIntegerField(default=0, verbose_name=_("Minus two")) accepted = models.NullBooleanField(choices=[ (True, "accepted"), (False, "rejected"), @@ -244,31 +244,31 @@ class ProposalResult(models.Model): result, created = cls._default_manager.get_or_create(proposal=proposal) result.comment_count = Review.objects.filter(proposal=proposal).count() result.vote_count = LatestVote.objects.filter(proposal=proposal).count() + result.plus_two = LatestVote.objects.filter( + proposal=proposal, + vote=VOTES.PLUS_TWO + ).count() result.plus_one = LatestVote.objects.filter( proposal=proposal, vote=VOTES.PLUS_ONE ).count() - result.plus_zero = LatestVote.objects.filter( - proposal=proposal, - vote=VOTES.PLUS_ZERO - ).count() - result.minus_zero = LatestVote.objects.filter( - proposal=proposal, - vote=VOTES.MINUS_ZERO - ).count() result.minus_one = LatestVote.objects.filter( proposal=proposal, vote=VOTES.MINUS_ONE ).count() + result.minus_two = LatestVote.objects.filter( + proposal=proposal, + vote=VOTES.MINUS_TWO + ).count() result.save() cls._default_manager.filter(pk=result.pk).update(score=score_expression()) def update_vote(self, vote, previous=None, removal=False): mapping = { + VOTES.PLUS_TWO: "plus_two", VOTES.PLUS_ONE: "plus_one", - VOTES.PLUS_ZERO: "plus_zero", - VOTES.MINUS_ZERO: "minus_zero", VOTES.MINUS_ONE: "minus_one", + VOTES.MINUS_TWO: "minus_two", } if previous: if previous == vote: diff --git a/symposion/reviews/views.py b/symposion/reviews/views.py index 83cdc8bf..037c6742 100644 --- a/symposion/reviews/views.py +++ b/symposion/reviews/views.py @@ -41,10 +41,10 @@ def proposals_generator(request, queryset, user_pk=None, check_speaker=True): obj.comment_count = obj.result.comment_count obj.total_votes = obj.result.vote_count + obj.plus_two = obj.result.plus_two obj.plus_one = obj.result.plus_one - obj.plus_zero = obj.result.plus_zero - obj.minus_zero = obj.result.minus_zero obj.minus_one = obj.result.minus_one + obj.minus_two = obj.result.minus_two lookup_params = dict(proposal=obj) if user_pk: @@ -144,22 +144,22 @@ def review_admin(request, section_slug): user.comment_count = Review.objects.filter(user=user).count() user.total_votes = LatestVote.objects.filter(user=user).count() + user.plus_two = LatestVote.objects.filter( + user=user, + vote=LatestVote.VOTES.PLUS_TWO + ).count() user.plus_one = LatestVote.objects.filter( user=user, vote=LatestVote.VOTES.PLUS_ONE ).count() - user.plus_zero = LatestVote.objects.filter( - user=user, - vote=LatestVote.VOTES.PLUS_ZERO - ).count() - user.minus_zero = LatestVote.objects.filter( - user=user, - vote=LatestVote.VOTES.MINUS_ZERO - ).count() user.minus_one = LatestVote.objects.filter( user=user, vote=LatestVote.VOTES.MINUS_ONE ).count() + user.minus_two = LatestVote.objects.filter( + user=user, + vote=LatestVote.VOTES.MINUS_TWO + ).count() yield user @@ -268,10 +268,10 @@ def review_detail(request, pk): proposal.comment_count = proposal.result.comment_count proposal.total_votes = proposal.result.vote_count + proposal.plus_two = proposal.result.plus_two proposal.plus_one = proposal.result.plus_one - proposal.plus_zero = proposal.result.plus_zero - proposal.minus_zero = proposal.result.minus_zero proposal.minus_one = proposal.result.minus_one + proposal.minus_two = proposal.result.minus_two reviews = Review.objects.filter(proposal=proposal).order_by("-submitted_at") messages = proposal.messages.order_by("submitted_at") @@ -319,22 +319,22 @@ def review_status(request, section_slug=None, key=None): queryset = queryset.filter(kind__section__slug=section_slug) proposals = { - # proposals with at least VOTE_THRESHOLD reviews and at least one +1 and no -1s, sorted by + # proposals with at least VOTE_THRESHOLD reviews and at least one +2 and no -2s, sorted by # the 'score' - "positive": queryset.filter(result__vote_count__gte=VOTE_THRESHOLD, result__plus_one__gt=0, - result__minus_one=0).order_by("-result__score"), - # proposals with at least VOTE_THRESHOLD reviews and at least one -1 and no +1s, reverse + "positive": queryset.filter(result__vote_count__gte=VOTE_THRESHOLD, result__plus_two__gt=0, + result__minus_two=0).order_by("-result__score"), + # proposals with at least VOTE_THRESHOLD reviews and at least one -2 and no +2s, reverse # sorted by the 'score' - "negative": queryset.filter(result__vote_count__gte=VOTE_THRESHOLD, result__minus_one__gt=0, - result__plus_one=0).order_by("result__score"), - # proposals with at least VOTE_THRESHOLD reviews and neither a +1 or a -1, sorted by total + "negative": queryset.filter(result__vote_count__gte=VOTE_THRESHOLD, result__minus_two__gt=0, + result__plus_two=0).order_by("result__score"), + # proposals with at least VOTE_THRESHOLD reviews and neither a +2 or a -2, sorted by total # votes (lowest first) - "indifferent": queryset.filter(result__vote_count__gte=VOTE_THRESHOLD, result__minus_one=0, - result__plus_one=0).order_by("result__vote_count"), - # proposals with at least VOTE_THRESHOLD reviews and both a +1 and -1, sorted by total + "indifferent": queryset.filter(result__vote_count__gte=VOTE_THRESHOLD, result__minus_two=0, + result__plus_two=0).order_by("result__vote_count"), + # proposals with at least VOTE_THRESHOLD reviews and both a +2 and -2, sorted by total # votes (highest first) "controversial": queryset.filter(result__vote_count__gte=VOTE_THRESHOLD, - result__plus_one__gt=0, result__minus_one__gt=0) + result__plus_two__gt=0, result__minus_two__gt=0) .order_by("-result__vote_count"), # proposals with fewer than VOTE_THRESHOLD reviews "too_few": queryset.filter(result__vote_count__lt=VOTE_THRESHOLD) From acc1b1490e33cd39cfe885102770f483ec874529 Mon Sep 17 00:00:00 2001 From: Scott Bragg Date: Mon, 13 Jun 2016 20:46:13 +1000 Subject: [PATCH 651/751] Update css_class methods to use new vote scoring (removed VOTE_ZERO) --- symposion/reviews/models.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/symposion/reviews/models.py b/symposion/reviews/models.py index ecfcde60..8cf828b7 100644 --- a/symposion/reviews/models.py +++ b/symposion/reviews/models.py @@ -177,10 +177,10 @@ class Review(models.Model): def css_class(self): return { + self.VOTES.PLUS_TWO: "plus-two", self.VOTES.PLUS_ONE: "plus-one", - self.VOTES.PLUS_ZERO: "plus-zero", - self.VOTES.MINUS_ZERO: "minus-zero", self.VOTES.MINUS_ONE: "minus-one", + self.VOTES.MINUS_TWO: "minus-two", }[self.vote] @property @@ -210,10 +210,10 @@ class LatestVote(models.Model): def css_class(self): return { + self.VOTES.PLUS_TWO: "plus-two", self.VOTES.PLUS_ONE: "plus-one", - self.VOTES.PLUS_ZERO: "plus-zero", - self.VOTES.MINUS_ZERO: "minus-zero", self.VOTES.MINUS_ONE: "minus-one", + self.VOTES.MINUS_TWO: "minus-two", }[self.vote] From a6405ccfc7f53f601088206c216c5167fd86359f Mon Sep 17 00:00:00 2001 From: Scott Bragg Date: Mon, 13 Jun 2016 21:20:46 +1000 Subject: [PATCH 652/751] Fix team permissions backend not pulling out manager_permissions Something like request.user.has_perm('reviews.can_manage_%s' % proposal.kind.section.slug) Will aways return false as the backend does a lookup of team membership (member or manager) but only grabs the 'permissions' and not the 'manager_permissions' field --- symposion/teams/backends.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/symposion/teams/backends.py b/symposion/teams/backends.py index 23b001b6..0effdc2d 100644 --- a/symposion/teams/backends.py +++ b/symposion/teams/backends.py @@ -16,15 +16,27 @@ class TeamPermissionsBackend(object): if user_obj.is_anonymous() or obj is not None: return set() if not hasattr(user_obj, "_team_perm_cache"): + # Member permissions memberships = Team.objects.filter( - Q(memberships__user=user_obj), - Q(memberships__state="manager") | Q(memberships__state="member"), + Q(memberships__user=user_obj), + Q(memberships__state="member"), ) perms = memberships.values_list( "permissions__content_type__app_label", "permissions__codename" ).order_by() - user_obj._team_perm_cache = set(["%s.%s" % (ct, name) for ct, name in perms]) + permissions = ["%s.%s" % (ct, name) for ct, name in perms] + # Manager permissions + memberships = Team.objects.filter( + Q(memberships__user=user_obj), + Q(memberships__state="manager"), + ) + perms = memberships.values_list( + "manager_permissions__content_type__app_label", + "manager_permissions__codename" + ).order_by() + permissions += ["%s.%s" % (ct, name) for ct, name in perms] + user_obj._team_perm_cache = set(permissions) return user_obj._team_perm_cache def has_perm(self, user_obj, perm, obj=None): From ce122994ccad30b17b5073076bb97ded86f099cf Mon Sep 17 00:00:00 2001 From: Scott Bragg Date: Mon, 13 Jun 2016 21:32:55 +1000 Subject: [PATCH 653/751] Changed admin to the 'can_manage' permission and added an is_manager to the page context --- symposion/reviews/views.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/symposion/reviews/views.py b/symposion/reviews/views.py index 037c6742..f6b967e3 100644 --- a/symposion/reviews/views.py +++ b/symposion/reviews/views.py @@ -185,7 +185,7 @@ def review_detail(request, pk): if not request.user.is_superuser and request.user in speakers: return access_not_permitted(request) - admin = request.user.is_staff + admin = request.user.has_perm("reviews.can_manage_%s" % proposal.kind.section.slug) try: latest_vote = LatestVote.objects.get(proposal=proposal, user=request.user) @@ -208,7 +208,7 @@ def review_detail(request, pk): return redirect(request.path) else: message_form = SpeakerCommentForm() - elif "message_submit" in request.POST: + elif "message_submit" in request.POST and admin: message_form = SpeakerCommentForm(request.POST) if message_form.is_valid(): @@ -282,7 +282,8 @@ def review_detail(request, pk): "reviews": reviews, "review_messages": messages, "review_form": review_form, - "message_form": message_form + "message_form": message_form, + "is_manager": admin }) From be4404c60283672b343eeedbd55356d67fcce4f9 Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer Date: Sat, 18 Jun 2016 11:40:18 +1000 Subject: [PATCH 654/751] #2 Adds abstain vote type and revises vote score to account for abstention. --- symposion/reviews/models.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/symposion/reviews/models.py b/symposion/reviews/models.py index 8cf828b7..77acb1dd 100644 --- a/symposion/reviews/models.py +++ b/symposion/reviews/models.py @@ -19,10 +19,13 @@ def score_expression(): return ( (2 * F("plus_two") + F("plus_one")) - (F("minus_one") + 2 * F("minus_two")) + ) / ( + F("vote_count") - F("abstain") ) class Votes(object): + ABSTAIN = "0" PLUS_TWO = "+2" PLUS_ONE = "+1" MINUS_ONE = "−1" @@ -33,6 +36,7 @@ class Votes(object): (PLUS_ONE, _("+1 — OK proposal, but I will not argue for it to be accepted.")), (MINUS_ONE, _("−1 — Weak proposal, but I will not argue strongly against acceptance.")), (MINUS_TWO, _("−2 — Serious issues and I will argue to reject this proposal.")), + (ABSTAIN, _("Abstain - I do not want to review this proposal and I do not want to see it again.")), ] VOTES = Votes() @@ -177,6 +181,7 @@ class Review(models.Model): def css_class(self): return { + self.VOTES.ABSTAIN: "abstain", self.VOTES.PLUS_TWO: "plus-two", self.VOTES.PLUS_ONE: "plus-one", self.VOTES.MINUS_ONE: "minus-one", @@ -210,6 +215,7 @@ class LatestVote(models.Model): def css_class(self): return { + self.VOTES.ABSTAIN: "abstain", self.VOTES.PLUS_TWO: "plus-two", self.VOTES.PLUS_ONE: "plus-one", self.VOTES.MINUS_ONE: "minus-one", @@ -222,6 +228,7 @@ class ProposalResult(models.Model): score = models.DecimalField(max_digits=5, decimal_places=2, default=Decimal("0.00"), verbose_name=_("Score")) comment_count = models.PositiveIntegerField(default=0, verbose_name=_("Comment count")) vote_count = models.PositiveIntegerField(default=0, verbose_name=_("Vote count")) + abstain = models.PositiveIntegerField(default=0, verbose_name=_("Abstain")) plus_two = models.PositiveIntegerField(default=0, verbose_name=_("Plus two")) plus_one = models.PositiveIntegerField(default=0, verbose_name=_("Plus one")) minus_one = models.PositiveIntegerField(default=0, verbose_name=_("Minus one")) @@ -244,6 +251,10 @@ class ProposalResult(models.Model): result, created = cls._default_manager.get_or_create(proposal=proposal) result.comment_count = Review.objects.filter(proposal=proposal).count() result.vote_count = LatestVote.objects.filter(proposal=proposal).count() + result.abstain = LatestVote.objects.filter( + proposal=proposal, + vote=VOTES.ABSTAIN, + ).count() result.plus_two = LatestVote.objects.filter( proposal=proposal, vote=VOTES.PLUS_TWO From 28592e352c21cda97ec14fdddec7643669c7339b Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer Date: Sat, 18 Jun 2016 13:07:13 +1000 Subject: [PATCH 655/751] More changes that make abstention work --- symposion/reviews/models.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/symposion/reviews/models.py b/symposion/reviews/models.py index 77acb1dd..f6234d51 100644 --- a/symposion/reviews/models.py +++ b/symposion/reviews/models.py @@ -20,7 +20,7 @@ def score_expression(): (2 * F("plus_two") + F("plus_one")) - (F("minus_one") + 2 * F("minus_two")) ) / ( - F("vote_count") - F("abstain") + F("vote_count") - F("abstain") * 1.0 ) @@ -276,6 +276,7 @@ class ProposalResult(models.Model): def update_vote(self, vote, previous=None, removal=False): mapping = { + VOTES.ABSTAIN: "abstain", VOTES.PLUS_TWO: "plus_two", VOTES.PLUS_ONE: "plus_one", VOTES.MINUS_ONE: "minus_one", From 47a6f212f21792fe339de277303600c5fa72f43f Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer Date: Sat, 18 Jun 2016 13:13:18 +1000 Subject: [PATCH 656/751] Add display of scores to the review list --- symposion/reviews/views.py | 1 + 1 file changed, 1 insertion(+) diff --git a/symposion/reviews/views.py b/symposion/reviews/views.py index f6b967e3..7a854f7e 100644 --- a/symposion/reviews/views.py +++ b/symposion/reviews/views.py @@ -40,6 +40,7 @@ def proposals_generator(request, queryset, user_pk=None, check_speaker=True): ProposalResult.objects.get_or_create(proposal=obj) obj.comment_count = obj.result.comment_count + obj.score = obj.result.score obj.total_votes = obj.result.vote_count obj.plus_two = obj.result.plus_two obj.plus_one = obj.result.plus_one From 149fbb4ac614e91769fdb0de5c7e432c131abfab Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer Date: Sat, 18 Jun 2016 13:33:06 +1000 Subject: [PATCH 657/751] Adds .DS_Store to .gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 2b2e626e..f0594b31 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ _build *.pyc dev.db site_media -*.egg-info \ No newline at end of file +*.egg-info +.DS_Store From 217f2f32c169ca30aacef92e98b85d3284558830 Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer Date: Sat, 18 Jun 2016 13:53:02 +1000 Subject: [PATCH 658/751] Adds extra fields to the speaker profile model --- symposion/speakers/forms.py | 4 ++++ symposion/speakers/models.py | 21 +++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/symposion/speakers/forms.py b/symposion/speakers/forms.py index c0acfe38..bab153a5 100644 --- a/symposion/speakers/forms.py +++ b/symposion/speakers/forms.py @@ -13,6 +13,10 @@ class SpeakerForm(forms.ModelForm): "biography", "photo", "twitter_username", + "experience", + "accessibility", + "travel_assistance", + "accommodation_assistance", ] def clean_twitter_username(self): diff --git a/symposion/speakers/models.py b/symposion/speakers/models.py index 6e6de9d4..77d48d9b 100644 --- a/symposion/speakers/models.py +++ b/symposion/speakers/models.py @@ -35,6 +35,27 @@ class Speaker(models.Model): blank=True, help_text=_(u"Your Twitter account") ) + experience = models.TextField( + blank=True, + help_text=_("Please describe your past speaking experience, or expertise on your given topic."), + verbose_name=_("Experience")) + accessibility = models.TextField( + blank=True, + help_text=_("Please describe any special accessibility requirements that you may have."), + verbose_name=_("Accessibility requirements")) + travel_assistance = models.BooleanField( + blank=True, + help_text=_("Check this box if you require assistance to travel to Hobart to " + "present your proposed sessions."), + verbose_name=_("Travel assistance required"), + ) + accommodation_assistance = models.BooleanField( + blank=True, + help_text=_("Check this box if you require us to provide you with student-style " + "accommodation in order to present your proposed sessions."), + verbose_name=_("Accommodation assistance required"), + ) + annotation = models.TextField(verbose_name=_("Annotation")) # staff only invite_email = models.CharField(max_length=200, unique=True, null=True, db_index=True, verbose_name=_("Invite_email")) invite_token = models.CharField(max_length=40, db_index=True, verbose_name=_("Invite token")) From f64a7573d3e798c87c7ec3601e7335cc2b33a3b4 Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer Date: Sat, 18 Jun 2016 16:50:11 +1000 Subject: [PATCH 659/751] =?UTF-8?q?-=20Adds=20defaults=20to=20the=20speake?= =?UTF-8?q?r=20profile=20-=20Removes=20=E2=80=9Cexperience=E2=80=9D,=20as?= =?UTF-8?q?=20this=20is=20listed=20in=20=E2=80=9Cother=20notes=E2=80=9D=20?= =?UTF-8?q?on=20a=20per-proposal=20basis.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- symposion/speakers/models.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/symposion/speakers/models.py b/symposion/speakers/models.py index 77d48d9b..a3542e8a 100644 --- a/symposion/speakers/models.py +++ b/symposion/speakers/models.py @@ -35,22 +35,20 @@ class Speaker(models.Model): blank=True, help_text=_(u"Your Twitter account") ) - experience = models.TextField( - blank=True, - help_text=_("Please describe your past speaking experience, or expertise on your given topic."), - verbose_name=_("Experience")) accessibility = models.TextField( blank=True, help_text=_("Please describe any special accessibility requirements that you may have."), verbose_name=_("Accessibility requirements")) travel_assistance = models.BooleanField( blank=True, + default=False, help_text=_("Check this box if you require assistance to travel to Hobart to " "present your proposed sessions."), verbose_name=_("Travel assistance required"), ) accommodation_assistance = models.BooleanField( blank=True, + default=False, help_text=_("Check this box if you require us to provide you with student-style " "accommodation in order to present your proposed sessions."), verbose_name=_("Accommodation assistance required"), From b2af1d0c06f7c5785fb05dd254cac07286f4ac98 Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer Date: Sat, 18 Jun 2016 16:51:28 +1000 Subject: [PATCH 660/751] oops --- symposion/speakers/forms.py | 1 - 1 file changed, 1 deletion(-) diff --git a/symposion/speakers/forms.py b/symposion/speakers/forms.py index bab153a5..122b064c 100644 --- a/symposion/speakers/forms.py +++ b/symposion/speakers/forms.py @@ -13,7 +13,6 @@ class SpeakerForm(forms.ModelForm): "biography", "photo", "twitter_username", - "experience", "accessibility", "travel_assistance", "accommodation_assistance", From 6e133970d9587c4fe3cb91ef6c06519bbcd98dd9 Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer Date: Sat, 18 Jun 2016 17:01:56 +1000 Subject: [PATCH 661/751] Removes div-by-zero error if the first vote is an abstention --- symposion/reviews/models.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/symposion/reviews/models.py b/symposion/reviews/models.py index f6234d51..3b248328 100644 --- a/symposion/reviews/models.py +++ b/symposion/reviews/models.py @@ -5,6 +5,7 @@ from decimal import Decimal from django.db import models from django.db.models import Q, F +from django.db.models import Case, When, Value from django.db.models.signals import post_save from django.contrib.auth.models import User @@ -16,13 +17,18 @@ from symposion.schedule.models import Presentation def score_expression(): - return ( + score = ( (2 * F("plus_two") + F("plus_one")) - (F("minus_one") + 2 * F("minus_two")) ) / ( F("vote_count") - F("abstain") * 1.0 ) + return Case( + When(vote_count=F("abstain"), then=Value("0")), # no divide by zero + default=score, + ) + class Votes(object): ABSTAIN = "0" From d305cd8c13d7b7cc32d13009db26158f9d923def Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer Date: Sat, 18 Jun 2016 17:07:21 +1000 Subject: [PATCH 662/751] Requires comments for non-abstain votes only --- symposion/reviews/models.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/symposion/reviews/models.py b/symposion/reviews/models.py index 3b248328..c48acf12 100644 --- a/symposion/reviews/models.py +++ b/symposion/reviews/models.py @@ -3,6 +3,8 @@ from __future__ import unicode_literals from datetime import datetime from decimal import Decimal +from django.core.exceptions import ValidationError + from django.db import models from django.db.models import Q, F from django.db.models import Case, When, Value @@ -126,10 +128,21 @@ class Review(models.Model): # No way to encode "-0" vs. "+0" into an IntegerField, and I don't feel # like some complicated encoding system. vote = models.CharField(max_length=2, blank=True, choices=VOTES.CHOICES, verbose_name=_("Vote")) - comment = models.TextField(verbose_name=_("Comment")) + comment = models.TextField( + blank=True, + verbose_name=_("Comment") + ) comment_html = models.TextField(blank=True) submitted_at = models.DateTimeField(default=datetime.now, editable=False, verbose_name=_("Submitted at")) + def clean(self): + err = {} + if self.vote != VOTES.ABSTAIN and not self.comment.strip(): + err["comment"] = ValidationError(_("You must provide a comment")) + + if err: + raise ValidationError(err) + def save(self, **kwargs): self.comment_html = parse(self.comment) if self.vote: From 0ce99678c3e871fe8812d3ede6c64a15d8d480f4 Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer Date: Sun, 19 Jun 2016 12:39:28 +1000 Subject: [PATCH 663/751] Updates ProposalBase to include information that LCA typically asks for --- symposion/proposals/models.py | 69 ++++++++++++++++++++++++++--------- 1 file changed, 51 insertions(+), 18 deletions(-) diff --git a/symposion/proposals/models.py b/symposion/proposals/models.py index 23f65608..34bd7f64 100644 --- a/symposion/proposals/models.py +++ b/symposion/proposals/models.py @@ -86,28 +86,60 @@ class ProposalBase(models.Model): kind = models.ForeignKey(ProposalKind, verbose_name=_("Kind")) title = models.CharField(max_length=100, verbose_name=_("Title")) - description = models.TextField( - _("Brief Description"), - max_length=400, # @@@ need to enforce 400 in UI - help_text=_("If your proposal is accepted this will be made public and printed in the " - "program. Should be one paragraph, maximum 400 characters.") - ) abstract = models.TextField( - _("Detailed Abstract"), - help_text=_("Detailed outline. Will be made public if your proposal is accepted. Edit " - "using Markdown.") + _("Abstract"), + help_text=_("This will appear in the conference programme. You will " + "have an opportunity to update it once the proposal is " + "accepted, but it should reasonably reflect what you will " + "be presenting, and in any case it will appear as-is on " + "the website in the draft programme. Up to about 500 " + "words. Edit using Markdown.") ) abstract_html = models.TextField(blank=True) - additional_notes = models.TextField( - _("Addtional Notes"), - blank=True, - help_text=_("Anything else you'd like the program committee to know when making their " - "selection: your past experience, etc. This is not made public. Edit using " - "Markdown.") ) - additional_notes_html = models.TextField(blank=True) + private_abstract_html = models.TextField(blank=True) + + technical_requirements = models.TextField( + _("Special Requirements"), + blank=True, + help_text=_("Speakers will be provided with: Internet access, power, " + "projector, audio. If you require anything in addition, " + "please list your technical requirements here. Such as: a " + "static IP address, A/V equipment or will be demonstrating " + "security-related techniques on the conference network. " + "Edit using Markdown.") + ) + technical_requirements_html = models.TextField(blank=True) + + project = models.TextField( + max_length=100, + blank=True, + help_text=_("The name of the project you will be talking about."), + ) + project_url = models.URLField( + _("Project URL"), + blank=True, + help_text=_("If your project has a webpage, specify the URL here so " + "the committee can find out more about your proposal.") + ) + video_url = models.URLField( + _("Video"), + blank=True, + help_text=_("You may optionally provide us with a link to a video of " + "you speaking at another event, or of a short 'elevator " + "pitch' of your proposed talk.") + ) + submitted = models.DateTimeField( default=now, editable=False, @@ -130,7 +162,8 @@ class ProposalBase(models.Model): def save(self, *args, **kwargs): self.abstract_html = parse(self.abstract) - self.additional_notes_html = parse(self.additional_notes) + self.private_abstract_html = parse(self.private_abstract) + self.technical_requirements_html = parse(self.technical_requirements) return super(ProposalBase, self).save(*args, **kwargs) def can_edit(self): From aa56ac00c36aba92fe143e979d1e4f4529acddac Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer Date: Sun, 19 Jun 2016 13:19:32 +1000 Subject: [PATCH 664/751] Updates speaker model to include information that LCA typically asks for --- symposion/speakers/forms.py | 3 +++ symposion/speakers/models.py | 43 +++++++++++++++++++++++++++++++----- 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/symposion/speakers/forms.py b/symposion/speakers/forms.py index 122b064c..a1c547b2 100644 --- a/symposion/speakers/forms.py +++ b/symposion/speakers/forms.py @@ -11,7 +11,10 @@ class SpeakerForm(forms.ModelForm): fields = [ "name", "biography", + "experience", "photo", + "telephone", + "homepage", "twitter_username", "accessibility", "travel_assistance", diff --git a/symposion/speakers/models.py b/symposion/speakers/models.py index a3542e8a..30d6490e 100644 --- a/symposion/speakers/models.py +++ b/symposion/speakers/models.py @@ -24,12 +24,40 @@ class Speaker(models.Model): name = models.CharField(verbose_name=_("Name"), max_length=100, help_text=_("As you would like it to appear in the" " conference program.")) - biography = models.TextField(blank=True, help_text=_("A little bit about you. Edit using " - "" - "Markdown."), verbose_name=_("Biography")) + biography = models.TextField( + blank=True, + help_text=_("This will appear on the conference website and in the " + "programme. Please write in the third person, eg 'Alice " + "is a Moblin hacker...', 150-200 words. Edit using " + "" + "Markdown."), + verbose_name=_("Biography"), + ) biography_html = models.TextField(blank=True) + experience = models.TextField( + blank=True, + help_text=_("Have you had any experience presenting elsewhere? If so, " + "we'd like to know. Anything you put here will only be " + "seen by the organisers and reviewers; use it to convince " + "them why they should accept your proposal. Edit using " + "" + "Markdown."), + verbose_name=_("Speaking experience"), + ) + experience_html = models.TextField(blank=True) photo = models.ImageField(upload_to="speaker_photos", blank=True, verbose_name=_("Photo")) + telephone = models.CharField( + max_length=15, + help_text=_(u"The conference team will need this to contact you " + "during the conference week. If you don't have one, or do " + "not wish to provide it, then enter NONE in this field.") + ) + homepage = models.URLField( + blank=True, + help_text=_(u"Your home page, if you have one") + ) twitter_username = models.CharField( max_length=15, blank=True, @@ -37,8 +65,12 @@ class Speaker(models.Model): ) accessibility = models.TextField( blank=True, - help_text=_("Please describe any special accessibility requirements that you may have."), + help_text=_("Please describe any special accessibility requirements " + "that you may have. Edit using " + "Markdown."), verbose_name=_("Accessibility requirements")) + accessibility_html = models.TextField(blank=True) travel_assistance = models.BooleanField( blank=True, default=False, @@ -70,6 +102,7 @@ class Speaker(models.Model): def save(self, *args, **kwargs): self.biography_html = parse(self.biography) + self.experience_html = parse(self.experience) return super(Speaker, self).save(*args, **kwargs) def __str__(self): From 782e5c9ea21d90cba7693bbf38dec5e2956b3d4e Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer Date: Sun, 19 Jun 2016 13:30:10 +1000 Subject: [PATCH 665/751] Tidies up some of the proposal fields --- symposion/proposals/models.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/symposion/proposals/models.py b/symposion/proposals/models.py index 34bd7f64..0a45b9d8 100644 --- a/symposion/proposals/models.py +++ b/symposion/proposals/models.py @@ -88,12 +88,8 @@ class ProposalBase(models.Model): title = models.CharField(max_length=100, verbose_name=_("Title")) abstract = models.TextField( _("Abstract"), - help_text=_("This will appear in the conference programme. You will " - "have an opportunity to update it once the proposal is " - "accepted, but it should reasonably reflect what you will " - "be presenting, and in any case it will appear as-is on " - "the website in the draft programme. Up to about 500 " - "words. Edit using Markdown.") ) abstract_html = models.TextField(blank=True) @@ -121,7 +117,7 @@ class ProposalBase(models.Model): ) technical_requirements_html = models.TextField(blank=True) - project = models.TextField( + project = models.CharField( max_length=100, blank=True, help_text=_("The name of the project you will be talking about."), From c0e9b90476d31651819d7955fe6d7433dc75f0f5 Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer Date: Sun, 19 Jun 2016 14:00:17 +1000 Subject: [PATCH 666/751] Requires acceptance of the code of conduct and T&Cs --- symposion/speakers/forms.py | 5 +++++ symposion/speakers/models.py | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/symposion/speakers/forms.py b/symposion/speakers/forms.py index a1c547b2..d4643415 100644 --- a/symposion/speakers/forms.py +++ b/symposion/speakers/forms.py @@ -19,8 +19,13 @@ class SpeakerForm(forms.ModelForm): "accessibility", "travel_assistance", "accommodation_assistance", + "agreement", ] + def __init__(self, *a, **k): + super(SpeakerForm, self).__init__(*a, **k) + self.fields['agreement'].required = True + def clean_twitter_username(self): value = self.cleaned_data["twitter_username"] if value.startswith("@"): diff --git a/symposion/speakers/models.py b/symposion/speakers/models.py index 30d6490e..a9b50163 100644 --- a/symposion/speakers/models.py +++ b/symposion/speakers/models.py @@ -85,6 +85,12 @@ class Speaker(models.Model): "accommodation in order to present your proposed sessions."), verbose_name=_("Accommodation assistance required"), ) + agreement = models.BooleanField( + default=False, + help_text=_("I agree to the terms and confitions of attendance, and " + "I have read, understood, and agree to act according to " + "the standards set forth in our Code of Conduct ") + ) annotation = models.TextField(verbose_name=_("Annotation")) # staff only invite_email = models.CharField(max_length=200, unique=True, null=True, db_index=True, verbose_name=_("Invite_email")) From 388c722ed631dc4e4fbbe04989f9a5af4cfa10f2 Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer Date: Sun, 19 Jun 2016 14:07:43 +1000 Subject: [PATCH 667/751] Makes sure accessibility requirements are parsed --- symposion/speakers/models.py | 1 + 1 file changed, 1 insertion(+) diff --git a/symposion/speakers/models.py b/symposion/speakers/models.py index a9b50163..1e94693b 100644 --- a/symposion/speakers/models.py +++ b/symposion/speakers/models.py @@ -109,6 +109,7 @@ class Speaker(models.Model): def save(self, *args, **kwargs): self.biography_html = parse(self.biography) self.experience_html = parse(self.experience) + self.accessibility_html = parse(self.accessibility) return super(Speaker, self).save(*args, **kwargs) def __str__(self): From 57acd04852f87512d68e2790de8615d8dbfc78d6 Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer Date: Sun, 19 Jun 2016 18:37:54 +1000 Subject: [PATCH 668/751] Removes a block of egregious stupidity from update_vote and replaces with something that actually works --- symposion/reviews/models.py | 74 ++++++++++++------------------------- 1 file changed, 24 insertions(+), 50 deletions(-) diff --git a/symposion/reviews/models.py b/symposion/reviews/models.py index c48acf12..d67d19b5 100644 --- a/symposion/reviews/models.py +++ b/symposion/reviews/models.py @@ -8,6 +8,7 @@ from django.core.exceptions import ValidationError from django.db import models from django.db.models import Q, F from django.db.models import Case, When, Value +from django.db.models import Count from django.db.models.signals import post_save from django.contrib.auth.models import User @@ -268,57 +269,30 @@ class ProposalResult(models.Model): def full_calculate(cls): for proposal in ProposalBase.objects.all(): result, created = cls._default_manager.get_or_create(proposal=proposal) - result.comment_count = Review.objects.filter(proposal=proposal).count() - result.vote_count = LatestVote.objects.filter(proposal=proposal).count() - result.abstain = LatestVote.objects.filter( - proposal=proposal, - vote=VOTES.ABSTAIN, - ).count() - result.plus_two = LatestVote.objects.filter( - proposal=proposal, - vote=VOTES.PLUS_TWO - ).count() - result.plus_one = LatestVote.objects.filter( - proposal=proposal, - vote=VOTES.PLUS_ONE - ).count() - result.minus_one = LatestVote.objects.filter( - proposal=proposal, - vote=VOTES.MINUS_ONE - ).count() - result.minus_two = LatestVote.objects.filter( - proposal=proposal, - vote=VOTES.MINUS_TWO - ).count() - result.save() - cls._default_manager.filter(pk=result.pk).update(score=score_expression()) + result.update_vote() - def update_vote(self, vote, previous=None, removal=False): - mapping = { - VOTES.ABSTAIN: "abstain", - VOTES.PLUS_TWO: "plus_two", - VOTES.PLUS_ONE: "plus_one", - VOTES.MINUS_ONE: "minus_one", - VOTES.MINUS_TWO: "minus_two", - } - if previous: - if previous == vote: - return - if removal: - setattr(self, mapping[previous], models.F(mapping[previous]) + 1) - else: - setattr(self, mapping[previous], models.F(mapping[previous]) - 1) - else: - if removal: - self.vote_count = models.F("vote_count") - 1 - else: - self.vote_count = models.F("vote_count") + 1 - if removal: - setattr(self, mapping[vote], models.F(mapping[vote]) - 1) - self.comment_count = models.F("comment_count") - 1 - else: - setattr(self, mapping[vote], models.F(mapping[vote]) + 1) - self.comment_count = models.F("comment_count") + 1 + def update_vote(self, *a, **k): + proposal = self.proposal + self.comment_count = Review.objects.filter(proposal=proposal).count() + agg = LatestVote.objects.filter(proposal=proposal).values( + "vote" + ).annotate( + count=Count("vote") + ) + vote_count = {} + # Set the defaults + for option in VOTES.CHOICES: + vote_count[option[0]] = 0 + # Set the actual values if present + for d in agg: + vote_count[d["vote"]] = d["count"] + + self.abstain = vote_count[VOTES.ABSTAIN] + self.plus_two = vote_count[VOTES.PLUS_TWO] + self.plus_one = vote_count[VOTES.PLUS_ONE] + self.minus_one = vote_count[VOTES.MINUS_ONE] + self.minus_two = vote_count[VOTES.MINUS_TWO] + self.vote_count = sum(i[1] for i in vote_count.items()) self.save() model = self.__class__ model._default_manager.filter(pk=self.pk).update(score=score_expression()) From 14dea7eafa5fa3a366db8c1d3c3b6bcce4d6cf6f Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer Date: Sun, 19 Jun 2016 18:38:14 +1000 Subject: [PATCH 669/751] Fixes deletion of reviews --- symposion/reviews/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/symposion/reviews/views.py b/symposion/reviews/views.py index 7a854f7e..c1ece5bb 100644 --- a/symposion/reviews/views.py +++ b/symposion/reviews/views.py @@ -292,7 +292,7 @@ def review_detail(request, pk): @require_POST def review_delete(request, pk): review = get_object_or_404(Review, pk=pk) - section_slug = review.section.slug + section_slug = review.section if not request.user.has_perm("reviews.can_manage_%s" % section_slug): return access_not_permitted(request) From 7f3ed91daed6e6f3e8821ec1cf42aa9e06914661 Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer Date: Sun, 19 Jun 2016 18:39:07 +1000 Subject: [PATCH 670/751] vote_count now only counts non-abstaining votes. Fixes #19 --- symposion/reviews/models.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/symposion/reviews/models.py b/symposion/reviews/models.py index d67d19b5..6727f6a9 100644 --- a/symposion/reviews/models.py +++ b/symposion/reviews/models.py @@ -24,11 +24,11 @@ def score_expression(): (2 * F("plus_two") + F("plus_one")) - (F("minus_one") + 2 * F("minus_two")) ) / ( - F("vote_count") - F("abstain") * 1.0 + F("vote_count") * 1.0 ) return Case( - When(vote_count=F("abstain"), then=Value("0")), # no divide by zero + When(vote_count=0, then=Value("0")), # no divide by zero default=score, ) @@ -247,6 +247,7 @@ class ProposalResult(models.Model): proposal = models.OneToOneField(ProposalBase, related_name="result", verbose_name=_("Proposal")) score = models.DecimalField(max_digits=5, decimal_places=2, default=Decimal("0.00"), verbose_name=_("Score")) comment_count = models.PositiveIntegerField(default=0, verbose_name=_("Comment count")) + # vote_count only counts non-abstain votes. vote_count = models.PositiveIntegerField(default=0, verbose_name=_("Vote count")) abstain = models.PositiveIntegerField(default=0, verbose_name=_("Abstain")) plus_two = models.PositiveIntegerField(default=0, verbose_name=_("Plus two")) @@ -292,7 +293,7 @@ class ProposalResult(models.Model): self.plus_one = vote_count[VOTES.PLUS_ONE] self.minus_one = vote_count[VOTES.MINUS_ONE] self.minus_two = vote_count[VOTES.MINUS_TWO] - self.vote_count = sum(i[1] for i in vote_count.items()) + self.vote_count = sum(i[1] for i in vote_count.items()) - self.abstain self.save() model = self.__class__ model._default_manager.filter(pk=self.pk).update(score=score_expression()) From d43d42bbc35bcc9aa3dddb53f70004d0bb81ff46 Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer Date: Wed, 29 Jun 2016 15:16:24 +1000 Subject: [PATCH 671/751] =?UTF-8?q?Fixes=20#21=20=E2=80=94=20the=20check?= =?UTF-8?q?=5Fspeaker=20argument=20to=20proposals=5Fgenerator=20is=20*fals?= =?UTF-8?q?e*=20for=20section=20chairs.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- symposion/reviews/views.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/symposion/reviews/views.py b/symposion/reviews/views.py index c1ece5bb..4ac472f4 100644 --- a/symposion/reviews/views.py +++ b/symposion/reviews/views.py @@ -92,7 +92,9 @@ def review_section(request, section_slug, assigned=False, reviewed="all"): speaker__user=request.user) reviewed = "user_not_reviewed" - proposals = proposals_generator(request, queryset) + # lca2017 #21 -- chairs want to be able to see their own proposals in the list + check_speaker = not request.user.has_perm("reviews.can_manage_%s" % section_slug) + proposals = proposals_generator(request, queryset, check_speaker=check_speaker) ctx = { "proposals": proposals, From d4230feab05fb97f54ec1d7c40268286af1ec176 Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer Date: Wed, 29 Jun 2016 16:00:07 +1000 Subject: [PATCH 672/751] #16 adds view for jumping to a random proposal --- symposion/reviews/urls.py | 3 +++ symposion/reviews/views.py | 28 +++++++++++++++++++++++++++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/symposion/reviews/urls.py b/symposion/reviews/urls.py index ba100c71..0ecc34e2 100644 --- a/symposion/reviews/urls.py +++ b/symposion/reviews/urls.py @@ -9,6 +9,7 @@ from .views import ( result_notification, result_notification_prepare, result_notification_send, + review_random_proposal, review_detail, review_delete, review_assignments, @@ -19,6 +20,7 @@ urlpatterns = [ url(r"^section/(?P[\w\-]+)/all/$", review_section, {"reviewed": "all"}, name="review_section"), url(r"^section/(?P[\w\-]+)/reviewed/$", review_section, {"reviewed": "reviewed"}, name="user_reviewed"), url(r"^section/(?P[\w\-]+)/not_reviewed/$", review_section, {"reviewed": "not_reviewed"}, name="user_not_reviewed"), + url(r"^section/(?P[\w\-]+)/random/$", review_random_proposal, name="user_random"), url(r"^section/(?P[\w\-]+)/assignments/$", review_section, {"assigned": True}, name="review_section_assignments"), url(r"^section/(?P[\w\-]+)/status/$", review_status, name="review_status"), url(r"^section/(?P[\w\-]+)/status/(?P\w+)/$", review_status, name="review_status"), @@ -29,6 +31,7 @@ urlpatterns = [ url(r"^section/(?P[\w\-]+)/notification/(?P\w+)/prepare/$", result_notification_prepare, name="result_notification_prepare"), url(r"^section/(?P[\w\-]+)/notification/(?P\w+)/send/$", result_notification_send, name="result_notification_send"), + url(r"^review/(?P\d+)/$", review_detail, name="review_detail"), url(r"^(?P\d+)/delete/$", review_delete, name="review_delete"), diff --git a/symposion/reviews/views.py b/symposion/reviews/views.py index c1ece5bb..dc6d969a 100644 --- a/symposion/reviews/views.py +++ b/symposion/reviews/views.py @@ -1,3 +1,5 @@ +import random + from django.core.mail import send_mass_mail from django.db.models import Q from django.http import HttpResponseBadRequest, HttpResponseNotAllowed @@ -103,6 +105,30 @@ def review_section(request, section_slug, assigned=False, reviewed="all"): return render(request, "symposion/reviews/review_list.html", ctx) +@login_required +def review_random_proposal(request, section_slug): + # lca2017 #16 view for random proposal + + if not request.user.has_perm("reviews.can_review_%s" % section_slug): + return access_not_permitted(request) + + section = get_object_or_404(ProposalSection, section__slug=section_slug) + queryset = ProposalBase.objects.filter(kind__section=section.section) + # Remove ones already reviewed + queryset = queryset.exclude(reviews__user=request.user) + # Remove talks the reviewer can't vote on -- their own. + queryset = queryset.exclude(speaker__user=request.user) + queryset = queryset.exclude(additional_speakers__user=request.user) + + if len(queryset) == 0: + return redirect("review_section", section_slug=section_slug, reviewed="all") + + # Realistically, there shouldn't be all that many proposals to choose + # from, so this should be cheap. + chosen = random.choice(queryset.all()) + return redirect("review_detail", pk=chosen.pk) + + @login_required def review_list(request, section_slug, user_pk): @@ -124,7 +150,7 @@ def review_list(request, section_slug, user_pk): ctx = { "proposals": proposals, } - return render(request, "symposion/reviews/review_list.html", ctx) + return (request, "symposion/reviews/review_list.html", ctx) @login_required From 073174e617b1c5fd022cb61c80d73c111659db24 Mon Sep 17 00:00:00 2001 From: Scott Bragg Date: Fri, 1 Jul 2016 10:10:23 +1000 Subject: [PATCH 673/751] Adds bcc to emails sent from server --- symposion/utils/mail.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/symposion/utils/mail.py b/symposion/utils/mail.py index e9759d09..8789e044 100644 --- a/symposion/utils/mail.py +++ b/symposion/utils/mail.py @@ -24,7 +24,8 @@ def send_email(to, kind, **kwargs): message_plaintext = strip_tags(message_html) from_email = settings.DEFAULT_FROM_EMAIL + bcc_email = settings.ENVELOPE_BCC_LIST - email = EmailMultiAlternatives(subject, message_plaintext, from_email, to) + email = EmailMultiAlternatives(subject, message_plaintext, from_email, to, bcc=bcc_email) email.attach_alternative(message_html, "text/html") email.send() From e17df73dde6e081fdb8fa091f140f3f7706b8b20 Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer <_@chrisjrn.com> Date: Sun, 3 Jul 2016 12:14:07 +1000 Subject: [PATCH 674/751] Update models.py en-GB spelling of "programme" --- symposion/speakers/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/symposion/speakers/models.py b/symposion/speakers/models.py index 1e94693b..8edaf428 100644 --- a/symposion/speakers/models.py +++ b/symposion/speakers/models.py @@ -23,7 +23,7 @@ class Speaker(models.Model): user = models.OneToOneField(User, null=True, related_name="speaker_profile", verbose_name=_("User")) name = models.CharField(verbose_name=_("Name"), max_length=100, help_text=_("As you would like it to appear in the" - " conference program.")) + " conference programme.")) biography = models.TextField( blank=True, help_text=_("This will appear on the conference website and in the " From 6811708b334d3bd8ad4ea9cfe4c17ced028f43f4 Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer Date: Mon, 4 Jul 2016 16:10:02 +1000 Subject: [PATCH 675/751] Adds a view that returns all proposals in CSV format. Fixes #27 --- symposion/reviews/urls.py | 6 +++++- symposion/reviews/views.py | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/symposion/reviews/urls.py b/symposion/reviews/urls.py index 0ecc34e2..6c35f3bb 100644 --- a/symposion/reviews/urls.py +++ b/symposion/reviews/urls.py @@ -14,6 +14,7 @@ from .views import ( review_delete, review_assignments, review_assignment_opt_out, + review_all_proposals_csv, ) urlpatterns = [ @@ -36,5 +37,8 @@ urlpatterns = [ url(r"^(?P\d+)/delete/$", review_delete, name="review_delete"), url(r"^assignments/$", review_assignments, name="review_assignments"), - url(r"^assignment/(?P\d+)/opt-out/$", review_assignment_opt_out, name="review_assignment_opt_out") + url(r"^assignment/(?P\d+)/opt-out/$", review_assignment_opt_out, name="review_assignment_opt_out"), + + url(r"^csv$", review_all_proposals_csv, name="review_all_proposals_csv"), + ] diff --git a/symposion/reviews/views.py b/symposion/reviews/views.py index 9bbbddf5..94ac7d70 100644 --- a/symposion/reviews/views.py +++ b/symposion/reviews/views.py @@ -1,7 +1,10 @@ +import csv import random +import StringIO from django.core.mail import send_mass_mail from django.db.models import Q +from django.http import HttpResponse from django.http import HttpResponseBadRequest, HttpResponseNotAllowed from django.shortcuts import render, redirect, get_object_or_404 from django.template import Context, Template @@ -107,6 +110,40 @@ def review_section(request, section_slug, assigned=False, reviewed="all"): return render(request, "symposion/reviews/review_list.html", ctx) +@login_required +def review_all_proposals_csv(request): + ''' Returns a CSV representation of all of the proposals this user has + permisison to review. ''' + + queryset = ProposalBase.objects.filter() + + # The fields from each proposal object to report in the csv + fields = [ + "id", "kind", "speaker_name", "title", "submitted", "cancelled", "status", + "score", "total_votes", "minus_two", "minus_one", "plus_one", "plus_two", + ] + + output = StringIO.StringIO() + writer = csv.writer(output, quoting=csv.QUOTE_NONNUMERIC) + + # Fields are the heading + writer.writerow(fields) + + for proposal in proposals_generator(request, queryset, check_speaker=False): + + proposal.speaker_name = proposal.speaker.name + proposal.kind = proposal.kind.section.slug + + if not request.user.has_perm("reviews.can_review_%s" % proposal.kind): + continue + + csv_line = [getattr(proposal, field) for field in fields] + + writer.writerow(csv_line) + + return HttpResponse(output.getvalue(), "text/csv") + + @login_required def review_random_proposal(request, section_slug): # lca2017 #16 view for random proposal From 6ebd0f09057b6625a488dfb5b489f51d3b13da7c Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer Date: Mon, 4 Jul 2016 16:28:31 +1000 Subject: [PATCH 676/751] snoops --- symposion/reviews/views.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/symposion/reviews/views.py b/symposion/reviews/views.py index 94ac7d70..349966b8 100644 --- a/symposion/reviews/views.py +++ b/symposion/reviews/views.py @@ -119,7 +119,7 @@ def review_all_proposals_csv(request): # The fields from each proposal object to report in the csv fields = [ - "id", "kind", "speaker_name", "title", "submitted", "cancelled", "status", + "id", "proposal_type", "speaker_name", "title", "submitted", "cancelled", "status", "score", "total_votes", "minus_two", "minus_one", "plus_one", "plus_two", ] @@ -132,9 +132,10 @@ def review_all_proposals_csv(request): for proposal in proposals_generator(request, queryset, check_speaker=False): proposal.speaker_name = proposal.speaker.name - proposal.kind = proposal.kind.section.slug + section_slug = proposal.kind.section.slug + proposal.proposal_type = section_slug - if not request.user.has_perm("reviews.can_review_%s" % proposal.kind): + if not request.user.has_perm("reviews.can_review_%s" % section_slug): continue csv_line = [getattr(proposal, field) for field in fields] From f1263303906518b5a93edb32aa25e3f1fcb540a5 Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer Date: Mon, 4 Jul 2016 22:18:34 +1000 Subject: [PATCH 677/751] Handles unicode strings properly. --- symposion/reviews/views.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/symposion/reviews/views.py b/symposion/reviews/views.py index 349966b8..b6569de4 100644 --- a/symposion/reviews/views.py +++ b/symposion/reviews/views.py @@ -140,6 +140,11 @@ def review_all_proposals_csv(request): csv_line = [getattr(proposal, field) for field in fields] + # Enusre that unicode items are handled properly. + for i, item in enumerate(csv_line): + if isinstance(item, unicode): + csv_line[i] = item.encode("utf8") + writer.writerow(csv_line) return HttpResponse(output.getvalue(), "text/csv") From 0c4597c4eae5b3ba35a2e53a1c70f04d8735b0df Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer <_@chrisjrn.com> Date: Fri, 22 Jul 2016 13:37:24 -0700 Subject: [PATCH 678/751] Update models.py Fixes a typo. Closes issue #31. --- symposion/speakers/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/symposion/speakers/models.py b/symposion/speakers/models.py index 8edaf428..8e832901 100644 --- a/symposion/speakers/models.py +++ b/symposion/speakers/models.py @@ -87,7 +87,7 @@ class Speaker(models.Model): ) agreement = models.BooleanField( default=False, - help_text=_("I agree to the terms and confitions of attendance, and " + help_text=_("I agree to the terms and conditions of attendance, and " "I have read, understood, and agree to act according to " "the standards set forth in our Code of Conduct ") ) From c0f47070323e2ec9a3f50f84cd684ab3b195bd82 Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer Date: Mon, 25 Jul 2016 14:24:25 -0700 Subject: [PATCH 679/751] Adds the speaker's e-mail address to the CSV output --- symposion/reviews/views.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/symposion/reviews/views.py b/symposion/reviews/views.py index b6569de4..29f85ed8 100644 --- a/symposion/reviews/views.py +++ b/symposion/reviews/views.py @@ -119,7 +119,8 @@ def review_all_proposals_csv(request): # The fields from each proposal object to report in the csv fields = [ - "id", "proposal_type", "speaker_name", "title", "submitted", "cancelled", "status", + "id", "proposal_type", "speaker_name","speaker_email", "title", + "submitted", "cancelled", "status", "score", "total_votes", "minus_two", "minus_one", "plus_one", "plus_two", ] From 5a8b7ecc30f48cba1f66f2ac99ecfe0a66f3f66d Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer Date: Tue, 2 Aug 2016 10:51:43 +1000 Subject: [PATCH 680/751] Fixes markdown cheat sheet links in speaker and proposals forms. Fixes #33. --- symposion/proposals/models.py | 12 +++++++----- symposion/speakers/models.py | 6 +++--- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/symposion/proposals/models.py b/symposion/proposals/models.py index 0a45b9d8..4fe07c2d 100644 --- a/symposion/proposals/models.py +++ b/symposion/proposals/models.py @@ -90,7 +90,8 @@ class ProposalBase(models.Model): _("Abstract"), help_text=_("This will appear in the conference programme. Up to about " "500 words. Edit using Markdown.") + "href='http://warpedvisions.org/projects/" + "markdown-cheat-sheet/' target='_blank'>Markdown.") ) abstract_html = models.TextField(blank=True) @@ -98,8 +99,9 @@ class ProposalBase(models.Model): _("Private Abstract"), help_text=_("This will only be shown to organisers and reviewers. You " "should provide any details about your proposal that you " - "don't want to be public here. Edit using Markdown.") + "don't want to be public here. Edit using Markdown.") ) private_abstract_html = models.TextField(blank=True) @@ -112,8 +114,8 @@ class ProposalBase(models.Model): "static IP address, A/V equipment or will be demonstrating " "security-related techniques on the conference network. " "Edit using Markdown.") + "href='http://warpedvisions.org/projects/" + "markdown-cheat-sheet/' target='_blank'>Markdown.") ) technical_requirements_html = models.TextField(blank=True) diff --git a/symposion/speakers/models.py b/symposion/speakers/models.py index 8e832901..d1ba409e 100644 --- a/symposion/speakers/models.py +++ b/symposion/speakers/models.py @@ -30,7 +30,7 @@ class Speaker(models.Model): "programme. Please write in the third person, eg 'Alice " "is a Moblin hacker...', 150-200 words. Edit using " "" + "markdown-cheat-sheet/' target='_blank'>" "Markdown."), verbose_name=_("Biography"), ) @@ -42,7 +42,7 @@ class Speaker(models.Model): "seen by the organisers and reviewers; use it to convince " "them why they should accept your proposal. Edit using " "" + "markdown-cheat-sheet/' target='_blank'>" "Markdown."), verbose_name=_("Speaking experience"), ) @@ -68,7 +68,7 @@ class Speaker(models.Model): help_text=_("Please describe any special accessibility requirements " "that you may have. Edit using " "Markdown."), + "markdown-cheat-sheet/' target='_blank'>Markdown."), verbose_name=_("Accessibility requirements")) accessibility_html = models.TextField(blank=True) travel_assistance = models.BooleanField( From 03a231093fde33cc1e100ecff05421f65f864d72 Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer Date: Tue, 2 Aug 2016 10:53:53 +1000 Subject: [PATCH 681/751] For some reason, our vote label in reviews.py used a unicode en dash. Oops. Fixes #34. --- symposion/reviews/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/symposion/reviews/models.py b/symposion/reviews/models.py index 6727f6a9..3baa4a6d 100644 --- a/symposion/reviews/models.py +++ b/symposion/reviews/models.py @@ -37,8 +37,8 @@ class Votes(object): ABSTAIN = "0" PLUS_TWO = "+2" PLUS_ONE = "+1" - MINUS_ONE = "−1" - MINUS_TWO = "−2" + MINUS_ONE = "-1" + MINUS_TWO = "-2" CHOICES = [ (PLUS_TWO, _("+2 — Good proposal and I will argue for it to be accepted.")), From 29fa7c63a9e523e39acda85e5c02a8cb452944f7 Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer Date: Sat, 13 Aug 2016 12:19:58 +1000 Subject: [PATCH 682/751] Makes the random proposal button take you to under reviewed proposals. Fixes #38 --- symposion/reviews/views.py | 66 ++++++++++++++++++++++++-------------- 1 file changed, 42 insertions(+), 24 deletions(-) diff --git a/symposion/reviews/views.py b/symposion/reviews/views.py index 29f85ed8..6531b20e 100644 --- a/symposion/reviews/views.py +++ b/symposion/reviews/views.py @@ -68,6 +68,38 @@ def proposals_generator(request, queryset, user_pk=None, check_speaker=True): yield obj +VOTE_THRESHOLD = settings.SYMPOSION_VOTE_THRESHOLD + +POSITIVE = "positive" +NEGATIVE = "negative" +INDIFFERENT = "indifferent" +CONTROVERSIAL = "controversial" +TOO_FEW = "too_few" + +REVIEW_STATUS_FILTERS = { + # proposals with at least VOTE_THRESHOLD reviews and at least one +2 and no -2s, sorted by + # the 'score' + POSITIVE: lambda qs: qs.filter(result__vote_count__gte=VOTE_THRESHOLD, result__plus_two__gt=0, + result__minus_two=0).order_by("-result__score"), + # proposals with at least VOTE_THRESHOLD reviews and at least one -2 and no +2s, reverse + # sorted by the 'score' + NEGATIVE: lambda qs: qs.filter(result__vote_count__gte=VOTE_THRESHOLD, result__minus_two__gt=0, + result__plus_two=0).order_by("result__score"), + # proposals with at least VOTE_THRESHOLD reviews and neither a +2 or a -2, sorted by total + # votes (lowest first) + INDIFFERENT: lambda qs: qs.filter(result__vote_count__gte=VOTE_THRESHOLD, result__minus_two=0, + result__plus_two=0).order_by("result__vote_count"), + # proposals with at least VOTE_THRESHOLD reviews and both a +2 and -2, sorted by total + # votes (highest first) + CONTROVERSIAL: lambda qs: qs.filter(result__vote_count__gte=VOTE_THRESHOLD, + result__plus_two__gt=0, result__minus_two__gt=0) + .order_by("-result__vote_count"), + # proposals with fewer than VOTE_THRESHOLD reviews + TOO_FEW: lambda qs: qs.filter(result__vote_count__lt=VOTE_THRESHOLD) + .order_by("result__vote_count"), +} + + # Returns a list of all proposals, proposals reviewed by the user, or the proposals the user has # yet to review depending on the link user clicks in dashboard @login_required @@ -169,6 +201,15 @@ def review_random_proposal(request, section_slug): if len(queryset) == 0: return redirect("review_section", section_slug=section_slug, reviewed="all") + # Direct reviewers to underreviewed proposals + too_few_set = REVIEW_STATUS_FILTERS[TOO_FEW](queryset) + indifferent_set = REVIEW_STATUS_FILTERS[INDIFFERENT](queryset) + + if len(too_few_set) > 0: + queryset = too_few_set + elif len(indifferent_set) > 0: + queryset = indifferent_set + # Realistically, there shouldn't be all that many proposals to choose # from, so this should be cheap. chosen = random.choice(queryset.all()) @@ -381,8 +422,6 @@ def review_status(request, section_slug=None, key=None): if not request.user.has_perm("reviews.can_review_%s" % section_slug): return access_not_permitted(request) - VOTE_THRESHOLD = settings.SYMPOSION_VOTE_THRESHOLD - ctx = { "section_slug": section_slug, "vote_threshold": VOTE_THRESHOLD, @@ -392,28 +431,7 @@ def review_status(request, section_slug=None, key=None): if section_slug: queryset = queryset.filter(kind__section__slug=section_slug) - proposals = { - # proposals with at least VOTE_THRESHOLD reviews and at least one +2 and no -2s, sorted by - # the 'score' - "positive": queryset.filter(result__vote_count__gte=VOTE_THRESHOLD, result__plus_two__gt=0, - result__minus_two=0).order_by("-result__score"), - # proposals with at least VOTE_THRESHOLD reviews and at least one -2 and no +2s, reverse - # sorted by the 'score' - "negative": queryset.filter(result__vote_count__gte=VOTE_THRESHOLD, result__minus_two__gt=0, - result__plus_two=0).order_by("result__score"), - # proposals with at least VOTE_THRESHOLD reviews and neither a +2 or a -2, sorted by total - # votes (lowest first) - "indifferent": queryset.filter(result__vote_count__gte=VOTE_THRESHOLD, result__minus_two=0, - result__plus_two=0).order_by("result__vote_count"), - # proposals with at least VOTE_THRESHOLD reviews and both a +2 and -2, sorted by total - # votes (highest first) - "controversial": queryset.filter(result__vote_count__gte=VOTE_THRESHOLD, - result__plus_two__gt=0, result__minus_two__gt=0) - .order_by("-result__vote_count"), - # proposals with fewer than VOTE_THRESHOLD reviews - "too_few": queryset.filter(result__vote_count__lt=VOTE_THRESHOLD) - .order_by("result__vote_count"), - } + proposals = dict((key, filt(queryset)) for key, filt in REVIEW_STATUS_FILTERS.items()) admin = request.user.has_perm("reviews.can_manage_%s" % section_slug) From 2a6c19244936e23af23eb71153ec53b30ab53145 Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer Date: Mon, 15 Aug 2016 10:17:27 +1000 Subject: [PATCH 683/751] Refactors the CSV piece to be nicer than it previously was. --- symposion/reviews/views.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/symposion/reviews/views.py b/symposion/reviews/views.py index 6531b20e..b4dd8d99 100644 --- a/symposion/reviews/views.py +++ b/symposion/reviews/views.py @@ -147,6 +147,10 @@ def review_all_proposals_csv(request): ''' Returns a CSV representation of all of the proposals this user has permisison to review. ''' + response = HttpResponse("text/csv") + response['Content-Disposition'] = 'attachment; filename="proposals.csv"' + writer = csv.writer(response, quoting=csv.QUOTE_NONNUMERIC) + queryset = ProposalBase.objects.filter() # The fields from each proposal object to report in the csv @@ -156,9 +160,6 @@ def review_all_proposals_csv(request): "score", "total_votes", "minus_two", "minus_one", "plus_one", "plus_two", ] - output = StringIO.StringIO() - writer = csv.writer(output, quoting=csv.QUOTE_NONNUMERIC) - # Fields are the heading writer.writerow(fields) @@ -180,7 +181,7 @@ def review_all_proposals_csv(request): writer.writerow(csv_line) - return HttpResponse(output.getvalue(), "text/csv") + return response @login_required From 3217f43af2c5a296e7e76225dce5186a082eb027 Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer Date: Mon, 15 Aug 2016 10:41:51 +1000 Subject: [PATCH 684/751] Adds abstains and averages to the reviewers scores list. --- symposion/reviews/views.py | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/symposion/reviews/views.py b/symposion/reviews/views.py index b4dd8d99..97ccb156 100644 --- a/symposion/reviews/views.py +++ b/symposion/reviews/views.py @@ -258,23 +258,30 @@ def review_admin(request, section_slug): already_seen.add(user.pk) user.comment_count = Review.objects.filter(user=user).count() - user.total_votes = LatestVote.objects.filter(user=user).count() - user.plus_two = LatestVote.objects.filter( - user=user, - vote=LatestVote.VOTES.PLUS_TWO + user_votes = LatestVote.objects.filter( + user=user + ) + user.total_votes = user_votes.exclude( + vote=LatestVote.VOTES.ABSTAIN, ).count() - user.plus_one = LatestVote.objects.filter( - user=user, - vote=LatestVote.VOTES.PLUS_ONE + user.plus_two = user_votes.filter( + vote=LatestVote.VOTES.PLUS_TWO, ).count() - user.minus_one = LatestVote.objects.filter( - user=user, - vote=LatestVote.VOTES.MINUS_ONE + user.plus_one = user_votes.filter( + vote=LatestVote.VOTES.PLUS_ONE, ).count() - user.minus_two = LatestVote.objects.filter( - user=user, - vote=LatestVote.VOTES.MINUS_TWO + user.minus_one = user_votes.filter( + vote=LatestVote.VOTES.MINUS_ONE, ).count() + user.minus_two = user_votes.filter( + vote=LatestVote.VOTES.MINUS_TWO, + ).count() + user.abstain = user_votes.filter( + vote=LatestVote.VOTES.ABSTAIN, + ).count() + user.average = ( + user.plus_two + user.plus_one + user.minus_one + user.minus_two + ) / (user.total_votes * 1.0) yield user From 24f8ac875711263bb346eb24aaec9eecd3c1f773 Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer Date: Mon, 15 Aug 2016 10:44:44 +1000 Subject: [PATCH 685/751] Makes the reviewer proposals list render properly --- symposion/reviews/urls.py | 2 +- symposion/reviews/views.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/symposion/reviews/urls.py b/symposion/reviews/urls.py index 6c35f3bb..6f8e9c43 100644 --- a/symposion/reviews/urls.py +++ b/symposion/reviews/urls.py @@ -25,7 +25,7 @@ urlpatterns = [ url(r"^section/(?P[\w\-]+)/assignments/$", review_section, {"assigned": True}, name="review_section_assignments"), url(r"^section/(?P[\w\-]+)/status/$", review_status, name="review_status"), url(r"^section/(?P[\w\-]+)/status/(?P\w+)/$", review_status, name="review_status"), - url(r"^section/(?P[\w\-]+)/list/(?P\d+)/$", review_list, name="review_list_user"), + url(r"^section/(?P[\w\-]+)/list_reviewer/(?P\d+)/$", review_list, name="review_list_user"), url(r"^section/(?P[\w\-]+)/admin/$", review_admin, name="review_admin"), url(r"^section/(?P[\w\-]+)/admin/accept/$", review_bulk_accept, name="review_bulk_accept"), url(r"^section/(?P[\w\-]+)/notification/(?P\w+)/$", result_notification, name="result_notification"), diff --git a/symposion/reviews/views.py b/symposion/reviews/views.py index 97ccb156..605051e6 100644 --- a/symposion/reviews/views.py +++ b/symposion/reviews/views.py @@ -238,7 +238,7 @@ def review_list(request, section_slug, user_pk): ctx = { "proposals": proposals, } - return (request, "symposion/reviews/review_list.html", ctx) + return render(request, "symposion/reviews/review_list.html", ctx) @login_required From 9ae17146df49675270e74b024ca3a6b9ec7a6330 Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer Date: Mon, 15 Aug 2016 11:11:25 +1000 Subject: [PATCH 686/751] More work on the reviewers list --- symposion/reviews/views.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/symposion/reviews/views.py b/symposion/reviews/views.py index 605051e6..a9c3d7f5 100644 --- a/symposion/reviews/views.py +++ b/symposion/reviews/views.py @@ -259,8 +259,11 @@ def review_admin(request, section_slug): user.comment_count = Review.objects.filter(user=user).count() user_votes = LatestVote.objects.filter( - user=user + user=user, + proposal__kind__section__slug=section_slug, ) + print section_slug + print [vote.proposal.kind.section.slug for vote in user_votes] user.total_votes = user_votes.exclude( vote=LatestVote.VOTES.ABSTAIN, ).count() @@ -279,9 +282,13 @@ def review_admin(request, section_slug): user.abstain = user_votes.filter( vote=LatestVote.VOTES.ABSTAIN, ).count() - user.average = ( - user.plus_two + user.plus_one + user.minus_one + user.minus_two - ) / (user.total_votes * 1.0) + if user.total_votes == 0: + user.average = "-" + else: + user.average = ( + user.plus_two + user.plus_one + + user.minus_one + user.minus_two + ) / (user.total_votes * 1.0) yield user From 9223c8fc909214c59d02f42c473cb417bc3d4796 Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer Date: Mon, 15 Aug 2016 12:22:40 +1000 Subject: [PATCH 687/751] =?UTF-8?q?Adds=20=E2=80=9CSubmit=20review=20and?= =?UTF-8?q?=20jump=20to=20random=20proposal=E2=80=9D=20button?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- symposion/reviews/views.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/symposion/reviews/views.py b/symposion/reviews/views.py index a9c3d7f5..e74a1193 100644 --- a/symposion/reviews/views.py +++ b/symposion/reviews/views.py @@ -325,7 +325,7 @@ def review_detail(request, pk): if request.user in speakers: return access_not_permitted(request) - if "vote_submit" in request.POST: + if "vote_submit" in request.POST or "vote_submit_and_random" in request.POST: review_form = ReviewForm(request.POST) if review_form.is_valid(): @@ -334,7 +334,13 @@ def review_detail(request, pk): review.proposal = proposal review.save() - return redirect(request.path) + if "vote_submit_and_random" in request.POST: + next_page = redirect("user_random", proposal.kind.section.slug) + next_page["Location"] += "#invalid_fragment" # Hack. + else: + next_page = redirect(request.path) + + return next_page else: message_form = SpeakerCommentForm() elif "message_submit" in request.POST and admin: From cc6db4ed8882d1452d0a20ba85cf2db466e03660 Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer Date: Mon, 15 Aug 2016 15:35:25 +1000 Subject: [PATCH 688/751] Fixes some errors with reviewer averages --- symposion/reviews/views.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/symposion/reviews/views.py b/symposion/reviews/views.py index e74a1193..7139a98e 100644 --- a/symposion/reviews/views.py +++ b/symposion/reviews/views.py @@ -257,13 +257,16 @@ def review_admin(request, section_slug): continue already_seen.add(user.pk) - user.comment_count = Review.objects.filter(user=user).count() + user.comment_count = Review.objects.filter( + user=user, + proposal__kind__section__slug=section_slug, + ).count() + user_votes = LatestVote.objects.filter( user=user, proposal__kind__section__slug=section_slug, ) - print section_slug - print [vote.proposal.kind.section.slug for vote in user_votes] + user.total_votes = user_votes.exclude( vote=LatestVote.VOTES.ABSTAIN, ).count() @@ -282,19 +285,23 @@ def review_admin(request, section_slug): user.abstain = user_votes.filter( vote=LatestVote.VOTES.ABSTAIN, ).count() + if user.total_votes == 0: user.average = "-" else: user.average = ( - user.plus_two + user.plus_one + - user.minus_one + user.minus_two + ((user.plus_two * 2) + user.plus_one) - + ((user.minus_two * 2) + user.minus_one) ) / (user.total_votes * 1.0) yield user + reviewers_sorted = list(reviewers()) + sort(reviewers_sorted: key= lambda reviewer: 0 - reviewer.total_votes) + ctx = { "section_slug": section_slug, - "reviewers": reviewers(), + "reviewers": reviewers_sorted, } return render(request, "symposion/reviews/review_admin.html", ctx) From 3f5ee591b6b44b254b17673665e88f4d4a3b27c5 Mon Sep 17 00:00:00 2001 From: Scott Bragg Date: Mon, 15 Aug 2016 18:56:54 +1000 Subject: [PATCH 689/751] Revert "(un-oops)" --- symposion/reviews/views.py | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/symposion/reviews/views.py b/symposion/reviews/views.py index 7139a98e..e74a1193 100644 --- a/symposion/reviews/views.py +++ b/symposion/reviews/views.py @@ -257,16 +257,13 @@ def review_admin(request, section_slug): continue already_seen.add(user.pk) - user.comment_count = Review.objects.filter( - user=user, - proposal__kind__section__slug=section_slug, - ).count() - + user.comment_count = Review.objects.filter(user=user).count() user_votes = LatestVote.objects.filter( user=user, proposal__kind__section__slug=section_slug, ) - + print section_slug + print [vote.proposal.kind.section.slug for vote in user_votes] user.total_votes = user_votes.exclude( vote=LatestVote.VOTES.ABSTAIN, ).count() @@ -285,23 +282,19 @@ def review_admin(request, section_slug): user.abstain = user_votes.filter( vote=LatestVote.VOTES.ABSTAIN, ).count() - if user.total_votes == 0: user.average = "-" else: user.average = ( - ((user.plus_two * 2) + user.plus_one) - - ((user.minus_two * 2) + user.minus_one) + user.plus_two + user.plus_one + + user.minus_one + user.minus_two ) / (user.total_votes * 1.0) yield user - reviewers_sorted = list(reviewers()) - sort(reviewers_sorted: key= lambda reviewer: 0 - reviewer.total_votes) - ctx = { "section_slug": section_slug, - "reviewers": reviewers_sorted, + "reviewers": reviewers(), } return render(request, "symposion/reviews/review_admin.html", ctx) From b9b27abdce373ac420183a0f6faa89c7d2a9c3dd Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer Date: Mon, 15 Aug 2016 22:42:54 +1000 Subject: [PATCH 690/751] sigh --- symposion/reviews/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/symposion/reviews/views.py b/symposion/reviews/views.py index 7139a98e..82d797fd 100644 --- a/symposion/reviews/views.py +++ b/symposion/reviews/views.py @@ -297,7 +297,7 @@ def review_admin(request, section_slug): yield user reviewers_sorted = list(reviewers()) - sort(reviewers_sorted: key= lambda reviewer: 0 - reviewer.total_votes) + reviewers_sorted.sort(key= lambda reviewer: 0 - reviewer.total_votes) ctx = { "section_slug": section_slug, From faa30b8866bf12eb7478c31da6f92bc28ac61926 Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer <_@chrisjrn.com> Date: Tue, 16 Aug 2016 09:14:50 +1000 Subject: [PATCH 691/751] Update views.py PEBCAK --- symposion/reviews/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/symposion/reviews/views.py b/symposion/reviews/views.py index d50fe3d7..5f53ec0a 100644 --- a/symposion/reviews/views.py +++ b/symposion/reviews/views.py @@ -297,7 +297,7 @@ def review_admin(request, section_slug): ctx = { "section_slug": section_slug, - "reviewers": reviewers(), + "reviewers": reviewers_sorted, } return render(request, "symposion/reviews/review_admin.html", ctx) From 20ad44236bb5dca4aae70dd66b886dfb1e8f2cf7 Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer <_@chrisjrn.com> Date: Tue, 16 Aug 2016 09:34:04 +1000 Subject: [PATCH 692/751] PEBCAK 2 removed --- symposion/reviews/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/symposion/reviews/views.py b/symposion/reviews/views.py index 5f53ec0a..8861c8d2 100644 --- a/symposion/reviews/views.py +++ b/symposion/reviews/views.py @@ -286,8 +286,8 @@ def review_admin(request, section_slug): user.average = "-" else: user.average = ( - user.plus_two + user.plus_one + - user.minus_one + user.minus_two + ((user.plus_two * 2) + user.plus_one) - + ((user.minus_two * 2) + user.minus_one) ) / (user.total_votes * 1.0) yield user From 32c2d697b0f08fa2e928f90bce41b6429a5c8071 Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer <_@chrisjrn.com> Date: Tue, 16 Aug 2016 09:35:34 +1000 Subject: [PATCH 693/751] PEBACK 3 --- symposion/reviews/views.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/symposion/reviews/views.py b/symposion/reviews/views.py index 8861c8d2..99c5bbdb 100644 --- a/symposion/reviews/views.py +++ b/symposion/reviews/views.py @@ -257,7 +257,11 @@ def review_admin(request, section_slug): continue already_seen.add(user.pk) - user.comment_count = Review.objects.filter(user=user).count() + user.comment_count = Review.objects.filter( + user=user, + proposal__kind__section__slug=section_slug, + ).count() + user_votes = LatestVote.objects.filter( user=user, proposal__kind__section__slug=section_slug, From 7b6843ca1e091b2d96dc4aeef0859a2dde6b5fe4 Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer Date: Wed, 17 Aug 2016 07:27:32 +1000 Subject: [PATCH 694/751] all-reviews CSV now includes the proposal type rather than the proposal section --- symposion/reviews/views.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/symposion/reviews/views.py b/symposion/reviews/views.py index 99c5bbdb..060dc28a 100644 --- a/symposion/reviews/views.py +++ b/symposion/reviews/views.py @@ -167,7 +167,8 @@ def review_all_proposals_csv(request): proposal.speaker_name = proposal.speaker.name section_slug = proposal.kind.section.slug - proposal.proposal_type = section_slug + kind_slug = proposal.kind.slug + proposal.proposal_type = kind_slug if not request.user.has_perm("reviews.can_review_%s" % section_slug): continue From 07198b2ecfd60c16314b997c0dea87768084380f Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer Date: Wed, 17 Aug 2016 07:35:46 +1000 Subject: [PATCH 695/751] Direct reviewers to the controversial talks instead of the indifferent talks --- symposion/reviews/views.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/symposion/reviews/views.py b/symposion/reviews/views.py index 060dc28a..c68e0d9e 100644 --- a/symposion/reviews/views.py +++ b/symposion/reviews/views.py @@ -205,12 +205,12 @@ def review_random_proposal(request, section_slug): # Direct reviewers to underreviewed proposals too_few_set = REVIEW_STATUS_FILTERS[TOO_FEW](queryset) - indifferent_set = REVIEW_STATUS_FILTERS[INDIFFERENT](queryset) + controversial_set = REVIEW_STATUS_FILTERS[CONTROVERSIAL](queryset) if len(too_few_set) > 0: queryset = too_few_set - elif len(indifferent_set) > 0: - queryset = indifferent_set + elif len(controversial_set) > 0: + queryset = controversial_set # Realistically, there shouldn't be all that many proposals to choose # from, so this should be cheap. From 5735c7745e44aa2ec39465175659e54bdf5ce4d6 Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer Date: Wed, 17 Aug 2016 07:44:28 +1000 Subject: [PATCH 696/751] =?UTF-8?q?The=20=E2=80=9Cfree=20for=20all?= =?UTF-8?q?=E2=80=9D=20random=20reviews=20should=20now=20direct=20reviewer?= =?UTF-8?q?s=20to=20under-reviewed=20proposals=20more=20generally.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- symposion/reviews/views.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/symposion/reviews/views.py b/symposion/reviews/views.py index c68e0d9e..482ec0a4 100644 --- a/symposion/reviews/views.py +++ b/symposion/reviews/views.py @@ -208,13 +208,21 @@ def review_random_proposal(request, section_slug): controversial_set = REVIEW_STATUS_FILTERS[CONTROVERSIAL](queryset) if len(too_few_set) > 0: - queryset = too_few_set + proposals = too_few_set.all() elif len(controversial_set) > 0: - queryset = controversial_set + proposals = controversial_set.all() + else: + # Select a proposal with less than the median number of total votes + proposals = proposals_generator(request, queryset, check_speaker=False) + proposals = list(proposals) + proposals.sort(key = lambda proposal: proposal.total_votes) + # The first half is the median or less. + # The +1 means we round _up_. + proposals = proposals[:(len(proposals) + 1) / 2] # Realistically, there shouldn't be all that many proposals to choose # from, so this should be cheap. - chosen = random.choice(queryset.all()) + chosen = random.choice(proposals) return redirect("review_detail", pk=chosen.pk) @@ -262,7 +270,7 @@ def review_admin(request, section_slug): user=user, proposal__kind__section__slug=section_slug, ).count() - + user_votes = LatestVote.objects.filter( user=user, proposal__kind__section__slug=section_slug, From d56bcea2e6839bb13f0dadb426efd6d66fd51335 Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer Date: Wed, 17 Aug 2016 08:09:21 +1000 Subject: [PATCH 697/751] =?UTF-8?q?Makes=20the=20=E2=80=9Creviewer?= =?UTF-8?q?=E2=80=99s=20reviews=E2=80=9D=20page=20filter=20by=20section?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- symposion/reviews/views.py | 1 + 1 file changed, 1 insertion(+) diff --git a/symposion/reviews/views.py b/symposion/reviews/views.py index 482ec0a4..a3eebfc6 100644 --- a/symposion/reviews/views.py +++ b/symposion/reviews/views.py @@ -237,6 +237,7 @@ def review_list(request, section_slug, user_pk): queryset = ProposalBase.objects.select_related("speaker__user", "result") reviewed = LatestVote.objects.filter(user__pk=user_pk).values_list("proposal", flat=True) + queryset = queryset.filter(kind__section__slug=section_slug) queryset = queryset.filter(pk__in=reviewed) proposals = queryset.order_by("submitted") From efd6ff88f8115d58a4357c63d0ed61e92b85fa5d Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer <_@chrisjrn.com> Date: Fri, 19 Aug 2016 10:29:04 +1000 Subject: [PATCH 698/751] "Random selection" change Reduces the frequency with which controversial proposals are brought to the front of the review queue. --- symposion/reviews/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/symposion/reviews/views.py b/symposion/reviews/views.py index a3eebfc6..531f2c86 100644 --- a/symposion/reviews/views.py +++ b/symposion/reviews/views.py @@ -209,7 +209,7 @@ def review_random_proposal(request, section_slug): if len(too_few_set) > 0: proposals = too_few_set.all() - elif len(controversial_set) > 0: + elif len(controversial_set) > 0 and random.random() < 0.2: proposals = controversial_set.all() else: # Select a proposal with less than the median number of total votes From 565a353375dd961a8b0a54c6efd06a8fd7942ea7 Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer Date: Sun, 21 Aug 2016 15:28:22 +1000 Subject: [PATCH 699/751] send_mail is no longer hardwired to point at symposion/emails (who does that?!) --- symposion/utils/mail.py | 39 +++++++++++++++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/symposion/utils/mail.py b/symposion/utils/mail.py index 8789e044..4204b4b6 100644 --- a/symposion/utils/mail.py +++ b/symposion/utils/mail.py @@ -1,3 +1,5 @@ +import os + from django.conf import settings from django.core.mail import EmailMultiAlternatives from django.template.loader import render_to_string @@ -6,7 +8,30 @@ from django.utils.html import strip_tags from django.contrib.sites.models import Site -def send_email(to, kind, **kwargs): +def sender(template_prefix): + ''' Creates a function called `send_email` ''' + + def send_email(to, kind, **kwargs): + ''' Sends an e-mail to the given address. + + to: The address + kind: the ID for an e-mail kind; it should point to a subdirectory of + %(template_prefix)s containing subject.txt and message.html, which + are django templates for the subject and HTML message respectively. + + context: a context for rendering the e-mail. + + ''' % {"template_prefix": template_prefix} + + return __send_email__(template_prefix, to, kind, **kwargs) + + return send_email + + +send_email = sender("symposion/emails") + + +def __send_email__(template_prefix, to, kind, **kwargs): current_site = Site.objects.get_current() @@ -15,16 +40,22 @@ def send_email(to, kind, **kwargs): "STATIC_URL": settings.STATIC_URL, } ctx.update(kwargs.get("context", {})) + subject_template = os.path.join(template_prefix, "%s/subject.txt" % kind) + message_template = os.path.join(template_prefix, "%s/message.html" % kind) subject = "[%s] %s" % ( current_site.name, - render_to_string("symposion/emails/%s/subject.txt" % kind, ctx).strip() + render_to_string(subject_template, ctx).strip() ) - message_html = render_to_string("symposion/emails/%s/message.html" % kind, ctx) + message_html = render_to_string(message_template, ctx) message_plaintext = strip_tags(message_html) from_email = settings.DEFAULT_FROM_EMAIL - bcc_email = settings.ENVELOPE_BCC_LIST + + try: + bcc_email = settings.ENVELOPE_BCC_LIST + except AttributeError: + bcc_email = None email = EmailMultiAlternatives(subject, message_plaintext, from_email, to, bcc=bcc_email) email.attach_alternative(message_html, "text/html") From b6b6c51cc17293d453210110380591885278adbb Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer Date: Sun, 21 Aug 2016 15:31:09 +1000 Subject: [PATCH 700/751] Refactors to be a bit less obtuse --- symposion/utils/mail.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/symposion/utils/mail.py b/symposion/utils/mail.py index 4204b4b6..2ba691b6 100644 --- a/symposion/utils/mail.py +++ b/symposion/utils/mail.py @@ -8,27 +8,28 @@ from django.utils.html import strip_tags from django.contrib.sites.models import Site -def sender(template_prefix): - ''' Creates a function called `send_email` ''' +class Sender(object): + ''' Class for sending e-mails under a templete prefix. ''' - def send_email(to, kind, **kwargs): + def __init__(self, template_prefix): + self.template_prefix = template_prefix + + def send_email(self, to, kind, **kwargs): ''' Sends an e-mail to the given address. to: The address kind: the ID for an e-mail kind; it should point to a subdirectory of - %(template_prefix)s containing subject.txt and message.html, which + self.template_prefix containing subject.txt and message.html, which are django templates for the subject and HTML message respectively. context: a context for rendering the e-mail. - ''' % {"template_prefix": template_prefix} + ''' - return __send_email__(template_prefix, to, kind, **kwargs) - - return send_email + return __send_email__(self.template_prefix, to, kind, **kwargs) -send_email = sender("symposion/emails") +send_email = Sender("symposion/emails").send_email def __send_email__(template_prefix, to, kind, **kwargs): From d9b1583dfe61c8dced0b2a7e83ba0323e18b1465 Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer <_@chrisjrn.com> Date: Thu, 25 Aug 2016 10:40:21 +1000 Subject: [PATCH 701/751] Adds more fields to the reviews CSV --- symposion/reviews/views.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/symposion/reviews/views.py b/symposion/reviews/views.py index 531f2c86..d2ef40e5 100644 --- a/symposion/reviews/views.py +++ b/symposion/reviews/views.py @@ -156,8 +156,9 @@ def review_all_proposals_csv(request): # The fields from each proposal object to report in the csv fields = [ "id", "proposal_type", "speaker_name","speaker_email", "title", - "submitted", "cancelled", "status", - "score", "total_votes", "minus_two", "minus_one", "plus_one", "plus_two", + "submitted", "other_speakers", "speaker_travel", + "speaker_accommodation", "cancelled", "status", "score", "total_votes", + "minus_two", "minus_one", "plus_one", "plus_two", ] # Fields are the heading @@ -170,6 +171,21 @@ def review_all_proposals_csv(request): kind_slug = proposal.kind.slug proposal.proposal_type = kind_slug + proposal.other_speakers = ", ".join( + speaker.name + for speaker in proposal.additional_speakers.all() + ) + + proposal.speaker_travel = ", ".join( + str(bool(speaker.travel_assistance)) + for speaker in proposal.speakers() + ) + + proposal.speaker_accommodation = ", ".join( + str(bool(speaker.accommodation_assistance)) + for speaker in proposal.speakers() + ) + if not request.user.has_perm("reviews.can_review_%s" % section_slug): continue From 420d8ec870bd115f0dfd292593771a3f798341b0 Mon Sep 17 00:00:00 2001 From: Scott Bragg Date: Sat, 3 Sep 2016 12:48:31 +1000 Subject: [PATCH 702/751] Remove description from Presentation, add fields to proposal for notification template. --- requirements/base.txt | 2 +- symposion/proposals/models.py | 3 ++- symposion/reviews/models.py | 1 - symposion/reviews/views.py | 6 ++++- .../migrations/0002_auto_20160903_0146.py | 23 +++++++++++++++++++ symposion/schedule/models.py | 3 --- symposion/schedule/views.py | 1 - 7 files changed, 31 insertions(+), 8 deletions(-) create mode 100644 symposion/schedule/migrations/0002_auto_20160903_0146.py diff --git a/requirements/base.txt b/requirements/base.txt index a5e06485..db8c567e 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -1,4 +1,4 @@ -Django>=1.9.2 +Django==1.9.2 django-appconf==1.0.1 django-model-utils==2.4.0 django-reversion==1.10.1 diff --git a/symposion/proposals/models.py b/symposion/proposals/models.py index 4fe07c2d..300407df 100644 --- a/symposion/proposals/models.py +++ b/symposion/proposals/models.py @@ -196,8 +196,9 @@ class ProposalBase(models.Model): def notification_email_context(self): return { "title": self.title, - "speaker": self.speaker.name, + "speaker": self.speaker, "speakers": ', '.join([x.name for x in self.speakers()]), + "additional_speakers": self.additional_speakers, "kind": self.kind.name, } diff --git a/symposion/reviews/models.py b/symposion/reviews/models.py index 3baa4a6d..99b741e9 100644 --- a/symposion/reviews/models.py +++ b/symposion/reviews/models.py @@ -361,7 +361,6 @@ def promote_proposal(proposal): else: presentation = Presentation( title=proposal.title, - description=proposal.description, abstract=proposal.abstract, speaker=proposal.speaker, section=proposal.section, diff --git a/symposion/reviews/views.py b/symposion/reviews/views.py index d2ef40e5..a7fa592f 100644 --- a/symposion/reviews/views.py +++ b/symposion/reviews/views.py @@ -645,7 +645,11 @@ def result_notification_send(request, section_slug, status): rn.template = notification_template rn.to_address = proposal.speaker_email rn.from_address = request.POST["from_address"] - rn.subject = request.POST["subject"] + rn.subject = Template(request.POST["subject"]).render( + Context({ + "proposal": proposal.notification_email_context() + }) + ) rn.body = Template(request.POST["body"]).render( Context({ "proposal": proposal.notification_email_context() diff --git a/symposion/schedule/migrations/0002_auto_20160903_0146.py b/symposion/schedule/migrations/0002_auto_20160903_0146.py new file mode 100644 index 00000000..a1ca17e7 --- /dev/null +++ b/symposion/schedule/migrations/0002_auto_20160903_0146.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.2 on 2016-09-03 01:46 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('symposion_schedule', '0001_initial'), + ] + + operations = [ + migrations.RemoveField( + model_name='presentation', + name='description', + ), + migrations.RemoveField( + model_name='presentation', + name='description_html', + ), + ] diff --git a/symposion/schedule/models.py b/symposion/schedule/models.py index 859faea1..c21a4367 100644 --- a/symposion/schedule/models.py +++ b/symposion/schedule/models.py @@ -185,8 +185,6 @@ class Presentation(models.Model): slot = models.OneToOneField(Slot, null=True, blank=True, related_name="content_ptr", verbose_name=_("Slot")) title = models.CharField(max_length=100, verbose_name=_("Title")) - description = models.TextField(verbose_name=_("Description")) - description_html = models.TextField(blank=True) abstract = models.TextField(verbose_name=_("Abstract")) abstract_html = models.TextField(blank=True) speaker = models.ForeignKey(Speaker, related_name="presentations", verbose_name=_("Speaker")) @@ -197,7 +195,6 @@ class Presentation(models.Model): section = models.ForeignKey(Section, related_name="presentations", verbose_name=_("Section")) def save(self, *args, **kwargs): - self.description_html = parse(self.description) self.abstract_html = parse(self.abstract) return super(Presentation, self).save(*args, **kwargs) diff --git a/symposion/schedule/views.py b/symposion/schedule/views.py index 9c6d7c57..75472fc5 100644 --- a/symposion/schedule/views.py +++ b/symposion/schedule/views.py @@ -225,7 +225,6 @@ def schedule_json(request): s.email for s in slot.content.speakers() ] if request.user.is_staff else ["redacted"], "abstract": slot.content.abstract.raw, - "description": slot.content.description.raw, "conf_url": "%s://%s%s" % ( protocol, Site.objects.get_current().domain, From c7608fb0d5faf64e4d494cd047b71668f16b4f7b Mon Sep 17 00:00:00 2001 From: Scott Bragg Date: Sat, 3 Sep 2016 13:16:05 +1000 Subject: [PATCH 703/751] Added ResultNotification to admin, fixed subject as template --- symposion/reviews/admin.py | 4 +++- symposion/reviews/models.py | 3 +++ symposion/reviews/views.py | 5 +++-- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/symposion/reviews/admin.py b/symposion/reviews/admin.py index a4a33844..c5b0dd48 100644 --- a/symposion/reviews/admin.py +++ b/symposion/reviews/admin.py @@ -1,6 +1,6 @@ from django.contrib import admin -from symposion.reviews.models import NotificationTemplate, ProposalResult +from symposion.reviews.models import NotificationTemplate, ProposalResult, ResultNotification admin.site.register( @@ -16,3 +16,5 @@ admin.site.register( ProposalResult, list_display=['proposal', 'status', 'score', 'vote_count', 'accepted'] ) + +admin.site.register(ResultNotification) \ No newline at end of file diff --git a/symposion/reviews/models.py b/symposion/reviews/models.py index 99b741e9..1a26f8dc 100644 --- a/symposion/reviews/models.py +++ b/symposion/reviews/models.py @@ -349,6 +349,9 @@ class ResultNotification(models.Model): for speaker in self.proposal.speakers(): yield speaker.email + def __unicode__(self): + return self.proposal.title + ' ' + self.timestamp.strftime('%Y-%m-%d %H:%M:%S') + @property def email_args(self): return (self.subject, self.body, self.from_address, self.recipients()) diff --git a/symposion/reviews/views.py b/symposion/reviews/views.py index a7fa592f..eff2f929 100644 --- a/symposion/reviews/views.py +++ b/symposion/reviews/views.py @@ -645,14 +645,15 @@ def result_notification_send(request, section_slug, status): rn.template = notification_template rn.to_address = proposal.speaker_email rn.from_address = request.POST["from_address"] + proposal_context = proposal.notification_email_context() rn.subject = Template(request.POST["subject"]).render( Context({ - "proposal": proposal.notification_email_context() + "proposal": proposal_context }) ) rn.body = Template(request.POST["body"]).render( Context({ - "proposal": proposal.notification_email_context() + "proposal": proposal_context }) ) rn.save() From da562267327ba0686922e8ed31d196b42d44c8f7 Mon Sep 17 00:00:00 2001 From: Scott Bragg Date: Sat, 3 Sep 2016 15:06:01 +1000 Subject: [PATCH 704/751] Changed var name --- symposion/proposals/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/symposion/proposals/models.py b/symposion/proposals/models.py index 300407df..e4b9e563 100644 --- a/symposion/proposals/models.py +++ b/symposion/proposals/models.py @@ -196,7 +196,7 @@ class ProposalBase(models.Model): def notification_email_context(self): return { "title": self.title, - "speaker": self.speaker, + "main_speaker": self.speaker, "speakers": ', '.join([x.name for x in self.speakers()]), "additional_speakers": self.additional_speakers, "kind": self.kind.name, From 6fadca1773a23e74c8d50980bfd2c7f78a590115 Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer Date: Wed, 7 Sep 2016 12:01:34 +1000 Subject: [PATCH 705/751] Removes BCC amendment to mail.py --- symposion/utils/mail.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/symposion/utils/mail.py b/symposion/utils/mail.py index 8789e044..e9759d09 100644 --- a/symposion/utils/mail.py +++ b/symposion/utils/mail.py @@ -24,8 +24,7 @@ def send_email(to, kind, **kwargs): message_plaintext = strip_tags(message_html) from_email = settings.DEFAULT_FROM_EMAIL - bcc_email = settings.ENVELOPE_BCC_LIST - email = EmailMultiAlternatives(subject, message_plaintext, from_email, to, bcc=bcc_email) + email = EmailMultiAlternatives(subject, message_plaintext, from_email, to) email.attach_alternative(message_html, "text/html") email.send() From 433a99a4020c5d99cd59e67cc7c71b96f0a9c459 Mon Sep 17 00:00:00 2001 From: Scott Bragg Date: Sat, 17 Sep 2016 15:53:47 +1000 Subject: [PATCH 706/751] All the migrations seem fixed now --- requirements/base.txt | 2 +- .../conference/migrations/0001_initial.py | 24 ++-- .../proposals/migrations/0001_initial.py | 68 +++++----- symposion/reviews/migrations/0001_initial.py | 122 +++++++++--------- symposion/reviews/models.py | 2 +- symposion/schedule/migrations/0001_initial.py | 67 +++++----- .../migrations/0002_auto_20160903_0146.py | 23 ---- symposion/speakers/migrations/0001_initial.py | 34 +++-- .../0002_speaker_twitter_username.py | 20 --- .../sponsorship/migrations/0001_initial.py | 78 +++++------ 10 files changed, 217 insertions(+), 223 deletions(-) delete mode 100644 symposion/schedule/migrations/0002_auto_20160903_0146.py delete mode 100644 symposion/speakers/migrations/0002_speaker_twitter_username.py diff --git a/requirements/base.txt b/requirements/base.txt index db8c567e..d2fe5479 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -1,4 +1,4 @@ -Django==1.9.2 +Django==1.9.7 django-appconf==1.0.1 django-model-utils==2.4.0 django-reversion==1.10.1 diff --git a/symposion/conference/migrations/0001_initial.py b/symposion/conference/migrations/0001_initial.py index acc4a962..888dcf63 100644 --- a/symposion/conference/migrations/0001_initial.py +++ b/symposion/conference/migrations/0001_initial.py @@ -1,12 +1,16 @@ # -*- coding: utf-8 -*- +# Generated by Django 1.9.7 on 2016-09-17 03:34 from __future__ import unicode_literals -from django.db import models, migrations +from django.db import migrations, models +import django.db.models.deletion import timezone_field.fields class Migration(migrations.Migration): + initial = True + dependencies = [ ] @@ -14,31 +18,31 @@ class Migration(migrations.Migration): migrations.CreateModel( name='Conference', fields=[ - ('id', models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name='ID')), + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('title', models.CharField(max_length=100, verbose_name='Title')), - ('start_date', models.DateField(null=True, blank=True, verbose_name='Start date')), - ('end_date', models.DateField(null=True, blank=True, verbose_name='End date')), + ('start_date', models.DateField(blank=True, null=True, verbose_name='Start date')), + ('end_date', models.DateField(blank=True, null=True, verbose_name='End date')), ('timezone', timezone_field.fields.TimeZoneField(blank=True, verbose_name='timezone')), ], options={ - 'verbose_name_plural': 'conferences', 'verbose_name': 'conference', + 'verbose_name_plural': 'conferences', }, ), migrations.CreateModel( name='Section', fields=[ - ('id', models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name='ID')), + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('name', models.CharField(max_length=100, verbose_name='Name')), ('slug', models.SlugField(verbose_name='Slug')), - ('start_date', models.DateField(null=True, blank=True, verbose_name='Start date')), - ('end_date', models.DateField(null=True, blank=True, verbose_name='End date')), - ('conference', models.ForeignKey(to='symposion_conference.Conference', verbose_name='Conference')), + ('start_date', models.DateField(blank=True, null=True, verbose_name='Start date')), + ('end_date', models.DateField(blank=True, null=True, verbose_name='End date')), + ('conference', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='symposion_conference.Conference', verbose_name='Conference')), ], options={ 'ordering': ['start_date'], - 'verbose_name_plural': 'sections', 'verbose_name': 'section', + 'verbose_name_plural': 'sections', }, ), ] diff --git a/symposion/proposals/migrations/0001_initial.py b/symposion/proposals/migrations/0001_initial.py index 31de5ed8..e66fc130 100644 --- a/symposion/proposals/migrations/0001_initial.py +++ b/symposion/proposals/migrations/0001_initial.py @@ -1,17 +1,21 @@ # -*- coding: utf-8 -*- +# Generated by Django 1.9.7 on 2016-09-17 03:35 from __future__ import unicode_literals -from django.db import models, migrations from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion import django.utils.timezone import symposion.proposals.models class Migration(migrations.Migration): + initial = True + dependencies = [ + ('symposion_speakers', '0001_initial'), migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('symposion_speakers', '__first__'), ('symposion_conference', '0001_initial'), ] @@ -19,8 +23,8 @@ class Migration(migrations.Migration): migrations.CreateModel( name='AdditionalSpeaker', fields=[ - ('id', models.AutoField(verbose_name='ID', primary_key=True, serialize=False, auto_created=True)), - ('status', models.IntegerField(verbose_name='Status', default=1, choices=[(1, 'Pending'), (2, 'Accepted'), (3, 'Declined')])), + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('status', models.IntegerField(choices=[(1, 'Pending'), (2, 'Accepted'), (3, 'Declined')], default=1, verbose_name='Status')), ], options={ 'verbose_name': 'Addtional speaker', @@ -30,68 +34,72 @@ class Migration(migrations.Migration): migrations.CreateModel( name='ProposalBase', fields=[ - ('id', models.AutoField(verbose_name='ID', primary_key=True, serialize=False, auto_created=True)), - ('title', models.CharField(verbose_name='Title', max_length=100)), - ('description', models.TextField(verbose_name='Brief Description', max_length=400, help_text='If your proposal is accepted this will be made public and printed in the program. Should be one paragraph, maximum 400 characters.')), - ('abstract', models.TextField(verbose_name='Detailed Abstract', help_text="Detailed outline. Will be made public if your proposal is accepted. Edit using Markdown.")), + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(max_length=100, verbose_name='Title')), + ('abstract', models.TextField(help_text="This will appear in the conference programme. Up to about 500 words. Edit using Markdown.", verbose_name='Abstract')), ('abstract_html', models.TextField(blank=True)), - ('additional_notes', models.TextField(blank=True, verbose_name='Addtional Notes', help_text="Anything else you'd like the program committee to know when making their selection: your past experience, etc. This is not made public. Edit using Markdown.")), - ('additional_notes_html', models.TextField(blank=True)), - ('submitted', models.DateTimeField(editable=False, default=django.utils.timezone.now, verbose_name='Submitted')), - ('cancelled', models.BooleanField(verbose_name='Cancelled', default=False)), - ('additional_speakers', models.ManyToManyField(blank=True, verbose_name='Addtional speakers', through='symposion_proposals.AdditionalSpeaker', to='symposion_speakers.Speaker')), + ('private_abstract', models.TextField(help_text="This will only be shown to organisers and reviewers. You should provide any details about your proposal that you don't want to be public here. Edit using Markdown.", verbose_name='Private Abstract')), + ('private_abstract_html', models.TextField(blank=True)), + ('technical_requirements', models.TextField(blank=True, help_text="Speakers will be provided with: Internet access, power, projector, audio. If you require anything in addition, please list your technical requirements here. Such as: a static IP address, A/V equipment or will be demonstrating security-related techniques on the conference network. Edit using Markdown.", verbose_name='Special Requirements')), + ('technical_requirements_html', models.TextField(blank=True)), + ('project', models.CharField(blank=True, help_text='The name of the project you will be talking about.', max_length=100)), + ('project_url', models.URLField(blank=True, help_text='If your project has a webpage, specify the URL here so the committee can find out more about your proposal.', verbose_name='Project URL')), + ('video_url', models.URLField(blank=True, help_text="You may optionally provide us with a link to a video of you speaking at another event, or of a short 'elevator pitch' of your proposed talk.", verbose_name='Video')), + ('submitted', models.DateTimeField(default=django.utils.timezone.now, editable=False, verbose_name='Submitted')), + ('cancelled', models.BooleanField(default=False, verbose_name='Cancelled')), + ('additional_speakers', models.ManyToManyField(blank=True, through='symposion_proposals.AdditionalSpeaker', to='symposion_speakers.Speaker', verbose_name='Addtional speakers')), ], ), migrations.CreateModel( name='ProposalKind', fields=[ - ('id', models.AutoField(verbose_name='ID', primary_key=True, serialize=False, auto_created=True)), - ('name', models.CharField(verbose_name='Name', max_length=100)), + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=100, verbose_name='Name')), ('slug', models.SlugField(verbose_name='Slug')), - ('section', models.ForeignKey(to='symposion_conference.Section', verbose_name='Section', related_name='proposal_kinds')), + ('section', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='proposal_kinds', to='symposion_conference.Section', verbose_name='Section')), ], ), migrations.CreateModel( name='ProposalSection', fields=[ - ('id', models.AutoField(verbose_name='ID', primary_key=True, serialize=False, auto_created=True)), - ('start', models.DateTimeField(blank=True, verbose_name='Start', null=True)), - ('end', models.DateTimeField(blank=True, verbose_name='End', null=True)), + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('start', models.DateTimeField(blank=True, null=True, verbose_name='Start')), + ('end', models.DateTimeField(blank=True, null=True, verbose_name='End')), ('closed', models.NullBooleanField(verbose_name='Closed')), ('published', models.NullBooleanField(verbose_name='Published')), - ('section', models.OneToOneField(to='symposion_conference.Section', verbose_name='Section')), + ('section', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='symposion_conference.Section', verbose_name='Section')), ], ), migrations.CreateModel( name='SupportingDocument', fields=[ - ('id', models.AutoField(verbose_name='ID', primary_key=True, serialize=False, auto_created=True)), - ('created_at', models.DateTimeField(verbose_name='Created at', default=django.utils.timezone.now)), - ('file', models.FileField(verbose_name='File', upload_to=symposion.proposals.models.uuid_filename)), - ('description', models.CharField(verbose_name='Description', max_length=140)), - ('proposal', models.ForeignKey(to='symposion_proposals.ProposalBase', verbose_name='Proposal', related_name='supporting_documents')), - ('uploaded_by', models.ForeignKey(to=settings.AUTH_USER_MODEL, verbose_name='Uploaded by')), + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(default=django.utils.timezone.now, verbose_name='Created at')), + ('file', models.FileField(upload_to=symposion.proposals.models.uuid_filename, verbose_name='File')), + ('description', models.CharField(max_length=140, verbose_name='Description')), + ('proposal', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='supporting_documents', to='symposion_proposals.ProposalBase', verbose_name='Proposal')), + ('uploaded_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Uploaded by')), ], ), migrations.AddField( model_name='proposalbase', name='kind', - field=models.ForeignKey(to='symposion_proposals.ProposalKind', verbose_name='Kind'), + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='symposion_proposals.ProposalKind', verbose_name='Kind'), ), migrations.AddField( model_name='proposalbase', name='speaker', - field=models.ForeignKey(to='symposion_speakers.Speaker', verbose_name='Speaker', related_name='proposals'), + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='proposals', to='symposion_speakers.Speaker', verbose_name='Speaker'), ), migrations.AddField( model_name='additionalspeaker', name='proposalbase', - field=models.ForeignKey(to='symposion_proposals.ProposalBase', verbose_name='Proposalbase'), + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='symposion_proposals.ProposalBase', verbose_name='Proposalbase'), ), migrations.AddField( model_name='additionalspeaker', name='speaker', - field=models.ForeignKey(to='symposion_speakers.Speaker', verbose_name='Speaker'), + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='symposion_speakers.Speaker', verbose_name='Speaker'), ), migrations.AlterUniqueTogether( name='additionalspeaker', diff --git a/symposion/reviews/migrations/0001_initial.py b/symposion/reviews/migrations/0001_initial.py index 70b87754..dbbece21 100644 --- a/symposion/reviews/migrations/0001_initial.py +++ b/symposion/reviews/migrations/0001_initial.py @@ -1,17 +1,20 @@ # -*- coding: utf-8 -*- +# Generated by Django 1.9.7 on 2016-09-17 03:35 from __future__ import unicode_literals -from django.db import models, migrations -import django.db.models.deletion -from django.conf import settings -from decimal import Decimal import datetime +from decimal import Decimal +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion class Migration(migrations.Migration): + initial = True + dependencies = [ - ('symposion_proposals', '0001_initial'), + ('symposion_proposals', '__first__'), migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] @@ -19,121 +22,122 @@ class Migration(migrations.Migration): migrations.CreateModel( name='Comment', fields=[ - ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True, serialize=False)), + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('text', models.TextField(verbose_name='Text')), ('text_html', models.TextField(blank=True)), - ('public', models.BooleanField(verbose_name='Public', default=False, choices=[(True, 'public'), (False, 'private')])), - ('commented_at', models.DateTimeField(verbose_name='Commented at', default=datetime.datetime.now)), - ('commenter', models.ForeignKey(verbose_name='Commenter', to=settings.AUTH_USER_MODEL)), - ('proposal', models.ForeignKey(to='symposion_proposals.ProposalBase', verbose_name='Proposal', related_name='comments')), + ('public', models.BooleanField(choices=[(True, 'public'), (False, 'private')], default=False, verbose_name='Public')), + ('commented_at', models.DateTimeField(default=datetime.datetime.now, verbose_name='Commented at')), + ('commenter', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Commenter')), + ('proposal', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='comments', to='symposion_proposals.ProposalBase', verbose_name='Proposal')), ], options={ - 'verbose_name_plural': 'comments', 'verbose_name': 'comment', + 'verbose_name_plural': 'comments', }, ), migrations.CreateModel( name='LatestVote', fields=[ - ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True, serialize=False)), - ('vote', models.CharField(choices=[('+1', '+1 \u2014 Good proposal and I will argue for it to be accepted.'), ('+0', '+0 \u2014 OK proposal, but I will not argue for it to be accepted.'), ('\u22120', '\u22120 \u2014 Weak proposal, but I will not argue strongly against acceptance.'), ('\u22121', '\u22121 \u2014 Serious issues and I will argue to reject this proposal.')], verbose_name='Vote', max_length=2)), - ('submitted_at', models.DateTimeField(editable=False, verbose_name='Submitted at', default=datetime.datetime.now)), - ('proposal', models.ForeignKey(to='symposion_proposals.ProposalBase', verbose_name='Proposal', related_name='votes')), - ('user', models.ForeignKey(verbose_name='User', to=settings.AUTH_USER_MODEL)), + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('vote', models.CharField(choices=[('+2', '+2 \u2014 Good proposal and I will argue for it to be accepted.'), ('+1', '+1 \u2014 OK proposal, but I will not argue for it to be accepted.'), ('-1', '\u22121 \u2014 Weak proposal, but I will not argue strongly against acceptance.'), ('-2', '\u22122 \u2014 Serious issues and I will argue to reject this proposal.'), ('0', 'Abstain - I do not want to review this proposal and I do not want to see it again.')], max_length=2, verbose_name='Vote')), + ('submitted_at', models.DateTimeField(default=datetime.datetime.now, editable=False, verbose_name='Submitted at')), + ('proposal', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='votes', to='symposion_proposals.ProposalBase', verbose_name='Proposal')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='User')), ], options={ - 'verbose_name_plural': 'latest votes', 'verbose_name': 'latest vote', + 'verbose_name_plural': 'latest votes', }, ), migrations.CreateModel( name='NotificationTemplate', fields=[ - ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True, serialize=False)), - ('label', models.CharField(verbose_name='Label', max_length=100)), - ('from_address', models.EmailField(verbose_name='From address', max_length=254)), - ('subject', models.CharField(verbose_name='Subject', max_length=100)), + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('label', models.CharField(max_length=100, verbose_name='Label')), + ('from_address', models.EmailField(max_length=254, verbose_name='From address')), + ('subject', models.CharField(max_length=100, verbose_name='Subject')), ('body', models.TextField(verbose_name='Body')), ], options={ - 'verbose_name_plural': 'notification templates', 'verbose_name': 'notification template', + 'verbose_name_plural': 'notification templates', }, ), migrations.CreateModel( name='ProposalMessage', fields=[ - ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True, serialize=False)), + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('message', models.TextField(verbose_name='Message')), ('message_html', models.TextField(blank=True)), - ('submitted_at', models.DateTimeField(editable=False, verbose_name='Submitted at', default=datetime.datetime.now)), - ('proposal', models.ForeignKey(to='symposion_proposals.ProposalBase', verbose_name='Proposal', related_name='messages')), - ('user', models.ForeignKey(verbose_name='User', to=settings.AUTH_USER_MODEL)), + ('submitted_at', models.DateTimeField(default=datetime.datetime.now, editable=False, verbose_name='Submitted at')), + ('proposal', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='messages', to='symposion_proposals.ProposalBase', verbose_name='Proposal')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='User')), ], options={ - 'verbose_name_plural': 'proposal messages', - 'verbose_name': 'proposal message', 'ordering': ['submitted_at'], + 'verbose_name': 'proposal message', + 'verbose_name_plural': 'proposal messages', }, ), migrations.CreateModel( name='ProposalResult', fields=[ - ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True, serialize=False)), - ('score', models.DecimalField(decimal_places=2, verbose_name='Score', max_digits=5, default=Decimal('0.00'))), - ('comment_count', models.PositiveIntegerField(verbose_name='Comment count', default=0)), - ('vote_count', models.PositiveIntegerField(verbose_name='Vote count', default=0)), - ('plus_one', models.PositiveIntegerField(verbose_name='Plus one', default=0)), - ('plus_zero', models.PositiveIntegerField(verbose_name='Plus zero', default=0)), - ('minus_zero', models.PositiveIntegerField(verbose_name='Minus zero', default=0)), - ('minus_one', models.PositiveIntegerField(verbose_name='Minus one', default=0)), - ('accepted', models.NullBooleanField(verbose_name='Accepted', default=None, choices=[(True, 'accepted'), (False, 'rejected'), (None, 'undecided')])), - ('status', models.CharField(choices=[('accepted', 'accepted'), ('rejected', 'rejected'), ('undecided', 'undecided'), ('standby', 'standby')], verbose_name='Status', max_length=20, default='undecided')), - ('proposal', models.OneToOneField(to='symposion_proposals.ProposalBase', verbose_name='Proposal', related_name='result')), + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('score', models.DecimalField(decimal_places=2, default=Decimal('0.00'), max_digits=5, verbose_name='Score')), + ('comment_count', models.PositiveIntegerField(default=0, verbose_name='Comment count')), + ('vote_count', models.PositiveIntegerField(default=0, verbose_name='Vote count')), + ('abstain', models.PositiveIntegerField(default=0, verbose_name='Abstain')), + ('plus_two', models.PositiveIntegerField(default=0, verbose_name='Plus two')), + ('plus_one', models.PositiveIntegerField(default=0, verbose_name='Plus one')), + ('minus_one', models.PositiveIntegerField(default=0, verbose_name='Minus one')), + ('minus_two', models.PositiveIntegerField(default=0, verbose_name='Minus two')), + ('accepted', models.NullBooleanField(choices=[(True, 'accepted'), (False, 'rejected'), (None, 'undecided')], default=None, verbose_name='Accepted')), + ('status', models.CharField(choices=[('accepted', 'accepted'), ('rejected', 'rejected'), ('undecided', 'undecided'), ('standby', 'standby')], default='undecided', max_length=20, verbose_name='Status')), + ('proposal', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='result', to='symposion_proposals.ProposalBase', verbose_name='Proposal')), ], options={ - 'verbose_name_plural': 'proposal_results', 'verbose_name': 'proposal_result', + 'verbose_name_plural': 'proposal_results', }, ), migrations.CreateModel( name='ResultNotification', fields=[ - ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True, serialize=False)), - ('timestamp', models.DateTimeField(verbose_name='Timestamp', default=datetime.datetime.now)), - ('to_address', models.EmailField(verbose_name='To address', max_length=254)), - ('from_address', models.EmailField(verbose_name='From address', max_length=254)), - ('subject', models.CharField(verbose_name='Subject', max_length=100)), + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('timestamp', models.DateTimeField(default=datetime.datetime.now, verbose_name='Timestamp')), + ('to_address', models.EmailField(max_length=254, verbose_name='To address')), + ('from_address', models.EmailField(max_length=254, verbose_name='From address')), + ('subject', models.CharField(max_length=255, verbose_name='Subject')), ('body', models.TextField(verbose_name='Body')), - ('proposal', models.ForeignKey(to='symposion_proposals.ProposalBase', verbose_name='Proposal', related_name='notifications')), - ('template', models.ForeignKey(to='symposion_reviews.NotificationTemplate', blank=True, verbose_name='Template', null=True, on_delete=django.db.models.deletion.SET_NULL)), + ('proposal', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notifications', to='symposion_proposals.ProposalBase', verbose_name='Proposal')), + ('template', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='symposion_reviews.NotificationTemplate', verbose_name='Template')), ], ), migrations.CreateModel( name='Review', fields=[ - ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True, serialize=False)), - ('vote', models.CharField(blank=True, verbose_name='Vote', max_length=2, choices=[('+1', '+1 \u2014 Good proposal and I will argue for it to be accepted.'), ('+0', '+0 \u2014 OK proposal, but I will not argue for it to be accepted.'), ('\u22120', '\u22120 \u2014 Weak proposal, but I will not argue strongly against acceptance.'), ('\u22121', '\u22121 \u2014 Serious issues and I will argue to reject this proposal.')])), - ('comment', models.TextField(verbose_name='Comment')), + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('vote', models.CharField(blank=True, choices=[('+2', '+2 \u2014 Good proposal and I will argue for it to be accepted.'), ('+1', '+1 \u2014 OK proposal, but I will not argue for it to be accepted.'), ('-1', '\u22121 \u2014 Weak proposal, but I will not argue strongly against acceptance.'), ('-2', '\u22122 \u2014 Serious issues and I will argue to reject this proposal.'), ('0', 'Abstain - I do not want to review this proposal and I do not want to see it again.')], max_length=2, verbose_name='Vote')), + ('comment', models.TextField(blank=True, verbose_name='Comment')), ('comment_html', models.TextField(blank=True)), - ('submitted_at', models.DateTimeField(editable=False, verbose_name='Submitted at', default=datetime.datetime.now)), - ('proposal', models.ForeignKey(to='symposion_proposals.ProposalBase', verbose_name='Proposal', related_name='reviews')), - ('user', models.ForeignKey(verbose_name='User', to=settings.AUTH_USER_MODEL)), + ('submitted_at', models.DateTimeField(default=datetime.datetime.now, editable=False, verbose_name='Submitted at')), + ('proposal', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='reviews', to='symposion_proposals.ProposalBase', verbose_name='Proposal')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='User')), ], options={ - 'verbose_name_plural': 'reviews', 'verbose_name': 'review', + 'verbose_name_plural': 'reviews', }, ), migrations.CreateModel( name='ReviewAssignment', fields=[ - ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True, serialize=False)), + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('origin', models.IntegerField(choices=[(0, 'auto-assigned, initial'), (1, 'opted-in'), (2, 'auto-assigned, later')], verbose_name='Origin')), - ('assigned_at', models.DateTimeField(verbose_name='Assigned at', default=datetime.datetime.now)), - ('opted_out', models.BooleanField(verbose_name='Opted out', default=False)), - ('proposal', models.ForeignKey(verbose_name='Proposal', to='symposion_proposals.ProposalBase')), - ('user', models.ForeignKey(verbose_name='User', to=settings.AUTH_USER_MODEL)), + ('assigned_at', models.DateTimeField(default=datetime.datetime.now, verbose_name='Assigned at')), + ('opted_out', models.BooleanField(default=False, verbose_name='Opted out')), + ('proposal', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='symposion_proposals.ProposalBase', verbose_name='Proposal')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='User')), ], ), migrations.AlterUniqueTogether( diff --git a/symposion/reviews/models.py b/symposion/reviews/models.py index 1a26f8dc..2f596744 100644 --- a/symposion/reviews/models.py +++ b/symposion/reviews/models.py @@ -342,7 +342,7 @@ class ResultNotification(models.Model): timestamp = models.DateTimeField(default=datetime.now, verbose_name=_("Timestamp")) to_address = models.EmailField(verbose_name=_("To address")) from_address = models.EmailField(verbose_name=_("From address")) - subject = models.CharField(max_length=100, verbose_name=_("Subject")) + subject = models.CharField(max_length=255, verbose_name=_("Subject")) body = models.TextField(verbose_name=_("Body")) def recipients(self): diff --git a/symposion/schedule/migrations/0001_initial.py b/symposion/schedule/migrations/0001_initial.py index 4e3954cf..9750d3d3 100644 --- a/symposion/schedule/migrations/0001_initial.py +++ b/symposion/schedule/migrations/0001_initial.py @@ -1,25 +1,29 @@ # -*- coding: utf-8 -*- +# Generated by Django 1.9.7 on 2016-09-17 03:35 from __future__ import unicode_literals -from django.db import models, migrations import datetime from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion class Migration(migrations.Migration): + initial = True + dependencies = [ + ('symposion_proposals', '__first__'), ('symposion_speakers', '__first__'), migrations.swappable_dependency(settings.AUTH_USER_MODEL), ('symposion_conference', '0001_initial'), - ('symposion_proposals', '0001_initial'), ] operations = [ migrations.CreateModel( name='Day', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('date', models.DateField(verbose_name='Date')), ], options={ @@ -31,16 +35,14 @@ class Migration(migrations.Migration): migrations.CreateModel( name='Presentation', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('title', models.CharField(max_length=100, verbose_name='Title')), - ('description', models.TextField(verbose_name='Description')), - ('description_html', models.TextField(blank=True)), ('abstract', models.TextField(verbose_name='Abstract')), ('abstract_html', models.TextField(blank=True)), ('cancelled', models.BooleanField(default=False, verbose_name='Cancelled')), - ('additional_speakers', models.ManyToManyField(related_name='copresentations', to='symposion_speakers.Speaker', verbose_name='Additional speakers', blank=True)), - ('proposal_base', models.OneToOneField(to='symposion_proposals.ProposalBase', related_name='presentation', verbose_name='Proposal base')), - ('section', models.ForeignKey(to='symposion_conference.Section', related_name='presentations', verbose_name='Section')), + ('additional_speakers', models.ManyToManyField(blank=True, related_name='copresentations', to='symposion_speakers.Speaker', verbose_name='Additional speakers')), + ('proposal_base', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='presentation', to='symposion_proposals.ProposalBase', verbose_name='Proposal base')), + ('section', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='presentations', to='symposion_conference.Section', verbose_name='Section')), ], options={ 'ordering': ['slot'], @@ -51,7 +53,7 @@ class Migration(migrations.Migration): migrations.CreateModel( name='Room', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('name', models.CharField(max_length=65, verbose_name='Name')), ('order', models.PositiveIntegerField(verbose_name='Order')), ], @@ -63,10 +65,10 @@ class Migration(migrations.Migration): migrations.CreateModel( name='Schedule', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('published', models.BooleanField(default=True, verbose_name='Published')), ('hidden', models.BooleanField(default=False, verbose_name='Hide schedule from overall conference view')), - ('section', models.OneToOneField(to='symposion_conference.Section', verbose_name='Section')), + ('section', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='symposion_conference.Section', verbose_name='Section')), ], options={ 'ordering': ['section'], @@ -77,8 +79,8 @@ class Migration(migrations.Migration): migrations.CreateModel( name='Session', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('day', models.ForeignKey(to='symposion_schedule.Day', related_name='sessions', verbose_name='Day')), + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('day', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sessions', to='symposion_schedule.Day', verbose_name='Day')), ], options={ 'verbose_name': 'Session', @@ -88,12 +90,12 @@ class Migration(migrations.Migration): migrations.CreateModel( name='SessionRole', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('role', models.IntegerField(verbose_name='Role', choices=[(1, 'Session Chair'), (2, 'Session Runner')])), + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('role', models.IntegerField(choices=[(1, 'Session Chair'), (2, 'Session Runner')], verbose_name='Role')), ('status', models.NullBooleanField(verbose_name='Status')), ('submitted', models.DateTimeField(default=datetime.datetime.now)), - ('session', models.ForeignKey(to='symposion_schedule.Session', verbose_name='Session')), - ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL, verbose_name='User')), + ('session', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='symposion_schedule.Session', verbose_name='Session')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='User')), ], options={ 'verbose_name': 'Session role', @@ -103,12 +105,13 @@ class Migration(migrations.Migration): migrations.CreateModel( name='Slot', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(editable=False, max_length=100)), ('start', models.TimeField(verbose_name='Start')), ('end', models.TimeField(verbose_name='End')), - ('content_override', models.TextField(verbose_name='Content override', blank=True)), + ('content_override', models.TextField(blank=True, verbose_name='Content override')), ('content_override_html', models.TextField(blank=True)), - ('day', models.ForeignKey(to='symposion_schedule.Day', verbose_name='Day')), + ('day', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='symposion_schedule.Day', verbose_name='Day')), ], options={ 'ordering': ['day', 'start', 'end'], @@ -119,9 +122,9 @@ class Migration(migrations.Migration): migrations.CreateModel( name='SlotKind', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('label', models.CharField(max_length=50, verbose_name='Label')), - ('schedule', models.ForeignKey(to='symposion_schedule.Schedule', verbose_name='schedule')), + ('schedule', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='symposion_schedule.Schedule', verbose_name='schedule')), ], options={ 'verbose_name': 'Slot kind', @@ -131,9 +134,9 @@ class Migration(migrations.Migration): migrations.CreateModel( name='SlotRoom', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('room', models.ForeignKey(to='symposion_schedule.Room', verbose_name='Room')), - ('slot', models.ForeignKey(to='symposion_schedule.Slot', verbose_name='Slot')), + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('room', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='symposion_schedule.Room', verbose_name='Room')), + ('slot', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='symposion_schedule.Slot', verbose_name='Slot')), ], options={ 'ordering': ['slot', 'room__order'], @@ -144,32 +147,32 @@ class Migration(migrations.Migration): migrations.AddField( model_name='slot', name='kind', - field=models.ForeignKey(to='symposion_schedule.SlotKind', verbose_name='Kind'), + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='symposion_schedule.SlotKind', verbose_name='Kind'), ), migrations.AddField( model_name='session', name='slots', - field=models.ManyToManyField(related_name='sessions', verbose_name='Slots', to='symposion_schedule.Slot'), + field=models.ManyToManyField(related_name='sessions', to='symposion_schedule.Slot', verbose_name='Slots'), ), migrations.AddField( model_name='room', name='schedule', - field=models.ForeignKey(to='symposion_schedule.Schedule', verbose_name='Schedule'), + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='symposion_schedule.Schedule', verbose_name='Schedule'), ), migrations.AddField( model_name='presentation', name='slot', - field=models.OneToOneField(to='symposion_schedule.Slot', related_name='content_ptr', blank=True, null=True, verbose_name='Slot'), + field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='content_ptr', to='symposion_schedule.Slot', verbose_name='Slot'), ), migrations.AddField( model_name='presentation', name='speaker', - field=models.ForeignKey(to='symposion_speakers.Speaker', related_name='presentations', verbose_name='Speaker'), + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='presentations', to='symposion_speakers.Speaker', verbose_name='Speaker'), ), migrations.AddField( model_name='day', name='schedule', - field=models.ForeignKey(to='symposion_schedule.Schedule', verbose_name='Schedule'), + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='symposion_schedule.Schedule', verbose_name='Schedule'), ), migrations.AlterUniqueTogether( name='slotroom', diff --git a/symposion/schedule/migrations/0002_auto_20160903_0146.py b/symposion/schedule/migrations/0002_auto_20160903_0146.py deleted file mode 100644 index a1ca17e7..00000000 --- a/symposion/schedule/migrations/0002_auto_20160903_0146.py +++ /dev/null @@ -1,23 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.9.2 on 2016-09-03 01:46 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('symposion_schedule', '0001_initial'), - ] - - operations = [ - migrations.RemoveField( - model_name='presentation', - name='description', - ), - migrations.RemoveField( - model_name='presentation', - name='description_html', - ), - ] diff --git a/symposion/speakers/migrations/0001_initial.py b/symposion/speakers/migrations/0001_initial.py index c3b6f2b1..aacf963f 100644 --- a/symposion/speakers/migrations/0001_initial.py +++ b/symposion/speakers/migrations/0001_initial.py @@ -1,13 +1,17 @@ # -*- coding: utf-8 -*- +# Generated by Django 1.9.7 on 2016-09-17 03:35 from __future__ import unicode_literals -from django.db import models, migrations import datetime from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion class Migration(migrations.Migration): + initial = True + dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] @@ -16,21 +20,31 @@ class Migration(migrations.Migration): migrations.CreateModel( name='Speaker', fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, verbose_name='ID', serialize=False)), - ('name', models.CharField(verbose_name='Name', help_text='As you would like it to appear in the conference program.', max_length=100)), - ('biography', models.TextField(verbose_name='Biography', blank=True, help_text="A little bit about you. Edit using Markdown.")), + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(help_text='As you would like it to appear in the conference programme.', max_length=100, verbose_name='Name')), + ('biography', models.TextField(blank=True, help_text="This will appear on the conference website and in the programme. Please write in the third person, eg 'Alice is a Moblin hacker...', 150-200 words. Edit using Markdown.", verbose_name='Biography')), ('biography_html', models.TextField(blank=True)), - ('photo', models.ImageField(verbose_name='Photo', upload_to='speaker_photos', blank=True)), + ('experience', models.TextField(blank=True, help_text="Have you had any experience presenting elsewhere? If so, we'd like to know. Anything you put here will only be seen by the organisers and reviewers; use it to convince them why they should accept your proposal. Edit using Markdown.", verbose_name='Speaking experience')), + ('experience_html', models.TextField(blank=True)), + ('photo', models.ImageField(blank=True, upload_to='speaker_photos', verbose_name='Photo')), + ('telephone', models.CharField(help_text="The conference team will need this to contact you during the conference week. If you don't have one, or do not wish to provide it, then enter NONE in this field.", max_length=15)), + ('homepage', models.URLField(blank=True, help_text='Your home page, if you have one')), + ('twitter_username', models.CharField(blank=True, help_text='Your Twitter account', max_length=15)), + ('accessibility', models.TextField(blank=True, help_text="Please describe any special accessibility requirements that you may have. Edit using Markdown.", verbose_name='Accessibility requirements')), + ('accessibility_html', models.TextField(blank=True)), + ('travel_assistance', models.BooleanField(default=False, help_text='Check this box if you require assistance to travel to Hobart to present your proposed sessions.', verbose_name='Travel assistance required')), + ('accommodation_assistance', models.BooleanField(default=False, help_text='Check this box if you require us to provide you with student-style accommodation in order to present your proposed sessions.', verbose_name='Accommodation assistance required')), + ('agreement', models.BooleanField(default=False, help_text='I agree to the terms and conditions of attendance, and I have read, understood, and agree to act according to the standards set forth in our Code of Conduct ')), ('annotation', models.TextField(verbose_name='Annotation')), - ('invite_email', models.CharField(verbose_name='Invite_email', unique=True, db_index=True, max_length=200, null=True)), - ('invite_token', models.CharField(verbose_name='Invite token', db_index=True, max_length=40)), - ('created', models.DateTimeField(editable=False, verbose_name='Created', default=datetime.datetime.now)), - ('user', models.OneToOneField(null=True, related_name='speaker_profile', verbose_name='User', to=settings.AUTH_USER_MODEL)), + ('invite_email', models.CharField(db_index=True, max_length=200, null=True, unique=True, verbose_name='Invite_email')), + ('invite_token', models.CharField(db_index=True, max_length=40, verbose_name='Invite token')), + ('created', models.DateTimeField(default=datetime.datetime.now, editable=False, verbose_name='Created')), + ('user', models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='speaker_profile', to=settings.AUTH_USER_MODEL, verbose_name='User')), ], options={ + 'ordering': ['name'], 'verbose_name': 'Speaker', 'verbose_name_plural': 'Speakers', - 'ordering': ['name'], }, ), ] diff --git a/symposion/speakers/migrations/0002_speaker_twitter_username.py b/symposion/speakers/migrations/0002_speaker_twitter_username.py deleted file mode 100644 index 9208697b..00000000 --- a/symposion/speakers/migrations/0002_speaker_twitter_username.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.9.4 on 2016-03-15 03:16 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('symposion_speakers', '0001_initial'), - ] - - operations = [ - migrations.AddField( - model_name='speaker', - name='twitter_username', - field=models.CharField(blank=True, help_text='Your Twitter account', max_length=15), - ), - ] diff --git a/symposion/sponsorship/migrations/0001_initial.py b/symposion/sponsorship/migrations/0001_initial.py index bf9c3aca..29a8cc05 100644 --- a/symposion/sponsorship/migrations/0001_initial.py +++ b/symposion/sponsorship/migrations/0001_initial.py @@ -1,13 +1,17 @@ # -*- coding: utf-8 -*- +# Generated by Django 1.9.7 on 2016-09-17 03:35 from __future__ import unicode_literals -from django.db import models, migrations -from django.conf import settings import datetime +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion class Migration(migrations.Migration): + initial = True + dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), ('symposion_conference', '0001_initial'), @@ -17,99 +21,99 @@ class Migration(migrations.Migration): migrations.CreateModel( name='Benefit', fields=[ - ('id', models.AutoField(serialize=False, auto_created=True, primary_key=True, verbose_name='ID')), - ('name', models.CharField(verbose_name='Name', max_length=100)), + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=100, verbose_name='Name')), ('description', models.TextField(blank=True, verbose_name='Description')), - ('type', models.CharField(default='simple', choices=[('text', 'Text'), ('file', 'File'), ('richtext', 'Rich Text'), ('weblogo', 'Web Logo'), ('simple', 'Simple'), ('option', 'Option')], verbose_name='Type', max_length=10)), - ('content_type', models.CharField(default='simple', choices=[('simple', 'Simple'), ('listing_text_af', 'Listing Text (Afrikaans)'), ('listing_text_ar', 'Listing Text (Arabic)'), ('listing_text_ast', 'Listing Text (Asturian)'), ('listing_text_az', 'Listing Text (Azerbaijani)'), ('listing_text_bg', 'Listing Text (Bulgarian)'), ('listing_text_be', 'Listing Text (Belarusian)'), ('listing_text_bn', 'Listing Text (Bengali)'), ('listing_text_br', 'Listing Text (Breton)'), ('listing_text_bs', 'Listing Text (Bosnian)'), ('listing_text_ca', 'Listing Text (Catalan)'), ('listing_text_cs', 'Listing Text (Czech)'), ('listing_text_cy', 'Listing Text (Welsh)'), ('listing_text_da', 'Listing Text (Danish)'), ('listing_text_de', 'Listing Text (German)'), ('listing_text_el', 'Listing Text (Greek)'), ('listing_text_en', 'Listing Text (English)'), ('listing_text_en-au', 'Listing Text (Australian English)'), ('listing_text_en-gb', 'Listing Text (British English)'), ('listing_text_eo', 'Listing Text (Esperanto)'), ('listing_text_es', 'Listing Text (Spanish)'), ('listing_text_es-ar', 'Listing Text (Argentinian Spanish)'), ('listing_text_es-mx', 'Listing Text (Mexican Spanish)'), ('listing_text_es-ni', 'Listing Text (Nicaraguan Spanish)'), ('listing_text_es-ve', 'Listing Text (Venezuelan Spanish)'), ('listing_text_et', 'Listing Text (Estonian)'), ('listing_text_eu', 'Listing Text (Basque)'), ('listing_text_fa', 'Listing Text (Persian)'), ('listing_text_fi', 'Listing Text (Finnish)'), ('listing_text_fr', 'Listing Text (French)'), ('listing_text_fy', 'Listing Text (Frisian)'), ('listing_text_ga', 'Listing Text (Irish)'), ('listing_text_gl', 'Listing Text (Galician)'), ('listing_text_he', 'Listing Text (Hebrew)'), ('listing_text_hi', 'Listing Text (Hindi)'), ('listing_text_hr', 'Listing Text (Croatian)'), ('listing_text_hu', 'Listing Text (Hungarian)'), ('listing_text_ia', 'Listing Text (Interlingua)'), ('listing_text_id', 'Listing Text (Indonesian)'), ('listing_text_io', 'Listing Text (Ido)'), ('listing_text_is', 'Listing Text (Icelandic)'), ('listing_text_it', 'Listing Text (Italian)'), ('listing_text_ja', 'Listing Text (Japanese)'), ('listing_text_ka', 'Listing Text (Georgian)'), ('listing_text_kk', 'Listing Text (Kazakh)'), ('listing_text_km', 'Listing Text (Khmer)'), ('listing_text_kn', 'Listing Text (Kannada)'), ('listing_text_ko', 'Listing Text (Korean)'), ('listing_text_lb', 'Listing Text (Luxembourgish)'), ('listing_text_lt', 'Listing Text (Lithuanian)'), ('listing_text_lv', 'Listing Text (Latvian)'), ('listing_text_mk', 'Listing Text (Macedonian)'), ('listing_text_ml', 'Listing Text (Malayalam)'), ('listing_text_mn', 'Listing Text (Mongolian)'), ('listing_text_mr', 'Listing Text (Marathi)'), ('listing_text_my', 'Listing Text (Burmese)'), ('listing_text_nb', 'Listing Text (Norwegian Bokmal)'), ('listing_text_ne', 'Listing Text (Nepali)'), ('listing_text_nl', 'Listing Text (Dutch)'), ('listing_text_nn', 'Listing Text (Norwegian Nynorsk)'), ('listing_text_os', 'Listing Text (Ossetic)'), ('listing_text_pa', 'Listing Text (Punjabi)'), ('listing_text_pl', 'Listing Text (Polish)'), ('listing_text_pt', 'Listing Text (Portuguese)'), ('listing_text_pt-br', 'Listing Text (Brazilian Portuguese)'), ('listing_text_ro', 'Listing Text (Romanian)'), ('listing_text_ru', 'Listing Text (Russian)'), ('listing_text_sk', 'Listing Text (Slovak)'), ('listing_text_sl', 'Listing Text (Slovenian)'), ('listing_text_sq', 'Listing Text (Albanian)'), ('listing_text_sr', 'Listing Text (Serbian)'), ('listing_text_sr-latn', 'Listing Text (Serbian Latin)'), ('listing_text_sv', 'Listing Text (Swedish)'), ('listing_text_sw', 'Listing Text (Swahili)'), ('listing_text_ta', 'Listing Text (Tamil)'), ('listing_text_te', 'Listing Text (Telugu)'), ('listing_text_th', 'Listing Text (Thai)'), ('listing_text_tr', 'Listing Text (Turkish)'), ('listing_text_tt', 'Listing Text (Tatar)'), ('listing_text_udm', 'Listing Text (Udmurt)'), ('listing_text_uk', 'Listing Text (Ukrainian)'), ('listing_text_ur', 'Listing Text (Urdu)'), ('listing_text_vi', 'Listing Text (Vietnamese)'), ('listing_text_zh-cn', 'Listing Text (Simplified Chinese)'), ('listing_text_zh-hans', 'Listing Text (Simplified Chinese)'), ('listing_text_zh-hant', 'Listing Text (Traditional Chinese)'), ('listing_text_zh-tw', 'Listing Text (Traditional Chinese)')], verbose_name='content type', max_length=20)), + ('type', models.CharField(choices=[('text', 'Text'), ('file', 'File'), ('richtext', 'Rich Text'), ('weblogo', 'Web Logo'), ('simple', 'Simple'), ('option', 'Option')], default='simple', max_length=10, verbose_name='Type')), + ('content_type', models.CharField(choices=[('simple', 'Simple'), ('listing_text_af', 'Listing Text (Afrikaans)'), ('listing_text_ar', 'Listing Text (Arabic)'), ('listing_text_ast', 'Listing Text (Asturian)'), ('listing_text_az', 'Listing Text (Azerbaijani)'), ('listing_text_bg', 'Listing Text (Bulgarian)'), ('listing_text_be', 'Listing Text (Belarusian)'), ('listing_text_bn', 'Listing Text (Bengali)'), ('listing_text_br', 'Listing Text (Breton)'), ('listing_text_bs', 'Listing Text (Bosnian)'), ('listing_text_ca', 'Listing Text (Catalan)'), ('listing_text_cs', 'Listing Text (Czech)'), ('listing_text_cy', 'Listing Text (Welsh)'), ('listing_text_da', 'Listing Text (Danish)'), ('listing_text_de', 'Listing Text (German)'), ('listing_text_el', 'Listing Text (Greek)'), ('listing_text_en', 'Listing Text (English)'), ('listing_text_en-au', 'Listing Text (Australian English)'), ('listing_text_en-gb', 'Listing Text (British English)'), ('listing_text_eo', 'Listing Text (Esperanto)'), ('listing_text_es', 'Listing Text (Spanish)'), ('listing_text_es-ar', 'Listing Text (Argentinian Spanish)'), ('listing_text_es-co', 'Listing Text (Colombian Spanish)'), ('listing_text_es-mx', 'Listing Text (Mexican Spanish)'), ('listing_text_es-ni', 'Listing Text (Nicaraguan Spanish)'), ('listing_text_es-ve', 'Listing Text (Venezuelan Spanish)'), ('listing_text_et', 'Listing Text (Estonian)'), ('listing_text_eu', 'Listing Text (Basque)'), ('listing_text_fa', 'Listing Text (Persian)'), ('listing_text_fi', 'Listing Text (Finnish)'), ('listing_text_fr', 'Listing Text (French)'), ('listing_text_fy', 'Listing Text (Frisian)'), ('listing_text_ga', 'Listing Text (Irish)'), ('listing_text_gd', 'Listing Text (Scottish Gaelic)'), ('listing_text_gl', 'Listing Text (Galician)'), ('listing_text_he', 'Listing Text (Hebrew)'), ('listing_text_hi', 'Listing Text (Hindi)'), ('listing_text_hr', 'Listing Text (Croatian)'), ('listing_text_hu', 'Listing Text (Hungarian)'), ('listing_text_ia', 'Listing Text (Interlingua)'), ('listing_text_id', 'Listing Text (Indonesian)'), ('listing_text_io', 'Listing Text (Ido)'), ('listing_text_is', 'Listing Text (Icelandic)'), ('listing_text_it', 'Listing Text (Italian)'), ('listing_text_ja', 'Listing Text (Japanese)'), ('listing_text_ka', 'Listing Text (Georgian)'), ('listing_text_kk', 'Listing Text (Kazakh)'), ('listing_text_km', 'Listing Text (Khmer)'), ('listing_text_kn', 'Listing Text (Kannada)'), ('listing_text_ko', 'Listing Text (Korean)'), ('listing_text_lb', 'Listing Text (Luxembourgish)'), ('listing_text_lt', 'Listing Text (Lithuanian)'), ('listing_text_lv', 'Listing Text (Latvian)'), ('listing_text_mk', 'Listing Text (Macedonian)'), ('listing_text_ml', 'Listing Text (Malayalam)'), ('listing_text_mn', 'Listing Text (Mongolian)'), ('listing_text_mr', 'Listing Text (Marathi)'), ('listing_text_my', 'Listing Text (Burmese)'), ('listing_text_nb', 'Listing Text (Norwegian Bokmal)'), ('listing_text_ne', 'Listing Text (Nepali)'), ('listing_text_nl', 'Listing Text (Dutch)'), ('listing_text_nn', 'Listing Text (Norwegian Nynorsk)'), ('listing_text_os', 'Listing Text (Ossetic)'), ('listing_text_pa', 'Listing Text (Punjabi)'), ('listing_text_pl', 'Listing Text (Polish)'), ('listing_text_pt', 'Listing Text (Portuguese)'), ('listing_text_pt-br', 'Listing Text (Brazilian Portuguese)'), ('listing_text_ro', 'Listing Text (Romanian)'), ('listing_text_ru', 'Listing Text (Russian)'), ('listing_text_sk', 'Listing Text (Slovak)'), ('listing_text_sl', 'Listing Text (Slovenian)'), ('listing_text_sq', 'Listing Text (Albanian)'), ('listing_text_sr', 'Listing Text (Serbian)'), ('listing_text_sr-latn', 'Listing Text (Serbian Latin)'), ('listing_text_sv', 'Listing Text (Swedish)'), ('listing_text_sw', 'Listing Text (Swahili)'), ('listing_text_ta', 'Listing Text (Tamil)'), ('listing_text_te', 'Listing Text (Telugu)'), ('listing_text_th', 'Listing Text (Thai)'), ('listing_text_tr', 'Listing Text (Turkish)'), ('listing_text_tt', 'Listing Text (Tatar)'), ('listing_text_udm', 'Listing Text (Udmurt)'), ('listing_text_uk', 'Listing Text (Ukrainian)'), ('listing_text_ur', 'Listing Text (Urdu)'), ('listing_text_vi', 'Listing Text (Vietnamese)'), ('listing_text_zh-hans', 'Listing Text (Simplified Chinese)'), ('listing_text_zh-hant', 'Listing Text (Traditional Chinese)')], default='simple', max_length=20, verbose_name='content type')), ], ), migrations.CreateModel( name='BenefitLevel', fields=[ - ('id', models.AutoField(serialize=False, auto_created=True, primary_key=True, verbose_name='ID')), - ('max_words', models.PositiveIntegerField(blank=True, verbose_name='Max words', null=True)), - ('other_limits', models.CharField(blank=True, verbose_name='Other limits', max_length=200)), - ('benefit', models.ForeignKey(to='symposion_sponsorship.Benefit', related_name='benefit_levels', verbose_name='Benefit')), + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('max_words', models.PositiveIntegerField(blank=True, null=True, verbose_name='Max words')), + ('other_limits', models.CharField(blank=True, max_length=200, verbose_name='Other limits')), + ('benefit', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='benefit_levels', to='symposion_sponsorship.Benefit', verbose_name='Benefit')), ], options={ - 'verbose_name_plural': 'Benefit levels', 'ordering': ['level'], 'verbose_name': 'Benefit level', + 'verbose_name_plural': 'Benefit levels', }, ), migrations.CreateModel( name='Sponsor', fields=[ - ('id', models.AutoField(serialize=False, auto_created=True, primary_key=True, verbose_name='ID')), - ('name', models.CharField(verbose_name='Sponsor Name', max_length=100)), + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=100, verbose_name='Sponsor Name')), ('display_url', models.URLField(blank=True, verbose_name='display URL')), ('external_url', models.URLField(verbose_name='External URL')), ('annotation', models.TextField(blank=True, verbose_name='Annotation')), - ('contact_name', models.CharField(verbose_name='Contact Name', max_length=100)), - ('contact_email', models.EmailField(verbose_name='Contact Email', max_length=254)), + ('contact_name', models.CharField(max_length=100, verbose_name='Contact Name')), + ('contact_email', models.EmailField(max_length=254, verbose_name='Contact Email')), ('added', models.DateTimeField(default=datetime.datetime.now, verbose_name='added')), ('active', models.BooleanField(default=False, verbose_name='active')), - ('web_logo_benefit', models.NullBooleanField(verbose_name='Web logo benefit', help_text='Web logo benefit is complete')), - ('print_logo_benefit', models.NullBooleanField(verbose_name='Print logo benefit', help_text='Print logo benefit is complete')), - ('print_description_benefit', models.NullBooleanField(verbose_name='Print description benefit', help_text='Print description benefit is complete')), - ('company_description_benefit', models.NullBooleanField(verbose_name='Company description benefit', help_text='Company description benefit is complete')), - ('applicant', models.ForeignKey(to=settings.AUTH_USER_MODEL, null=True, related_name='sponsorships', verbose_name='Applicant')), + ('web_logo_benefit', models.NullBooleanField(help_text='Web logo benefit is complete', verbose_name='Web logo benefit')), + ('print_logo_benefit', models.NullBooleanField(help_text='Print logo benefit is complete', verbose_name='Print logo benefit')), + ('print_description_benefit', models.NullBooleanField(help_text='Print description benefit is complete', verbose_name='Print description benefit')), + ('company_description_benefit', models.NullBooleanField(help_text='Company description benefit is complete', verbose_name='Company description benefit')), + ('applicant', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='sponsorships', to=settings.AUTH_USER_MODEL, verbose_name='Applicant')), ], options={ - 'verbose_name_plural': 'Sponsors', 'ordering': ['name'], 'verbose_name': 'Sponsor', + 'verbose_name_plural': 'Sponsors', }, ), migrations.CreateModel( name='SponsorBenefit', fields=[ - ('id', models.AutoField(serialize=False, auto_created=True, primary_key=True, verbose_name='ID')), + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('active', models.BooleanField(default=True, verbose_name='Active')), - ('max_words', models.PositiveIntegerField(blank=True, verbose_name='Max words', null=True)), - ('other_limits', models.CharField(blank=True, verbose_name='Other limits', max_length=200)), + ('max_words', models.PositiveIntegerField(blank=True, null=True, verbose_name='Max words')), + ('other_limits', models.CharField(blank=True, max_length=200, verbose_name='Other limits')), ('text', models.TextField(blank=True, verbose_name='Text')), - ('upload', models.FileField(blank=True, verbose_name='File', upload_to='sponsor_files')), - ('is_complete', models.NullBooleanField(verbose_name='Complete?', help_text='True - benefit complete; False - benefit incomplete; Null - n/a')), - ('benefit', models.ForeignKey(to='symposion_sponsorship.Benefit', related_name='sponsor_benefits', verbose_name='Benefit')), - ('sponsor', models.ForeignKey(to='symposion_sponsorship.Sponsor', related_name='sponsor_benefits', verbose_name='Sponsor')), + ('upload', models.FileField(blank=True, upload_to='sponsor_files', verbose_name='File')), + ('is_complete', models.NullBooleanField(help_text='True - benefit complete; False - benefit incomplete; Null - n/a', verbose_name='Complete?')), + ('benefit', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sponsor_benefits', to='symposion_sponsorship.Benefit', verbose_name='Benefit')), + ('sponsor', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sponsor_benefits', to='symposion_sponsorship.Sponsor', verbose_name='Sponsor')), ], options={ - 'verbose_name_plural': 'Sponsor benefits', 'ordering': ['-active'], 'verbose_name': 'Sponsor benefit', + 'verbose_name_plural': 'Sponsor benefits', }, ), migrations.CreateModel( name='SponsorLevel', fields=[ - ('id', models.AutoField(serialize=False, auto_created=True, primary_key=True, verbose_name='ID')), - ('name', models.CharField(verbose_name='Name', max_length=100)), + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=100, verbose_name='Name')), ('order', models.IntegerField(default=0, verbose_name='Order')), ('cost', models.PositiveIntegerField(verbose_name='Cost')), - ('description', models.TextField(blank=True, verbose_name='Description', help_text='This is private.')), - ('conference', models.ForeignKey(to='symposion_conference.Conference', verbose_name='Conference')), + ('description', models.TextField(blank=True, help_text='This is private.', verbose_name='Description')), + ('conference', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='symposion_conference.Conference', verbose_name='Conference')), ], options={ - 'verbose_name_plural': 'Sponsor levels', 'ordering': ['conference', 'order'], 'verbose_name': 'Sponsor level', + 'verbose_name_plural': 'Sponsor levels', }, ), migrations.AddField( model_name='sponsor', name='level', - field=models.ForeignKey(to='symposion_sponsorship.SponsorLevel', verbose_name='level'), + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='symposion_sponsorship.SponsorLevel', verbose_name='level'), ), migrations.AddField( model_name='sponsor', name='sponsor_logo', - field=models.ForeignKey(blank=True, to='symposion_sponsorship.SponsorBenefit', null=True, related_name='+', verbose_name='Sponsor logo', editable=False), + field=models.ForeignKey(blank=True, editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='symposion_sponsorship.SponsorBenefit', verbose_name='Sponsor logo'), ), migrations.AddField( model_name='benefitlevel', name='level', - field=models.ForeignKey(to='symposion_sponsorship.SponsorLevel', related_name='benefit_levels', verbose_name='Level'), + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='benefit_levels', to='symposion_sponsorship.SponsorLevel', verbose_name='Level'), ), ] From d54d47487eccaa14a6e19ead404d3da1990c5951 Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer Date: Sun, 21 Aug 2016 15:28:22 +1000 Subject: [PATCH 707/751] send_mail is no longer hardwired to point at symposion/emails (who does that?!) --- symposion/utils/mail.py | 38 +++++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/symposion/utils/mail.py b/symposion/utils/mail.py index e9759d09..858e637e 100644 --- a/symposion/utils/mail.py +++ b/symposion/utils/mail.py @@ -1,3 +1,5 @@ +import os + from django.conf import settings from django.core.mail import EmailMultiAlternatives from django.template.loader import render_to_string @@ -6,7 +8,30 @@ from django.utils.html import strip_tags from django.contrib.sites.models import Site -def send_email(to, kind, **kwargs): +def sender(template_prefix): + ''' Creates a function called `send_email` ''' + + def send_email(to, kind, **kwargs): + ''' Sends an e-mail to the given address. + + to: The address + kind: the ID for an e-mail kind; it should point to a subdirectory of + %(template_prefix)s containing subject.txt and message.html, which + are django templates for the subject and HTML message respectively. + + context: a context for rendering the e-mail. + + ''' % {"template_prefix": template_prefix} + + return __send_email__(template_prefix, to, kind, **kwargs) + + return send_email + + +send_email = sender("symposion/emails") + + +def __send_email__(template_prefix, to, kind, **kwargs): current_site = Site.objects.get_current() @@ -15,16 +40,23 @@ def send_email(to, kind, **kwargs): "STATIC_URL": settings.STATIC_URL, } ctx.update(kwargs.get("context", {})) + subject_template = os.path.join(template_prefix, "%s/subject.txt" % kind) + message_template = os.path.join(template_prefix, "%s/message.html" % kind) subject = "[%s] %s" % ( current_site.name, - render_to_string("symposion/emails/%s/subject.txt" % kind, ctx).strip() + render_to_string(subject_template, ctx).strip() ) - message_html = render_to_string("symposion/emails/%s/message.html" % kind, ctx) + message_html = render_to_string(message_template, ctx) message_plaintext = strip_tags(message_html) from_email = settings.DEFAULT_FROM_EMAIL + try: + bcc_email = settings.ENVELOPE_BCC_LIST + except AttributeError: + bcc_email = None + email = EmailMultiAlternatives(subject, message_plaintext, from_email, to) email.attach_alternative(message_html, "text/html") email.send() From 96683b6d7dba9187845331db3c88c1e0eb032f7a Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer Date: Sun, 21 Aug 2016 15:31:09 +1000 Subject: [PATCH 708/751] Refactors to be a bit less obtuse --- symposion/utils/mail.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/symposion/utils/mail.py b/symposion/utils/mail.py index 858e637e..b301c4bb 100644 --- a/symposion/utils/mail.py +++ b/symposion/utils/mail.py @@ -8,27 +8,28 @@ from django.utils.html import strip_tags from django.contrib.sites.models import Site -def sender(template_prefix): - ''' Creates a function called `send_email` ''' +class Sender(object): + ''' Class for sending e-mails under a templete prefix. ''' - def send_email(to, kind, **kwargs): + def __init__(self, template_prefix): + self.template_prefix = template_prefix + + def send_email(self, to, kind, **kwargs): ''' Sends an e-mail to the given address. to: The address kind: the ID for an e-mail kind; it should point to a subdirectory of - %(template_prefix)s containing subject.txt and message.html, which + self.template_prefix containing subject.txt and message.html, which are django templates for the subject and HTML message respectively. context: a context for rendering the e-mail. - ''' % {"template_prefix": template_prefix} + ''' - return __send_email__(template_prefix, to, kind, **kwargs) - - return send_email + return __send_email__(self.template_prefix, to, kind, **kwargs) -send_email = sender("symposion/emails") +send_email = Sender("symposion/emails").send_email def __send_email__(template_prefix, to, kind, **kwargs): From 3b4a51e6d43edc4982905eee66d71624b3895e47 Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer Date: Sun, 18 Sep 2016 10:45:42 +1000 Subject: [PATCH 709/751] =?UTF-8?q?Adds=20=E2=80=9Cunpublish=E2=80=9D=20op?= =?UTF-8?q?tion=20to=20presentations=20(to=20temporarily=20hide=20from=20t?= =?UTF-8?q?he=20schedule=20and=20from=20view=20by=20non-staff)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../migrations/0002_presentation_unpublish.py | 20 +++++++++++++++++++ symposion/schedule/models.py | 4 ++++ 2 files changed, 24 insertions(+) create mode 100644 symposion/schedule/migrations/0002_presentation_unpublish.py diff --git a/symposion/schedule/migrations/0002_presentation_unpublish.py b/symposion/schedule/migrations/0002_presentation_unpublish.py new file mode 100644 index 00000000..8e2a7cab --- /dev/null +++ b/symposion/schedule/migrations/0002_presentation_unpublish.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.7 on 2016-09-18 00:43 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('symposion_schedule', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='presentation', + name='unpublish', + field=models.BooleanField(default=False, verbose_name='Do not publish'), + ), + ] diff --git a/symposion/schedule/models.py b/symposion/schedule/models.py index c21a4367..cfa36662 100644 --- a/symposion/schedule/models.py +++ b/symposion/schedule/models.py @@ -190,6 +190,10 @@ class Presentation(models.Model): speaker = models.ForeignKey(Speaker, related_name="presentations", verbose_name=_("Speaker")) additional_speakers = models.ManyToManyField(Speaker, related_name="copresentations", blank=True, verbose_name=_("Additional speakers")) + unpublish = models.BooleanField( + default=False, + verbose_name=_("Do not publish"), + ) cancelled = models.BooleanField(default=False, verbose_name=_("Cancelled")) proposal_base = models.OneToOneField(ProposalBase, related_name="presentation", verbose_name=_("Proposal base")) section = models.ForeignKey(Section, related_name="presentations", verbose_name=_("Section")) From 970e002157dd5ef16786a10fad9b0c7f3d503ade Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer Date: Sun, 18 Sep 2016 11:05:01 +1000 Subject: [PATCH 710/751] Do not show a presentation if it is unpublished --- symposion/schedule/views.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/symposion/schedule/views.py b/symposion/schedule/views.py index 75472fc5..4e950959 100644 --- a/symposion/schedule/views.py +++ b/symposion/schedule/views.py @@ -174,12 +174,18 @@ def schedule_slot_edit(request, slug, slot_pk): def schedule_presentation_detail(request, pk): presentation = get_object_or_404(Presentation, pk=pk) + if presentation.slot: + # 1) Schedule from presentation's slot schedule = presentation.slot.day.schedule - if not schedule.published and not request.user.is_staff: - raise Http404() else: - schedule = None + # 2) Fall back to the schedule for this proposal + schedule = presentation.proposal.kind.section.schedule + + if not request.user.is_staff: + # 3) Is proposal unpublished? + if presentation.unpublish or not (schedule and schedule.published): + raise Http404() ctx = { "presentation": presentation, @@ -214,8 +220,6 @@ def schedule_json(request): "tags": "", "released": True, "contact": [], - - } if hasattr(slot.content, "proposal"): slot_data.update({ From f42766beefc6dfa9e2f93f692c8d10133dc58a50 Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer Date: Sun, 18 Sep 2016 11:17:15 +1000 Subject: [PATCH 711/751] Respects unpublishing in lists. --- symposion/schedule/views.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/symposion/schedule/views.py b/symposion/schedule/views.py index 4e950959..08a50cbb 100644 --- a/symposion/schedule/views.py +++ b/symposion/schedule/views.py @@ -78,6 +78,9 @@ def schedule_list(request, slug=None): presentations = Presentation.objects.filter(section=schedule.section) presentations = presentations.exclude(cancelled=True) + if not request.user.is_staff: + presentations = presentations.exclude(unpublish=True) + ctx = { "schedule": schedule, "presentations": presentations, @@ -91,7 +94,10 @@ def schedule_list_csv(request, slug=None): raise Http404() presentations = Presentation.objects.filter(section=schedule.section) - presentations = presentations.exclude(cancelled=True).order_by("id") + presentations = presentations.exclude(cancelled=True) + if not request.user.is_staff: + presentations = presentations.exclude(unpublish=True) + presentations = presentations.order_by("id") response = HttpResponse(content_type="text/csv") if slug: @@ -222,6 +228,9 @@ def schedule_json(request): "contact": [], } if hasattr(slot.content, "proposal"): + if slot.content.proposal.unpublish and not request.user.is_staff: + continue + slot_data.update({ "name": slot.content.title, "authors": [s.name for s in slot.content.speakers()], From 699b32b938d2acd06e3e3dda12adab995a891aa8 Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer Date: Sun, 18 Sep 2016 15:46:53 +1000 Subject: [PATCH 712/751] =?UTF-8?q?Adds=20=E2=80=9Cpublish=20changes?= =?UTF-8?q?=E2=80=9D=20behaviour=20to=20views=20so=20that=20we=20can=20pub?= =?UTF-8?q?lish=20edits=20to=20abstracts.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- symposion/reviews/models.py | 12 +++++++++--- symposion/reviews/views.py | 9 ++++++--- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/symposion/reviews/models.py b/symposion/reviews/models.py index 2f596744..d47feff5 100644 --- a/symposion/reviews/models.py +++ b/symposion/reviews/models.py @@ -361,6 +361,12 @@ def promote_proposal(proposal): if hasattr(proposal, "presentation") and proposal.presentation: # already promoted presentation = proposal.presentation + presentation.title = proposal.title + presentation.abstract = proposal.abstract + presentation.speaker = proposal.speaker + presentation.proposal_base = proposal + presentation.save() + presentation.additional_speakers.clear() else: presentation = Presentation( title=proposal.title, @@ -370,9 +376,9 @@ def promote_proposal(proposal): proposal_base=proposal, ) presentation.save() - for speaker in proposal.additional_speakers.all(): - presentation.additional_speakers.add(speaker) - presentation.save() + for speaker in proposal.additional_speakers.all(): + presentation.additional_speakers.add(speaker) + presentation.save() return presentation diff --git a/symposion/reviews/views.py b/symposion/reviews/views.py index eff2f929..8c8015f9 100644 --- a/symposion/reviews/views.py +++ b/symposion/reviews/views.py @@ -23,7 +23,7 @@ from symposion.reviews.forms import ReviewForm, SpeakerCommentForm from symposion.reviews.forms import BulkPresentationForm from symposion.reviews.models import ( ReviewAssignment, Review, LatestVote, ProposalResult, NotificationTemplate, - ResultNotification + ResultNotification, promote_proposal ) @@ -422,8 +422,11 @@ def review_detail(request, pk): elif result == "standby": proposal.result.status = "standby" proposal.result.save() - - return redirect(request.path) + return redirect(request.path) + elif "publish_changes" in request.POST: + if admin and proposal.result.status == "accepted": + promote_proposal(proposal) + return redirect(request.path) else: initial = {} if latest_vote: From 2aa0074bdce1674696c9c28d23a3ff0a449a0454 Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer Date: Mon, 26 Sep 2016 11:18:05 +1000 Subject: [PATCH 713/751] Removes speaker assistance options from the profile form. --- symposion/speakers/forms.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/symposion/speakers/forms.py b/symposion/speakers/forms.py index d4643415..9c82113a 100644 --- a/symposion/speakers/forms.py +++ b/symposion/speakers/forms.py @@ -17,8 +17,6 @@ class SpeakerForm(forms.ModelForm): "homepage", "twitter_username", "accessibility", - "travel_assistance", - "accommodation_assistance", "agreement", ] From 42372791d58f21d12e6c80cab3deea45216c4c6a Mon Sep 17 00:00:00 2001 From: Scott Bragg Date: Tue, 27 Sep 2016 19:21:57 +1000 Subject: [PATCH 714/751] Don't notify everyone when a proposal changes, only the admins --- symposion/proposals/views.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/symposion/proposals/views.py b/symposion/proposals/views.py index d3ccdd0e..c3fff8a4 100644 --- a/symposion/proposals/views.py +++ b/symposion/proposals/views.py @@ -205,10 +205,14 @@ def proposal_edit(request, pk): if form.is_valid(): form.save() if hasattr(proposal, "reviews"): - users = User.objects.filter( - Q(review__proposal=proposal) | - Q(proposalmessage__proposal=proposal) - ) + # Miniconf updates should only email the admins + if proposal.kind.slug == 'miniconf': + users = User.objects.filter(username__in=settings.ADMIN_USERNAMES) + else: + users = User.objects.filter( + Q(review__proposal=proposal) | + Q(proposalmessage__proposal=proposal) + ) users = users.exclude(id=request.user.id).distinct() for user in users: ctx = { From 02d7066c443d30d4d27e3e9589d41c5452160166 Mon Sep 17 00:00:00 2001 From: Scott Bragg Date: Sun, 13 Nov 2016 15:31:08 +1100 Subject: [PATCH 715/751] Increase slot name since it's made up of room names and our room names are long. --- .../migrations/0003_auto_20161113_1530.py | 20 +++++++++++++++++++ symposion/schedule/models.py | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 symposion/schedule/migrations/0003_auto_20161113_1530.py diff --git a/symposion/schedule/migrations/0003_auto_20161113_1530.py b/symposion/schedule/migrations/0003_auto_20161113_1530.py new file mode 100644 index 00000000..61f02b21 --- /dev/null +++ b/symposion/schedule/migrations/0003_auto_20161113_1530.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.7 on 2016-11-13 04:30 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('symposion_schedule', '0002_presentation_unpublish'), + ] + + operations = [ + migrations.AlterField( + model_name='slot', + name='name', + field=models.CharField(editable=False, max_length=150), + ), + ] diff --git a/symposion/schedule/models.py b/symposion/schedule/models.py index cfa36662..9f6a170c 100644 --- a/symposion/schedule/models.py +++ b/symposion/schedule/models.py @@ -82,7 +82,7 @@ class SlotKind(models.Model): @python_2_unicode_compatible class Slot(models.Model): - name = models.CharField(max_length=100, editable=False) + name = models.CharField(max_length=150, editable=False) day = models.ForeignKey(Day, verbose_name=_("Day")) kind = models.ForeignKey(SlotKind, verbose_name=_("Kind")) start = models.TimeField(verbose_name=_("Start")) From 4838adf7758c5eee1e6157aea3c0a9561975f0ff Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer Date: Sat, 10 Dec 2016 08:30:44 +1100 Subject: [PATCH 716/751] =?UTF-8?q?Adds=20=E2=80=9Cexclusive=E2=80=9D=20fi?= =?UTF-8?q?eld=20to=20slots,=20so=20that=20you=20don=E2=80=99t=20need=20to?= =?UTF-8?q?=20add=20every=20single=20room=20to=20exclusive=20events=20(lik?= =?UTF-8?q?e=20keynotes)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../migrations/0003_slot_exclusive.py | 20 +++++++++++++++++++ symposion/schedule/models.py | 5 +++++ symposion/schedule/timetable.py | 3 ++- 3 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 symposion/schedule/migrations/0003_slot_exclusive.py diff --git a/symposion/schedule/migrations/0003_slot_exclusive.py b/symposion/schedule/migrations/0003_slot_exclusive.py new file mode 100644 index 00000000..9fc0d847 --- /dev/null +++ b/symposion/schedule/migrations/0003_slot_exclusive.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.7 on 2016-12-09 20:53 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('symposion_schedule', '0002_presentation_unpublish'), + ] + + operations = [ + migrations.AddField( + model_name='slot', + name='exclusive', + field=models.BooleanField(default=False, help_text='Set to true if this is the only event during this timeslot'), + ), + ] diff --git a/symposion/schedule/models.py b/symposion/schedule/models.py index 9f6a170c..dd54bc83 100644 --- a/symposion/schedule/models.py +++ b/symposion/schedule/models.py @@ -87,6 +87,11 @@ class Slot(models.Model): kind = models.ForeignKey(SlotKind, verbose_name=_("Kind")) start = models.TimeField(verbose_name=_("Start")) end = models.TimeField(verbose_name=_("End")) + exclusive = models.BooleanField( + default=False, + help_text=_("Set to true if this is the only event during this " + "timeslot"), + ) content_override = models.TextField(blank=True, verbose_name=_("Content override")) content_override_html = models.TextField(blank=True) diff --git a/symposion/schedule/timetable.py b/symposion/schedule/timetable.py index cc77a7d8..d5f422b0 100644 --- a/symposion/schedule/timetable.py +++ b/symposion/schedule/timetable.py @@ -30,12 +30,13 @@ class TimeTable(object): slots = slots.annotate(room_count=Count("slotroom"), order=Min("slotroom__room__order")) slots = slots.order_by("start", "order") row = [] + total_room_count = self.rooms().count() for time, next_time in pairwise(times): row = {"time": time, "slots": []} for slot in slots: if slot.start == time: slot.rowspan = TimeTable.rowspan(times, slot.start, slot.end) - slot.colspan = slot.room_count + slot.colspan = slot.room_count if not slot.exclusive else total_room_count row["slots"].append(slot) if row["slots"] or next_time is None: yield row From 5e372be5f6ba970a7ec67bd9b1805f1ff78967a9 Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer Date: Sat, 10 Dec 2016 14:48:30 +1100 Subject: [PATCH 717/751] Fixes issue with conference.json view --- symposion/schedule/views.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/symposion/schedule/views.py b/symposion/schedule/views.py index 08a50cbb..2cc7fa8a 100644 --- a/symposion/schedule/views.py +++ b/symposion/schedule/views.py @@ -228,7 +228,7 @@ def schedule_json(request): "contact": [], } if hasattr(slot.content, "proposal"): - if slot.content.proposal.unpublish and not request.user.is_staff: + if slot.content.unpublish and not request.user.is_staff: continue slot_data.update({ @@ -237,7 +237,7 @@ def schedule_json(request): "contact": [ s.email for s in slot.content.speakers() ] if request.user.is_staff else ["redacted"], - "abstract": slot.content.abstract.raw, + "abstract": slot.content.abstract, "conf_url": "%s://%s%s" % ( protocol, Site.objects.get_current().domain, @@ -247,7 +247,7 @@ def schedule_json(request): }) else: slot_data.update({ - "name": slot.content_override.raw if slot.content_override else "Slot", + "name": slot.content_override if slot.content_override else "Slot", }) data.append(slot_data) From de38ffac9e5450935c5849004821defa1d9150c2 Mon Sep 17 00:00:00 2001 From: Scott Bragg Date: Sat, 10 Dec 2016 17:07:38 +1100 Subject: [PATCH 718/751] Needs an additional migration to merge two 0003 migrations in schedule. --- symposion/schedule/migrations/0004_merge.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 symposion/schedule/migrations/0004_merge.py diff --git a/symposion/schedule/migrations/0004_merge.py b/symposion/schedule/migrations/0004_merge.py new file mode 100644 index 00000000..b89bc7cd --- /dev/null +++ b/symposion/schedule/migrations/0004_merge.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.7 on 2016-12-10 06:05 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('symposion_schedule', '0003_slot_exclusive'), + ('symposion_schedule', '0003_auto_20161113_1530'), + ] + + operations = [ + ] From cdec6e225865c5812d78a4779cf46239f3e11e0b Mon Sep 17 00:00:00 2001 From: Scott Bragg Date: Sat, 10 Dec 2016 17:37:01 +1100 Subject: [PATCH 719/751] Slot name needs to be looooooonger --- .../migrations/0005_auto_20161210_1736.py | 20 +++++++++++++++++++ symposion/schedule/models.py | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 symposion/schedule/migrations/0005_auto_20161210_1736.py diff --git a/symposion/schedule/migrations/0005_auto_20161210_1736.py b/symposion/schedule/migrations/0005_auto_20161210_1736.py new file mode 100644 index 00000000..e4b5e49d --- /dev/null +++ b/symposion/schedule/migrations/0005_auto_20161210_1736.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.7 on 2016-12-10 06:36 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('symposion_schedule', '0004_merge'), + ] + + operations = [ + migrations.AlterField( + model_name='slot', + name='name', + field=models.CharField(editable=False, max_length=512), + ), + ] diff --git a/symposion/schedule/models.py b/symposion/schedule/models.py index dd54bc83..5622b844 100644 --- a/symposion/schedule/models.py +++ b/symposion/schedule/models.py @@ -82,7 +82,7 @@ class SlotKind(models.Model): @python_2_unicode_compatible class Slot(models.Model): - name = models.CharField(max_length=150, editable=False) + name = models.CharField(max_length=512, editable=False) day = models.ForeignKey(Day, verbose_name=_("Day")) kind = models.ForeignKey(SlotKind, verbose_name=_("Kind")) start = models.TimeField(verbose_name=_("Start")) From a37d620afb1349e1a801e2a39285c9c479bd0330 Mon Sep 17 00:00:00 2001 From: Scott Bragg Date: Thu, 22 Dec 2016 12:00:23 +1100 Subject: [PATCH 720/751] Adds an ical feed --- requirements/base.txt | 1 + symposion/schedule/urls.py | 2 ++ symposion/schedule/views.py | 43 +++++++++++++++++++++++++++++++++++++ 3 files changed, 46 insertions(+) diff --git a/requirements/base.txt b/requirements/base.txt index d2fe5479..ad552399 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -10,3 +10,4 @@ easy-thumbnails==2.3 html5lib==0.9999999 markdown==2.6.5 pytz==2015.7 +django-ical==1.4 diff --git a/symposion/schedule/urls.py b/symposion/schedule/urls.py index 28b05f1f..7dc6befb 100644 --- a/symposion/schedule/urls.py +++ b/symposion/schedule/urls.py @@ -13,6 +13,7 @@ from .views import ( session_staff_email, session_list, session_detail, + EventFeed ) urlpatterns = [ @@ -27,6 +28,7 @@ urlpatterns = [ url(r"^([\w\-]+)/presentations.csv$", schedule_list_csv, name="schedule_list_csv"), url(r"^([\w\-]+)/edit/slot/(\d+)/", schedule_slot_edit, name="schedule_slot_edit"), url(r"^conference.json", schedule_json, name="schedule_json"), + url(r"^conference.ics", EventFeed(), name="ical_feed"), url(r"^sessions/staff.txt$", session_staff_email, name="schedule_session_staff_email"), url(r"^sessions/$", session_list, name="schedule_session_list"), url(r"^session/(\d+)/$", session_detail, name="schedule_session_detail"), diff --git a/symposion/schedule/views.py b/symposion/schedule/views.py index 2cc7fa8a..a3881145 100644 --- a/symposion/schedule/views.py +++ b/symposion/schedule/views.py @@ -10,6 +10,8 @@ from django.contrib.auth.models import User from django.contrib import messages from django.contrib.sites.models import Site +from django_ical.views import ICalFeed + from account.decorators import login_required from symposion.schedule.forms import SlotEditForm, ScheduleSectionForm @@ -256,6 +258,47 @@ def schedule_json(request): content_type="application/json" ) +class EventFeed(ICalFeed): + + product_id = '-//linux.conf.au/schedule//EN' + timezone = 'Australia/Tasmania' + filename = 'conference.ics' + + def items(self): + return Slot.objects.filter( + day__schedule__published=True, + day__schedule__hidden=False + ).order_by("start") + + def item_title(self, item): + if hasattr(item.content, 'proposal'): + return item.content.title + else: + item.content_override if item.content_override else "Slot" + + def item_description(self, item): + if hasattr(item.content, 'proposal'): + return item.content.abstract + else: + return None + + def item_start_datetime(self, item): + return item.start_datetime + + def item_end_datetime(self, item): + return item.end_datetime + + def item_location(self, item): + return ", ".join(room["name"] for room in item.rooms.values()) + + def item_link(self, item): + if hasattr(item.content, 'proposal'): + return 'http://%s%s' % ( + Site.objects.get_current().domain, + reverse('schedule_presentation_detail', args=[item.content.pk]) + ) + else: + return 'http://%s' % Site.objects.get_current().domain def session_list(request): sessions = Session.objects.all().order_by('pk') From d6ac7edc5d4aa4a7b8b46f1cfc5916929b278d72 Mon Sep 17 00:00:00 2001 From: Scott Bragg Date: Fri, 23 Dec 2016 20:12:38 +1100 Subject: [PATCH 721/751] Added timezone to start/end datetimes --- symposion/schedule/views.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/symposion/schedule/views.py b/symposion/schedule/views.py index a3881145..5340d53a 100644 --- a/symposion/schedule/views.py +++ b/symposion/schedule/views.py @@ -1,10 +1,12 @@ from __future__ import unicode_literals import json +import pytz from django.core.urlresolvers import reverse from django.http import Http404, HttpResponse from django.shortcuts import render, get_object_or_404, redirect from django.template import loader, Context +from django.conf import settings from django.contrib.auth.models import User from django.contrib import messages @@ -261,7 +263,7 @@ def schedule_json(request): class EventFeed(ICalFeed): product_id = '-//linux.conf.au/schedule//EN' - timezone = 'Australia/Tasmania' + timezone = settings.TIME_ZONE filename = 'conference.ics' def items(self): @@ -283,10 +285,10 @@ class EventFeed(ICalFeed): return None def item_start_datetime(self, item): - return item.start_datetime + return pytz.timezone(settings.TIME_ZONE).localize(item.start_datetime) def item_end_datetime(self, item): - return item.end_datetime + return pytz.timezone(settings.TIME_ZONE).localize(item.end_datetime) def item_location(self, item): return ", ".join(room["name"] for room in item.rooms.values()) From 7ae022d2c412930e263499ba4a22d2718482116d Mon Sep 17 00:00:00 2001 From: Scott Bragg Date: Sat, 24 Dec 2016 15:24:31 +1100 Subject: [PATCH 722/751] Added track name to room, added day option to conference schedule view --- .../schedule/migrations/0006_room_track.py | 20 +++++++++++++++++++ symposion/schedule/models.py | 3 ++- symposion/schedule/views.py | 2 ++ 3 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 symposion/schedule/migrations/0006_room_track.py diff --git a/symposion/schedule/migrations/0006_room_track.py b/symposion/schedule/migrations/0006_room_track.py new file mode 100644 index 00000000..3e88890f --- /dev/null +++ b/symposion/schedule/migrations/0006_room_track.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.7 on 2016-12-24 00:10 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('symposion_schedule', '0005_auto_20161210_1736'), + ] + + operations = [ + migrations.AddField( + model_name='room', + name='track', + field=models.CharField(blank=True, default=None, max_length=80, null=True, verbose_name='Track'), + ), + ] diff --git a/symposion/schedule/models.py b/symposion/schedule/models.py index 5622b844..0ee60677 100644 --- a/symposion/schedule/models.py +++ b/symposion/schedule/models.py @@ -51,10 +51,11 @@ class Room(models.Model): schedule = models.ForeignKey(Schedule, verbose_name=_("Schedule")) name = models.CharField(max_length=65, verbose_name=_("Name")) + track = models.CharField(max_length=80, verbose_name=_("Track"), default=None, blank=True, null=True) order = models.PositiveIntegerField(verbose_name=_("Order")) def __str__(self): - return self.name + return '%s (%s)' % (self.name, self.track) class Meta: verbose_name = _("Room") diff --git a/symposion/schedule/views.py b/symposion/schedule/views.py index 5340d53a..996560f9 100644 --- a/symposion/schedule/views.py +++ b/symposion/schedule/views.py @@ -52,8 +52,10 @@ def schedule_conference(request): "days": days, }) + day_switch = request.GET.get('day', None) ctx = { "sections": sections, + "day_switch": day_switch } return render(request, "symposion/schedule/schedule_conference.html", ctx) From 765e80765c3e3e3bb5ea8cfb4cf2a32e4a81409e Mon Sep 17 00:00:00 2001 From: Scott Bragg Date: Sat, 24 Dec 2016 17:55:11 +1100 Subject: [PATCH 723/751] Added Track model for schedule headings --- symposion/schedule/admin.py | 3 +- .../migrations/0007_auto_20161224_1709.py | 41 +++++++++++++++++++ symposion/schedule/models.py | 18 +++++++- 3 files changed, 59 insertions(+), 3 deletions(-) create mode 100644 symposion/schedule/migrations/0007_auto_20161224_1709.py diff --git a/symposion/schedule/admin.py b/symposion/schedule/admin.py index 708ea131..7cf59ecb 100644 --- a/symposion/schedule/admin.py +++ b/symposion/schedule/admin.py @@ -1,7 +1,7 @@ from __future__ import unicode_literals from django.contrib import admin -from symposion.schedule.models import Schedule, Day, Room, SlotKind, Slot, SlotRoom, Presentation, Session, SessionRole +from symposion.schedule.models import Schedule, Day, Room, SlotKind, Slot, SlotRoom, Presentation, Session, SessionRole, Track class DayInline(admin.StackedInline): @@ -55,3 +55,4 @@ admin.site.register(Slot, SlotAdmin) admin.site.register(Session) admin.site.register(SessionRole) admin.site.register(Presentation, PresentationAdmin) +admin.site.register(Track) diff --git a/symposion/schedule/migrations/0007_auto_20161224_1709.py b/symposion/schedule/migrations/0007_auto_20161224_1709.py new file mode 100644 index 00000000..2ddbfafb --- /dev/null +++ b/symposion/schedule/migrations/0007_auto_20161224_1709.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.7 on 2016-12-24 06:09 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('symposion_schedule', '0006_room_track'), + ] + + operations = [ + migrations.CreateModel( + name='Track', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=80, verbose_name='Track')), + ('day', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='symposion_schedule.Day')), + ], + options={ + 'verbose_name': 'Track', + 'verbose_name_plural': 'Tracks', + }, + ), + migrations.RemoveField( + model_name='room', + name='track', + ), + migrations.AddField( + model_name='track', + name='room', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='symposion_schedule.Room'), + ), + migrations.AlterUniqueTogether( + name='track', + unique_together=set([('room', 'day')]), + ), + ] diff --git a/symposion/schedule/models.py b/symposion/schedule/models.py index 0ee60677..a5074252 100644 --- a/symposion/schedule/models.py +++ b/symposion/schedule/models.py @@ -51,17 +51,31 @@ class Room(models.Model): schedule = models.ForeignKey(Schedule, verbose_name=_("Schedule")) name = models.CharField(max_length=65, verbose_name=_("Name")) - track = models.CharField(max_length=80, verbose_name=_("Track"), default=None, blank=True, null=True) order = models.PositiveIntegerField(verbose_name=_("Order")) def __str__(self): - return '%s (%s)' % (self.name, self.track) + return self.name class Meta: verbose_name = _("Room") verbose_name_plural = _("Rooms") +@python_2_unicode_compatible +class Track(models.Model): + name = models.CharField(max_length=80, verbose_name=_("Track")) + room = models.ForeignKey(Room) + day = models.ForeignKey(Day) + + def __str__(self): + return self.name + + class Meta: + unique_together = [('room', 'day')] + verbose_name = _("Track") + verbose_name_plural = _("Tracks") + + @python_2_unicode_compatible class SlotKind(models.Model): """ From dbb4ebbb704453eeddb866d696aae2ce0d6ed598 Mon Sep 17 00:00:00 2001 From: Scott Bragg Date: Fri, 30 Dec 2016 19:21:36 +1100 Subject: [PATCH 724/751] Add a permission to view speaker contact details in conference.json --- symposion/schedule/views.py | 4 ++-- .../migrations/0002_auto_20161230_1900.py | 19 +++++++++++++++++++ symposion/speakers/models.py | 1 + 3 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 symposion/speakers/migrations/0002_auto_20161230_1900.py diff --git a/symposion/schedule/views.py b/symposion/schedule/views.py index 996560f9..4afaa473 100644 --- a/symposion/schedule/views.py +++ b/symposion/schedule/views.py @@ -242,7 +242,7 @@ def schedule_json(request): "authors": [s.name for s in slot.content.speakers()], "contact": [ s.email for s in slot.content.speakers() - ] if request.user.is_staff else ["redacted"], + ] if request.user.has_perm('symposion_speakers.can_view_contact_details') or request.user.is_staff else ["redacted"], "abstract": slot.content.abstract, "conf_url": "%s://%s%s" % ( protocol, @@ -258,7 +258,7 @@ def schedule_json(request): data.append(slot_data) return HttpResponse( - json.dumps({"schedule": data}), + json.dumps({"schedule": data}, indent=2), content_type="application/json" ) diff --git a/symposion/speakers/migrations/0002_auto_20161230_1900.py b/symposion/speakers/migrations/0002_auto_20161230_1900.py new file mode 100644 index 00000000..68444662 --- /dev/null +++ b/symposion/speakers/migrations/0002_auto_20161230_1900.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.7 on 2016-12-30 08:09 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('symposion_speakers', '0001_initial'), + ] + + operations = [ + migrations.AlterModelOptions( + name='speaker', + options={'ordering': ['name'], 'permissions': (('can_view_contact_details', 'Can View Contact Details'),), 'verbose_name': 'Speaker', 'verbose_name_plural': 'Speakers'}, + ), + ] diff --git a/symposion/speakers/models.py b/symposion/speakers/models.py index d1ba409e..ddbbceee 100644 --- a/symposion/speakers/models.py +++ b/symposion/speakers/models.py @@ -105,6 +105,7 @@ class Speaker(models.Model): ordering = ['name'] verbose_name = _("Speaker") verbose_name_plural = _("Speakers") + permissions = (('can_view_contact_details', 'Can View Contact Details'),) def save(self, *args, **kwargs): self.biography_html = parse(self.biography) From b783901e98d98d3b3fc34b402bb2b2f47df124d6 Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer Date: Sat, 10 Dec 2016 16:39:30 +1100 Subject: [PATCH 725/751] Volunteers need to have a ticket before they can apply to volunteer. --- symposion/schedule/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/symposion/schedule/views.py b/symposion/schedule/views.py index 4afaa473..03533fce 100644 --- a/symposion/schedule/views.py +++ b/symposion/schedule/views.py @@ -350,8 +350,8 @@ def session_detail(request, session_id): runner_denied = True if request.method == "POST" and request.user.is_authenticated(): - if not hasattr(request.user, "profile") or not request.user.profile.is_complete: - response = redirect("profile_edit") + if not hasattr(request.user, "attendee") or not request.user.attendee.completed_registration: + response = redirect("guided_registration") response["Location"] += "?next=%s" % request.path return response From 34e250322c07b9389c868a9cb5e7b5b5ae6f8709 Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer Date: Sat, 10 Dec 2016 17:00:49 +1100 Subject: [PATCH 726/751] Adds chair() method to sessions so that templates can easily determine who the chair is. --- symposion/schedule/models.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/symposion/schedule/models.py b/symposion/schedule/models.py index a5074252..c4807df6 100644 --- a/symposion/schedule/models.py +++ b/symposion/schedule/models.py @@ -270,6 +270,12 @@ class Session(models.Model): else: return None + def chair(self): + for role in self.sessionrole_set.all(): + if role.role == SessionRole.SESSION_ROLE_CHAIR: + return role + return None + def __str__(self): start = self.start() end = self.end() From 710d377016c1290528e02d8bb2110fd7b56a1a98 Mon Sep 17 00:00:00 2001 From: James Polley Date: Thu, 12 Jan 2017 15:56:56 +1100 Subject: [PATCH 727/751] Include author details in iCal event description --- symposion/schedule/views.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/symposion/schedule/views.py b/symposion/schedule/views.py index 03533fce..017ab11f 100644 --- a/symposion/schedule/views.py +++ b/symposion/schedule/views.py @@ -267,34 +267,36 @@ class EventFeed(ICalFeed): product_id = '-//linux.conf.au/schedule//EN' timezone = settings.TIME_ZONE filename = 'conference.ics' - + def items(self): return Slot.objects.filter( day__schedule__published=True, day__schedule__hidden=False - ).order_by("start") - + ).order_by("start") + def item_title(self, item): if hasattr(item.content, 'proposal'): return item.content.title else: item.content_override if item.content_override else "Slot" - + def item_description(self, item): if hasattr(item.content, 'proposal'): - return item.content.abstract + description = "Speaker: %s\n%s" % ( + item.content.speaker, item.content.abstract) + return description else: return None - + def item_start_datetime(self, item): return pytz.timezone(settings.TIME_ZONE).localize(item.start_datetime) - + def item_end_datetime(self, item): return pytz.timezone(settings.TIME_ZONE).localize(item.end_datetime) - + def item_location(self, item): return ", ".join(room["name"] for room in item.rooms.values()) - + def item_link(self, item): if hasattr(item.content, 'proposal'): return 'http://%s%s' % ( From 3d626e8420676aac988d759852cd7ba7d7739138 Mon Sep 17 00:00:00 2001 From: James Polley Date: Thu, 12 Jan 2017 16:49:07 +1100 Subject: [PATCH 728/751] Handle slots with no Proposal * Use the item kind for a title if there's no proposal * Use the content_override for the description if there's no proposal --- symposion/schedule/views.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/symposion/schedule/views.py b/symposion/schedule/views.py index 017ab11f..3fca85e6 100644 --- a/symposion/schedule/views.py +++ b/symposion/schedule/views.py @@ -276,17 +276,18 @@ class EventFeed(ICalFeed): def item_title(self, item): if hasattr(item.content, 'proposal'): - return item.content.title + title = item.content.title else: - item.content_override if item.content_override else "Slot" + title = item.kind if item.kind else "Slot" + return title def item_description(self, item): if hasattr(item.content, 'proposal'): description = "Speaker: %s\n%s" % ( item.content.speaker, item.content.abstract) - return description else: - return None + description = item.content_override if item.content_override else "No description" + return description def item_start_datetime(self, item): return pytz.timezone(settings.TIME_ZONE).localize(item.start_datetime) From 46ca912f7ce004207a3f5a35fe3eefcb59e2d68d Mon Sep 17 00:00:00 2001 From: Scott Bragg Date: Thu, 12 Jan 2017 20:21:47 +1100 Subject: [PATCH 729/751] Give all ical events a unique uid and remove shortbreaks --- symposion/schedule/views.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/symposion/schedule/views.py b/symposion/schedule/views.py index 3fca85e6..319d5013 100644 --- a/symposion/schedule/views.py +++ b/symposion/schedule/views.py @@ -272,6 +272,8 @@ class EventFeed(ICalFeed): return Slot.objects.filter( day__schedule__published=True, day__schedule__hidden=False + ).exclude( + kind__label='shortbreak' ).order_by("start") def item_title(self, item): @@ -306,6 +308,9 @@ class EventFeed(ICalFeed): ) else: return 'http://%s' % Site.objects.get_current().domain + + def item_guid(self, item): + return '%d@%s' % (item.pk, Site.objects.get_current().domain) def session_list(request): sessions = Session.objects.all().order_by('pk') From 2dbf020a81a92c49caea0deb48d5e12d005aad62 Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer Date: Fri, 13 Jan 2017 09:05:47 +1100 Subject: [PATCH 730/751] Unbreaks URLs --- symposion/schedule/urls.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/symposion/schedule/urls.py b/symposion/schedule/urls.py index 7dc6befb..f6f3f900 100644 --- a/symposion/schedule/urls.py +++ b/symposion/schedule/urls.py @@ -22,6 +22,9 @@ urlpatterns = [ url(r"^list/$", schedule_list, name="schedule_list"), url(r"^presentations.csv$", schedule_list_csv, name="schedule_list_csv"), url(r"^presentation/(\d+)/$", schedule_presentation_detail, name="schedule_presentation_detail"), + url(r"^sessions/staff.txt$", session_staff_email, name="schedule_session_staff_email"), + url(r"^sessions/$", session_list, name="schedule_session_list"), + url(r"^session/(\d+)/$", session_detail, name="schedule_session_detail"), url(r"^([\w\-]+)/$", schedule_detail, name="schedule_detail"), url(r"^([\w\-]+)/edit/$", schedule_edit, name="schedule_edit"), url(r"^([\w\-]+)/list/$", schedule_list, name="schedule_list"), @@ -29,7 +32,4 @@ urlpatterns = [ url(r"^([\w\-]+)/edit/slot/(\d+)/", schedule_slot_edit, name="schedule_slot_edit"), url(r"^conference.json", schedule_json, name="schedule_json"), url(r"^conference.ics", EventFeed(), name="ical_feed"), - url(r"^sessions/staff.txt$", session_staff_email, name="schedule_session_staff_email"), - url(r"^sessions/$", session_list, name="schedule_session_list"), - url(r"^session/(\d+)/$", session_detail, name="schedule_session_detail"), ] From 4e22717639a72c23d0bd1dfa8bd3fe2025bc43d4 Mon Sep 17 00:00:00 2001 From: Scott Bragg Date: Sat, 14 Jan 2017 09:47:14 +1100 Subject: [PATCH 731/751] Add description to ICal feed to help Giggety app identify conference --- symposion/schedule/views.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/symposion/schedule/views.py b/symposion/schedule/views.py index 319d5013..ca70542e 100644 --- a/symposion/schedule/views.py +++ b/symposion/schedule/views.py @@ -19,7 +19,7 @@ from account.decorators import login_required from symposion.schedule.forms import SlotEditForm, ScheduleSectionForm from symposion.schedule.models import Schedule, Day, Slot, Presentation, Session, SessionRole from symposion.schedule.timetable import TimeTable - +from symposion.conference.models import Conference def fetch_schedule(slug): qs = Schedule.objects.all() @@ -267,6 +267,7 @@ class EventFeed(ICalFeed): product_id = '-//linux.conf.au/schedule//EN' timezone = settings.TIME_ZONE filename = 'conference.ics' + description = Conference.objects.all().first().title def items(self): return Slot.objects.filter( From 8cf4bf34903908994c807785852d9d009ba83beb Mon Sep 17 00:00:00 2001 From: Scott Bragg Date: Sun, 15 Jan 2017 09:55:53 +1100 Subject: [PATCH 732/751] Add twitter to json --- symposion/schedule/views.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/symposion/schedule/views.py b/symposion/schedule/views.py index ca70542e..188a9082 100644 --- a/symposion/schedule/views.py +++ b/symposion/schedule/views.py @@ -251,6 +251,8 @@ def schedule_json(request): ), "cancelled": slot.content.cancelled, }) + if not slot.content.speaker.twitter_username == '': + slot_data["twitter_id"] = slot.content.speaker.twitter_username else: slot_data.update({ "name": slot.content_override if slot.content_override else "Slot", From 37dd7dd15ba6d35ae5deb024dbc22123d9c77ce2 Mon Sep 17 00:00:00 2001 From: Scott Bragg Date: Sun, 15 Jan 2017 15:39:44 +1100 Subject: [PATCH 733/751] Fixed recording release to default false --- symposion/schedule/views.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/symposion/schedule/views.py b/symposion/schedule/views.py index 188a9082..aba9a9a4 100644 --- a/symposion/schedule/views.py +++ b/symposion/schedule/views.py @@ -230,7 +230,7 @@ def schedule_json(request): # base proposals "license": "CC BY", "tags": "", - "released": True, + "released": False, "contact": [], } if hasattr(slot.content, "proposal"): @@ -250,6 +250,7 @@ def schedule_json(request): reverse("schedule_presentation_detail", args=[slot.content.pk]) ), "cancelled": slot.content.cancelled, + "released": slot.content.proposal.recording_release }) if not slot.content.speaker.twitter_username == '': slot_data["twitter_id"] = slot.content.speaker.twitter_username From 155f841afa5dd8db7ef8e0a6d55669c037427efd Mon Sep 17 00:00:00 2001 From: Joel Addison Date: Sun, 29 Jan 2017 22:08:49 +1000 Subject: [PATCH 734/751] Generate ical description dynamically Use method instead of class attribute for ical feed description. This allows the class to be instantiated without a database being available (eg. during migrate). --- symposion/schedule/views.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/symposion/schedule/views.py b/symposion/schedule/views.py index aba9a9a4..e7c2eedd 100644 --- a/symposion/schedule/views.py +++ b/symposion/schedule/views.py @@ -270,7 +270,8 @@ class EventFeed(ICalFeed): product_id = '-//linux.conf.au/schedule//EN' timezone = settings.TIME_ZONE filename = 'conference.ics' - description = Conference.objects.all().first().title + def description(self): + return Conference.objects.all().first().title def items(self): return Slot.objects.filter( From a8a5d3b5e865d57015dd5238dec9630d7ecc6b01 Mon Sep 17 00:00:00 2001 From: Sachi King Date: Sun, 26 Mar 2017 11:36:11 +1100 Subject: [PATCH 735/751] django-sitetree >= 1.7.0 and not use version pin In the future, when we want to lock a package to a specific version, we will do that via a constraints file in the master project. Making it difficult to update deps is not okay. --- requirements/base.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/base.txt b/requirements/base.txt index ad552399..d896174b 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -2,7 +2,7 @@ Django==1.9.7 django-appconf==1.0.1 django-model-utils==2.4.0 django-reversion==1.10.1 -django-sitetree==1.5.1 +django-sitetree>=1.7.0 django-taggit==0.18.0 django-timezone-field==1.3 django-user-accounts==1.3.1 From d5986de8704332d3ae5e3a8beb13158e40b779fd Mon Sep 17 00:00:00 2001 From: Sachi King Date: Sun, 26 Mar 2017 11:50:10 +1100 Subject: [PATCH 736/751] Use django's login_required decorator This only seems to be here to have some custom login_url used bypassing django's model. Well as we want to use django's model, this just won't do. So lets move to using django's model. --- symposion/conference/views.py | 3 +-- symposion/proposals/views.py | 2 +- symposion/reviews/views.py | 3 +-- symposion/schedule/views.py | 2 +- symposion/speakers/views.py | 3 +-- symposion/sponsorship/views.py | 3 +-- symposion/teams/views.py | 3 +-- symposion/views.py | 4 +--- 8 files changed, 8 insertions(+), 15 deletions(-) diff --git a/symposion/conference/views.py b/symposion/conference/views.py index 7b107a7e..33e3b097 100644 --- a/symposion/conference/views.py +++ b/symposion/conference/views.py @@ -1,10 +1,9 @@ from django.http import Http404 from django.shortcuts import render +from django.contrib.auth.decorators import login_required from django.contrib.auth.models import User -from account.decorators import login_required - @login_required def user_list(request): diff --git a/symposion/proposals/views.py b/symposion/proposals/views.py index c3fff8a4..0654f7a5 100644 --- a/symposion/proposals/views.py +++ b/symposion/proposals/views.py @@ -4,6 +4,7 @@ import random import sys from django.conf import settings +from django.contrib.auth.decorators import login_required from django.core.exceptions import ObjectDoesNotExist from django.core.urlresolvers import reverse from django.db.models import Q @@ -16,7 +17,6 @@ from django.contrib.auth.models import User from django.utils.translation import ugettext_lazy as _ -from account.decorators import login_required from account.models import EmailAddress from symposion.proposals.models import ( diff --git a/symposion/reviews/views.py b/symposion/reviews/views.py index 8c8015f9..a2904dbe 100644 --- a/symposion/reviews/views.py +++ b/symposion/reviews/views.py @@ -2,6 +2,7 @@ import csv import random import StringIO +from django.contrib.auth.decorators import login_required from django.core.mail import send_mass_mail from django.db.models import Q from django.http import HttpResponse @@ -10,8 +11,6 @@ from django.shortcuts import render, redirect, get_object_or_404 from django.template import Context, Template from django.views.decorators.http import require_POST -from account.decorators import login_required - # @@@ switch to pinax-teams from symposion.teams.models import Team diff --git a/symposion/schedule/views.py b/symposion/schedule/views.py index e7c2eedd..6e85a2fd 100644 --- a/symposion/schedule/views.py +++ b/symposion/schedule/views.py @@ -14,7 +14,7 @@ from django.contrib.sites.models import Site from django_ical.views import ICalFeed -from account.decorators import login_required +from django.contrib.auth.decorators import login_required from symposion.schedule.forms import SlotEditForm, ScheduleSectionForm from symposion.schedule.models import Schedule, Day, Slot, Presentation, Session, SessionRole diff --git a/symposion/speakers/views.py b/symposion/speakers/views.py index f60064e0..9ed32ec4 100644 --- a/symposion/speakers/views.py +++ b/symposion/speakers/views.py @@ -4,11 +4,10 @@ from django.http import Http404 from django.shortcuts import render, redirect, get_object_or_404 from django.contrib import messages +from django.contrib.auth.decorators import login_required from django.contrib.auth.models import User from django.utils.translation import ugettext_lazy as _ -from account.decorators import login_required - from symposion.proposals.models import ProposalBase from symposion.speakers.forms import SpeakerForm from symposion.speakers.models import Speaker diff --git a/symposion/sponsorship/views.py b/symposion/sponsorship/views.py index 3f749900..d3bd47fe 100644 --- a/symposion/sponsorship/views.py +++ b/symposion/sponsorship/views.py @@ -13,13 +13,12 @@ from zipfile import ZipFile, ZipInfo from django.conf import settings from django.contrib import messages from django.contrib.admin.views.decorators import staff_member_required +from django.contrib.auth.decorators import login_required 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 account.decorators import login_required - from symposion.sponsorship.forms import SponsorApplicationForm, \ SponsorDetailsForm, SponsorBenefitsFormSet from symposion.sponsorship.models import Benefit, Sponsor, SponsorBenefit, \ diff --git a/symposion/teams/views.py b/symposion/teams/views.py index 78cf7a88..be11be7f 100644 --- a/symposion/teams/views.py +++ b/symposion/teams/views.py @@ -3,10 +3,9 @@ from django.http import Http404, HttpResponseNotAllowed from django.shortcuts import render, redirect, get_object_or_404 from django.contrib import messages +from django.contrib.auth.decorators import login_required from django.utils.translation import ugettext_lazy as _ -from account.decorators import login_required - from symposion.utils.mail import send_email from symposion.teams.forms import TeamInvitationForm from symposion.teams.models import Team, Membership diff --git a/symposion/views.py b/symposion/views.py index 1cffbc11..a03545eb 100644 --- a/symposion/views.py +++ b/symposion/views.py @@ -1,11 +1,9 @@ from __future__ import unicode_literals +from django.contrib.auth.decorators import login_required from django.shortcuts import render, redirect -from account.decorators import login_required - - @login_required def dashboard(request): if request.session.get("pending-token"): From 8621bdb8fe121e58fdc1b41b0ca13887cb89a05f Mon Sep 17 00:00:00 2001 From: Sachi King Date: Sun, 26 Mar 2017 12:29:36 +1100 Subject: [PATCH 737/751] Use django to do lookups. Removes dj-user-accounts --- requirements/base.txt | 1 - symposion/proposals/views.py | 16 ++++++++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/requirements/base.txt b/requirements/base.txt index d896174b..8a08820a 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -5,7 +5,6 @@ django-reversion==1.10.1 django-sitetree>=1.7.0 django-taggit==0.18.0 django-timezone-field==1.3 -django-user-accounts==1.3.1 easy-thumbnails==2.3 html5lib==0.9999999 markdown==2.6.5 diff --git a/symposion/proposals/views.py b/symposion/proposals/views.py index 0654f7a5..d7131e61 100644 --- a/symposion/proposals/views.py +++ b/symposion/proposals/views.py @@ -5,7 +5,7 @@ import sys from django.conf import settings from django.contrib.auth.decorators import login_required -from django.core.exceptions import ObjectDoesNotExist +from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned from django.core.urlresolvers import reverse from django.db.models import Q from django.http import Http404, HttpResponse, HttpResponseForbidden @@ -17,8 +17,6 @@ from django.contrib.auth.models import User from django.utils.translation import ugettext_lazy as _ -from account.models import EmailAddress - from symposion.proposals.models import ( ProposalBase, ProposalSection, ProposalKind ) @@ -137,10 +135,16 @@ def proposal_speaker_manage(request, pk): return pending, token email_address = add_speaker_form.cleaned_data["email"] # check if email is on the site now - users = EmailAddress.objects.get_users_for(email_address) - if users: + try: + user = User.objects.get(email=email_address) + except MultipleObjectsReturned: + # FIXME: This is not handled in the previous code, so I'm not + # going to frett on this now, but should be handled as it is + # an occourance that really, really shouldn't occour. + # Previously, code took [0] from the list and continued. + raise NotImplementedError("Non unique email should not occour") + if user: # should only be one since we enforce unique email - user = users[0] message_ctx["user"] = user # look for speaker profile try: From f9e36033739fcecd6d3f77142b7b240417dede01 Mon Sep 17 00:00:00 2001 From: Sachi King Date: Thu, 30 Mar 2017 23:09:50 +1100 Subject: [PATCH 738/751] Support Django 1.10 with timezone upgrade Needed as SubfieldBase is depricated --- requirements/base.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/base.txt b/requirements/base.txt index 8a08820a..02c9d41c 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -4,7 +4,7 @@ django-model-utils==2.4.0 django-reversion==1.10.1 django-sitetree>=1.7.0 django-taggit==0.18.0 -django-timezone-field==1.3 +django-timezone-field>=2.0 easy-thumbnails==2.3 html5lib==0.9999999 markdown==2.6.5 From 0517c61542af230fc39c57ad83fe679568f49c54 Mon Sep 17 00:00:00 2001 From: Sachi King Date: Sat, 1 Apr 2017 17:38:47 +1100 Subject: [PATCH 739/751] Update django-model-utils to support Django 1.10 --- requirements/base.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/base.txt b/requirements/base.txt index 02c9d41c..5a955d0d 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -1,6 +1,6 @@ Django==1.9.7 django-appconf==1.0.1 -django-model-utils==2.4.0 +django-model-utils>=2.6.1 django-reversion==1.10.1 django-sitetree>=1.7.0 django-taggit==0.18.0 From de08802d5bfb364bfd806229797eb9bde0cbdc6b Mon Sep 17 00:00:00 2001 From: Sachi King Date: Sun, 2 Apr 2017 11:49:12 +1000 Subject: [PATCH 740/751] If user does not exist, catch exception User = None --- symposion/proposals/views.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/symposion/proposals/views.py b/symposion/proposals/views.py index d7131e61..7d5983b9 100644 --- a/symposion/proposals/views.py +++ b/symposion/proposals/views.py @@ -137,6 +137,8 @@ def proposal_speaker_manage(request, pk): # check if email is on the site now try: user = User.objects.get(email=email_address) + except ObjectDoesNotExist: + user = None except MultipleObjectsReturned: # FIXME: This is not handled in the previous code, so I'm not # going to frett on this now, but should be handled as it is From ecf14b514d2e2223c26653754ef3a3a670c4c03c Mon Sep 17 00:00:00 2001 From: Sachi King Date: Mon, 17 Apr 2017 15:47:10 +1000 Subject: [PATCH 741/751] Remove print statements. I was originally going to switch to print function, but frankly I can't see any reason to keep these. I don't know what data it's leaking into the logs, but it certainly doesn't seem useful StringIO is not in Py3. And it's not used at all, so removing that import. Seems to work with Py3 now. More testing is required. --- symposion/reviews/views.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/symposion/reviews/views.py b/symposion/reviews/views.py index a2904dbe..3768225d 100644 --- a/symposion/reviews/views.py +++ b/symposion/reviews/views.py @@ -1,6 +1,5 @@ import csv import random -import StringIO from django.contrib.auth.decorators import login_required from django.core.mail import send_mass_mail @@ -291,8 +290,6 @@ def review_admin(request, section_slug): user=user, proposal__kind__section__slug=section_slug, ) - print section_slug - print [vote.proposal.kind.section.slug for vote in user_votes] user.total_votes = user_votes.exclude( vote=LatestVote.VOTES.ABSTAIN, ).count() From 298b162be697e2340374470a155e3fde4e4172eb Mon Sep 17 00:00:00 2001 From: Sachi King Date: Mon, 17 Apr 2017 22:51:48 +1000 Subject: [PATCH 742/751] Flake8 Fixes Mostly whitespace fixes Some unicode fixes Fixed up CSV writer. str is not bytes and all. --- symposion/proposals/actions.py | 4 +- symposion/proposals/models.py | 1 + symposion/reviews/admin.py | 2 +- .../management/commands/assign_reviewers.py | 2 +- .../commands/create_review_permissions.py | 2 +- symposion/reviews/models.py | 4 + symposion/reviews/views.py | 27 +++---- symposion/schedule/helpers.py | 2 +- symposion/schedule/views.py | 17 ++-- .../commands/export_speaker_data.py | 15 ++-- symposion/speakers/models.py | 6 +- .../commands/export_sponsors_data.py | 80 +++++++++---------- .../commands/reset_sponsor_benefits.py | 2 +- symposion/sponsorship/models.py | 6 ++ symposion/teams/admin.py | 1 + symposion/teams/backends.py | 8 +- symposion/teams/models.py | 2 + symposion/utils/mail.py | 10 --- 18 files changed, 97 insertions(+), 94 deletions(-) diff --git a/symposion/proposals/actions.py b/symposion/proposals/actions.py index eeeb6289..f5dfd19f 100644 --- a/symposion/proposals/actions.py +++ b/symposion/proposals/actions.py @@ -25,13 +25,13 @@ def export_as_csv_action(description=None, fields=None, exclude=None, excludeset = set(exclude) field_names = field_names - excludeset response = HttpResponse(content_type="text/csv") - response["Content-Disposition"] = "attachment; filename=%s.csv" % unicode(opts).replace(".", "_") + response["Content-Disposition"] = "attachment; filename=%s.csv" % str(opts).replace(".", "_") writer = csv.writer(response) if header: writer.writerow(list(field_names)) for obj in queryset: writer.writerow( - [unicode(getattr(obj, field)).encode("utf-8", "replace") for field in field_names]) + [str(getattr(obj, field)).encode("utf-8", "replace") for field in field_names]) return response if description is None: description = _("Export selected objects as CSV file") diff --git a/symposion/proposals/models.py b/symposion/proposals/models.py index e4b9e563..bdf43ffd 100644 --- a/symposion/proposals/models.py +++ b/symposion/proposals/models.py @@ -205,6 +205,7 @@ class ProposalBase(models.Model): def __str__(self): return self.title + reversion.register(ProposalBase) diff --git a/symposion/reviews/admin.py b/symposion/reviews/admin.py index c5b0dd48..5144c2ca 100644 --- a/symposion/reviews/admin.py +++ b/symposion/reviews/admin.py @@ -17,4 +17,4 @@ admin.site.register( list_display=['proposal', 'status', 'score', 'vote_count', 'accepted'] ) -admin.site.register(ResultNotification) \ No newline at end of file +admin.site.register(ResultNotification) diff --git a/symposion/reviews/management/commands/assign_reviewers.py b/symposion/reviews/management/commands/assign_reviewers.py index 20594f4c..6364e0fc 100644 --- a/symposion/reviews/management/commands/assign_reviewers.py +++ b/symposion/reviews/management/commands/assign_reviewers.py @@ -8,5 +8,5 @@ class Command(BaseCommand): def handle(self, *args, **options): for proposal in ProposalBase.objects.filter(cancelled=0): - print "Creating assignments for %s" % (proposal.title,) + print("Creating assignments for %s" % proposal.title) ReviewAssignment.create_assignments(proposal) diff --git a/symposion/reviews/management/commands/create_review_permissions.py b/symposion/reviews/management/commands/create_review_permissions.py index 67dfc96b..2f21754a 100644 --- a/symposion/reviews/management/commands/create_review_permissions.py +++ b/symposion/reviews/management/commands/create_review_permissions.py @@ -22,4 +22,4 @@ class Command(BaseCommand): content_type__pk=ct.id, defaults={"name": "Can %s %s" % (action, ps), "content_type": ct} ) - print perm + print(perm) diff --git a/symposion/reviews/models.py b/symposion/reviews/models.py index d47feff5..63c190cf 100644 --- a/symposion/reviews/models.py +++ b/symposion/reviews/models.py @@ -47,6 +47,8 @@ class Votes(object): (MINUS_TWO, _("−2 — Serious issues and I will argue to reject this proposal.")), (ABSTAIN, _("Abstain - I do not want to review this proposal and I do not want to see it again.")), ] + + VOTES = Votes() @@ -395,4 +397,6 @@ def accepted_proposal(sender, instance=None, **kwargs): promote_proposal(instance.proposal) else: unpromote_proposal(instance.proposal) + + post_save.connect(accepted_proposal, sender=ProposalResult) diff --git a/symposion/reviews/views.py b/symposion/reviews/views.py index 3768225d..c45dbf4a 100644 --- a/symposion/reviews/views.py +++ b/symposion/reviews/views.py @@ -78,23 +78,23 @@ REVIEW_STATUS_FILTERS = { # proposals with at least VOTE_THRESHOLD reviews and at least one +2 and no -2s, sorted by # the 'score' POSITIVE: lambda qs: qs.filter(result__vote_count__gte=VOTE_THRESHOLD, result__plus_two__gt=0, - result__minus_two=0).order_by("-result__score"), + result__minus_two=0).order_by("-result__score"), # proposals with at least VOTE_THRESHOLD reviews and at least one -2 and no +2s, reverse # sorted by the 'score' NEGATIVE: lambda qs: qs.filter(result__vote_count__gte=VOTE_THRESHOLD, result__minus_two__gt=0, - result__plus_two=0).order_by("result__score"), + result__plus_two=0).order_by("result__score"), # proposals with at least VOTE_THRESHOLD reviews and neither a +2 or a -2, sorted by total # votes (lowest first) INDIFFERENT: lambda qs: qs.filter(result__vote_count__gte=VOTE_THRESHOLD, result__minus_two=0, - result__plus_two=0).order_by("result__vote_count"), + result__plus_two=0).order_by("result__vote_count"), # proposals with at least VOTE_THRESHOLD reviews and both a +2 and -2, sorted by total # votes (highest first) - CONTROVERSIAL: lambda qs: qs.filter(result__vote_count__gte=VOTE_THRESHOLD, - result__plus_two__gt=0, result__minus_two__gt=0) - .order_by("-result__vote_count"), + CONTROVERSIAL: lambda qs: qs.filter( + result__vote_count__gte=VOTE_THRESHOLD, result__plus_two__gt=0, + result__minus_two__gt=0).order_by("-result__vote_count"), # proposals with fewer than VOTE_THRESHOLD reviews - TOO_FEW: lambda qs: qs.filter(result__vote_count__lt=VOTE_THRESHOLD) - .order_by("result__vote_count"), + TOO_FEW: lambda qs: qs.filter( + result__vote_count__lt=VOTE_THRESHOLD).order_by("result__vote_count"), } @@ -153,7 +153,7 @@ def review_all_proposals_csv(request): # The fields from each proposal object to report in the csv fields = [ - "id", "proposal_type", "speaker_name","speaker_email", "title", + "id", "proposal_type", "speaker_name", "speaker_email", "title", "submitted", "other_speakers", "speaker_travel", "speaker_accommodation", "cancelled", "status", "score", "total_votes", "minus_two", "minus_one", "plus_one", "plus_two", @@ -189,11 +189,6 @@ def review_all_proposals_csv(request): csv_line = [getattr(proposal, field) for field in fields] - # Enusre that unicode items are handled properly. - for i, item in enumerate(csv_line): - if isinstance(item, unicode): - csv_line[i] = item.encode("utf8") - writer.writerow(csv_line) return response @@ -229,7 +224,7 @@ def review_random_proposal(request, section_slug): # Select a proposal with less than the median number of total votes proposals = proposals_generator(request, queryset, check_speaker=False) proposals = list(proposals) - proposals.sort(key = lambda proposal: proposal.total_votes) + proposals.sort(key=lambda proposal: proposal.total_votes) # The first half is the median or less. # The +1 means we round _up_. proposals = proposals[:(len(proposals) + 1) / 2] @@ -319,7 +314,7 @@ def review_admin(request, section_slug): yield user reviewers_sorted = list(reviewers()) - reviewers_sorted.sort(key= lambda reviewer: 0 - reviewer.total_votes) + reviewers_sorted.sort(key=lambda reviewer: 0 - reviewer.total_votes) ctx = { "section_slug": section_slug, diff --git a/symposion/schedule/helpers.py b/symposion/schedule/helpers.py index 68e72e39..2802f489 100644 --- a/symposion/schedule/helpers.py +++ b/symposion/schedule/helpers.py @@ -28,4 +28,4 @@ def create_slot(section_slug, date, kind, start, end, rooms): slot_room.slot = slot slot_room.room = room slot_room.save(force_insert=True) - print "created {} [start={}; end={}]".format(slot.kind.label, slot.start, slot.end) + print("created {} [start={}; end={}]".format(slot.kind.label, slot.start, slot.end)) diff --git a/symposion/schedule/views.py b/symposion/schedule/views.py index 6e85a2fd..f7375523 100644 --- a/symposion/schedule/views.py +++ b/symposion/schedule/views.py @@ -21,6 +21,7 @@ from symposion.schedule.models import Schedule, Day, Slot, Presentation, Session from symposion.schedule.timetable import TimeTable from symposion.conference.models import Conference + def fetch_schedule(slug): qs = Schedule.objects.all() @@ -265,11 +266,13 @@ def schedule_json(request): content_type="application/json" ) + class EventFeed(ICalFeed): product_id = '-//linux.conf.au/schedule//EN' timezone = settings.TIME_ZONE filename = 'conference.ics' + def description(self): return Conference.objects.all().first().title @@ -283,7 +286,7 @@ class EventFeed(ICalFeed): def item_title(self, item): if hasattr(item.content, 'proposal'): - title = item.content.title + title = item.content.title else: title = item.kind if item.kind else "Slot" return title @@ -305,18 +308,18 @@ class EventFeed(ICalFeed): def item_location(self, item): return ", ".join(room["name"] for room in item.rooms.values()) - def item_link(self, item): + def item_link(self, item) -> str: if hasattr(item.content, 'proposal'): - return 'http://%s%s' % ( - Site.objects.get_current().domain, - reverse('schedule_presentation_detail', args=[item.content.pk]) - ) + return ( + 'http://%s%s' % (Site.objects.get_current().domain, + reverse('schedule_presentation_detail', args=[item.content.pk]))) else: return 'http://%s' % Site.objects.get_current().domain - + def item_guid(self, item): return '%d@%s' % (item.pk, Site.objects.get_current().domain) + def session_list(request): sessions = Session.objects.all().order_by('pk') diff --git a/symposion/speakers/management/commands/export_speaker_data.py b/symposion/speakers/management/commands/export_speaker_data.py index 00e82588..08da5891 100644 --- a/symposion/speakers/management/commands/export_speaker_data.py +++ b/symposion/speakers/management/commands/export_speaker_data.py @@ -9,11 +9,12 @@ from symposion.speakers.models import Speaker class Command(BaseCommand): def handle(self, *args, **options): - csv_file = csv.writer(open(os.path.join(os.getcwd(), "speakers.csv"), "wb")) - csv_file.writerow(["Name", "Bio"]) + with open(os.path.join(os.getcwd(), "speakers.csv"), "w") as csv_file: + csv_writer = csv.writer(csv_file) + csv_writer.writerow(["Name", "Bio"]) - for speaker in Speaker.objects.all(): - csv_file.writerow([ - speaker.name.encode("utf-8"), - speaker.biography.encode("utf-8"), - ]) + for speaker in Speaker.objects.all(): + csv_writer.writerow([ + speaker.name, + speaker.biography, + ]) diff --git a/symposion/speakers/models.py b/symposion/speakers/models.py index ddbbceee..98d0d8a8 100644 --- a/symposion/speakers/models.py +++ b/symposion/speakers/models.py @@ -66,9 +66,9 @@ class Speaker(models.Model): accessibility = models.TextField( blank=True, help_text=_("Please describe any special accessibility requirements " - "that you may have. Edit using " - "Markdown."), + "that you may have. Edit using " + "Markdown."), verbose_name=_("Accessibility requirements")) accessibility_html = models.TextField(blank=True) travel_assistance = models.BooleanField( diff --git a/symposion/sponsorship/management/commands/export_sponsors_data.py b/symposion/sponsorship/management/commands/export_sponsors_data.py index 61172d44..a20e3f3b 100644 --- a/symposion/sponsorship/management/commands/export_sponsors_data.py +++ b/symposion/sponsorship/management/commands/export_sponsors_data.py @@ -30,48 +30,48 @@ class Command(BaseCommand): except: pass - csv_file = csv.writer( - open(os.path.join(os.getcwd(), "build", "sponsors.csv"), "wb") - ) - csv_file.writerow(["Name", "URL", "Level", "Description"]) + with open(os.path.join(os.getcwd(), "build", "sponsors.csv"), "w") as csv_file: + csv_writer = csv.writer(csv_file) + csv_writer.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 + 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 + data = { + "name": sponsor.name, + "url": sponsor.external_url, + "level": sponsor.level.name, + "description": "", + } - 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) + 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 - csv_file.writerow([ - data["name"].encode("utf-8"), - data["url"].encode("utf-8"), - data["level"].encode("utf-8"), - data["description"].encode("utf-8") - ]) + 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) - zipdir( - os.path.join(os.getcwd(), "build"), - os.path.join(os.getcwd(), "sponsors.zip")) + csv_writer.writerow([ + data["name"], + data["url"], + data["level"], + data["description"] + ]) + + zipdir( + os.path.join(os.getcwd(), "build"), + os.path.join(os.getcwd(), "sponsors.zip")) diff --git a/symposion/sponsorship/management/commands/reset_sponsor_benefits.py b/symposion/sponsorship/management/commands/reset_sponsor_benefits.py index 3c17efab..efc7edba 100644 --- a/symposion/sponsorship/management/commands/reset_sponsor_benefits.py +++ b/symposion/sponsorship/management/commands/reset_sponsor_benefits.py @@ -19,7 +19,7 @@ class Command(BaseCommand): sponsor=sponsor, benefit=benefit_level.benefit) if created: - print "created", sponsor_benefit, "for", sponsor + print("created %s for %s" % (sponsor_benefit, sponsor)) # and set to default limits for this level. sponsor_benefit.max_words = benefit_level.max_words diff --git a/symposion/sponsorship/models.py b/symposion/sponsorship/models.py index f28bc57e..f93bf8c2 100644 --- a/symposion/sponsorship/models.py +++ b/symposion/sponsorship/models.py @@ -203,12 +203,16 @@ class Sponsor(models.Model): def _store_initial_level(sender, instance, **kwargs): if instance: instance._initial_level_id = instance.level_id + + post_init.connect(_store_initial_level, sender=Sponsor) def _check_level_change(sender, instance, created, **kwargs): if instance and (created or instance.level_id != instance._initial_level_id): instance.reset_benefits() + + post_save.connect(_check_level_change, sender=Sponsor) @@ -330,4 +334,6 @@ def _denorm_weblogo(sender, instance, created, **kwargs): sponsor = instance.sponsor sponsor.sponsor_logo = instance sponsor.save() + + post_save.connect(_denorm_weblogo, sender=SponsorBenefit) diff --git a/symposion/teams/admin.py b/symposion/teams/admin.py index 1a27c7ee..a8c3bec7 100644 --- a/symposion/teams/admin.py +++ b/symposion/teams/admin.py @@ -13,4 +13,5 @@ class MembershipAdmin(VersionAdmin): list_filter = ["team"] search_fields = ["user__username"] + admin.site.register(Membership, MembershipAdmin) diff --git a/symposion/teams/backends.py b/symposion/teams/backends.py index 0effdc2d..a882bf5d 100644 --- a/symposion/teams/backends.py +++ b/symposion/teams/backends.py @@ -16,16 +16,16 @@ class TeamPermissionsBackend(object): if user_obj.is_anonymous() or obj is not None: return set() if not hasattr(user_obj, "_team_perm_cache"): - # Member permissions + # Member permissions memberships = Team.objects.filter( - Q(memberships__user=user_obj), + Q(memberships__user=user_obj), Q(memberships__state="member"), ) perms = memberships.values_list( "permissions__content_type__app_label", "permissions__codename" ).order_by() - permissions = ["%s.%s" % (ct, name) for ct, name in perms] + permissions = ["%s.%s" % (ct, name) for ct, name in perms] # Manager permissions memberships = Team.objects.filter( Q(memberships__user=user_obj), @@ -35,7 +35,7 @@ class TeamPermissionsBackend(object): "manager_permissions__content_type__app_label", "manager_permissions__codename" ).order_by() - permissions += ["%s.%s" % (ct, name) for ct, name in perms] + permissions += ["%s.%s" % (ct, name) for ct, name in perms] user_obj._team_perm_cache = set(permissions) return user_obj._team_perm_cache diff --git a/symposion/teams/models.py b/symposion/teams/models.py index 774dfbd8..f6ab7253 100644 --- a/symposion/teams/models.py +++ b/symposion/teams/models.py @@ -69,6 +69,7 @@ class Team(models.Model): verbose_name = _('Team') verbose_name_plural = _('Teams') + MEMBERSHIP_STATE_CHOICES = [ ("applied", _("applied")), ("invited", _("invited")), @@ -93,4 +94,5 @@ class Membership(models.Model): verbose_name = _("Membership") verbose_name_plural = _("Memberships") + reversion.register(Membership) diff --git a/symposion/utils/mail.py b/symposion/utils/mail.py index ed90eeb1..91e37891 100644 --- a/symposion/utils/mail.py +++ b/symposion/utils/mail.py @@ -53,16 +53,6 @@ def __send_email__(template_prefix, to, kind, **kwargs): from_email = settings.DEFAULT_FROM_EMAIL - try: - bcc_email = settings.ENVELOPE_BCC_LIST - except AttributeError: - bcc_email = None - - try: - bcc_email = settings.ENVELOPE_BCC_LIST - except AttributeError: - bcc_email = None - email = EmailMultiAlternatives(subject, message_plaintext, from_email, to) email.attach_alternative(message_html, "text/html") email.send() From 0652471164ecad89ce35c06a025669631141a5ec Mon Sep 17 00:00:00 2001 From: Sachi King Date: Fri, 21 Apr 2017 10:34:48 +1000 Subject: [PATCH 743/751] Sanitize user input on markdown fields This is an XSS vulnribilitiy. This also blocks a number of MD attributes that a user might attempt to use. The following are the allowed attributes. ['a', 'abbr', 'acronym', 'b', 'blockquote', 'code', 'em', 'i', 'li', 'ol', 'p', 'pre', 'strong', 'ul'] I belive this to be acceptable, as honeslty, a speaker using H1 is going to stomp all over the page and make it harder for the reviewer to parse. UX wise, it's less than great. A user can do # title and be left with

        in the sanitized output. --- requirements/base.txt | 2 +- symposion/markdown_parser.py | 17 +++++++---------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/requirements/base.txt b/requirements/base.txt index 5a955d0d..830c9b29 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -6,7 +6,7 @@ django-sitetree>=1.7.0 django-taggit==0.18.0 django-timezone-field>=2.0 easy-thumbnails==2.3 -html5lib==0.9999999 +bleach markdown==2.6.5 pytz==2015.7 django-ical==1.4 diff --git a/symposion/markdown_parser.py b/symposion/markdown_parser.py index b3eaa53c..d92a5020 100644 --- a/symposion/markdown_parser.py +++ b/symposion/markdown_parser.py @@ -1,17 +1,14 @@ from __future__ import unicode_literals +import bleach import markdown +tags = bleach.sanitizer.ALLOWED_TAGS[:] +tags.extend(['p', 'pre']) + + def parse(text): - - # First run through the Markdown parser - text = markdown.markdown(text, extensions=["extra"], safe_mode=False) - - # Sanitize using html5lib - # bits = [] - # parser = html5parser.HTMLParser(tokenizer=sanitizer.HTMLSanitizer) - # for token in parser.parseFragment(text).childNodes: - # bits.append(token.toxml()) - # return "".join(bits) + md = markdown.markdown(text, extensions=['extra']) + text = bleach.clean(md, tags=tags) return text From 21b2a01a84132205aaaaa1b35fd8789afe4a133b Mon Sep 17 00:00:00 2001 From: Sachi King Date: Sat, 22 Apr 2017 10:06:04 +1000 Subject: [PATCH 744/751] Py2 compatability has been broke elsewhere drop it This is all noop in Py3, and Py2 is broke now in various places. Dropping Py2 code as it will not be a thing going forward. Django 2 is the next release, Py2 support will be dropped, as such, dropping this is forward looking. --- setup.py | 2 +- symposion/conference/apps.py | 1 - symposion/conference/migrations/0001_initial.py | 2 -- symposion/conference/models.py | 4 ---- symposion/markdown_parser.py | 2 -- symposion/proposals/actions.py | 1 - symposion/proposals/apps.py | 1 - symposion/proposals/forms.py | 1 - symposion/proposals/migrations/0001_initial.py | 2 -- symposion/proposals/models.py | 6 ------ symposion/proposals/views.py | 1 - symposion/reviews/apps.py | 1 - symposion/reviews/forms.py | 1 - symposion/reviews/migrations/0001_initial.py | 2 -- symposion/reviews/models.py | 1 - symposion/reviews/utils.py | 3 --- symposion/schedule/admin.py | 1 - symposion/schedule/apps.py | 1 - symposion/schedule/forms.py | 1 - symposion/schedule/helpers.py | 1 - symposion/schedule/migrations/0001_initial.py | 2 -- .../migrations/0002_presentation_unpublish.py | 2 -- .../schedule/migrations/0003_auto_20161113_1530.py | 2 -- .../schedule/migrations/0003_slot_exclusive.py | 2 -- symposion/schedule/migrations/0004_merge.py | 2 -- .../schedule/migrations/0005_auto_20161210_1736.py | 2 -- symposion/schedule/migrations/0006_room_track.py | 2 -- .../schedule/migrations/0007_auto_20161224_1709.py | 2 -- symposion/schedule/models.py | 13 ------------- symposion/schedule/timetable.py | 1 - symposion/schedule/urls.py | 1 - symposion/schedule/views.py | 1 - symposion/speakers/admin.py | 1 - symposion/speakers/apps.py | 1 - symposion/speakers/forms.py | 1 - symposion/speakers/migrations/0001_initial.py | 2 -- .../speakers/migrations/0002_auto_20161230_1900.py | 2 -- symposion/speakers/models.py | 4 ---- symposion/speakers/urls.py | 1 - symposion/speakers/views.py | 1 - symposion/sponsorship/admin.py | 2 -- symposion/sponsorship/apps.py | 1 - symposion/sponsorship/forms.py | 1 - symposion/sponsorship/migrations/0001_initial.py | 2 -- symposion/sponsorship/models.py | 8 -------- symposion/sponsorship/views.py | 7 +------ symposion/teams/forms.py | 1 - symposion/teams/migrations/0001_initial.py | 2 -- symposion/teams/models.py | 4 ---- symposion/teams/views.py | 1 - symposion/views.py | 2 -- 51 files changed, 2 insertions(+), 109 deletions(-) diff --git a/setup.py b/setup.py index bdf75e6f..e19058cb 100644 --- a/setup.py +++ b/setup.py @@ -26,7 +26,7 @@ setup( include_package_data=True, classifiers=( "Development Status :: 4 - Beta", - "Programming Language :: Python", + "Programming Language :: Python :: 3 :: Only", "Framework :: Django", "Intended Audience :: Developers", "Natural Language :: English", diff --git a/symposion/conference/apps.py b/symposion/conference/apps.py index 45e19ffb..7a330359 100644 --- a/symposion/conference/apps.py +++ b/symposion/conference/apps.py @@ -1,4 +1,3 @@ -from __future__ import unicode_literals from django.apps import AppConfig from django.utils.translation import ugettext_lazy as _ diff --git a/symposion/conference/migrations/0001_initial.py b/symposion/conference/migrations/0001_initial.py index 888dcf63..c9922906 100644 --- a/symposion/conference/migrations/0001_initial.py +++ b/symposion/conference/migrations/0001_initial.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.7 on 2016-09-17 03:34 -from __future__ import unicode_literals - from django.db import migrations, models import django.db.models.deletion import timezone_field.fields diff --git a/symposion/conference/models.py b/symposion/conference/models.py index ae6c1bea..14e5ad3f 100644 --- a/symposion/conference/models.py +++ b/symposion/conference/models.py @@ -1,7 +1,5 @@ -from __future__ import unicode_literals from django.db import models from django.utils.translation import ugettext_lazy as _ -from django.utils.encoding import python_2_unicode_compatible from timezone_field import TimeZoneField @@ -9,7 +7,6 @@ from timezone_field import TimeZoneField CONFERENCE_CACHE = {} -@python_2_unicode_compatible class Conference(models.Model): """ the full conference for a specific year, e.g. US PyCon 2012. @@ -45,7 +42,6 @@ class Conference(models.Model): verbose_name_plural = _("conferences") -@python_2_unicode_compatible class Section(models.Model): """ a section of the conference such as "Tutorials", "Workshops", diff --git a/symposion/markdown_parser.py b/symposion/markdown_parser.py index d92a5020..f5495336 100644 --- a/symposion/markdown_parser.py +++ b/symposion/markdown_parser.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import bleach import markdown diff --git a/symposion/proposals/actions.py b/symposion/proposals/actions.py index f5dfd19f..7317f88e 100644 --- a/symposion/proposals/actions.py +++ b/symposion/proposals/actions.py @@ -1,4 +1,3 @@ -from __future__ import unicode_literals import csv from django.http import HttpResponse diff --git a/symposion/proposals/apps.py b/symposion/proposals/apps.py index 613471ce..c35c024c 100644 --- a/symposion/proposals/apps.py +++ b/symposion/proposals/apps.py @@ -1,4 +1,3 @@ -from __future__ import unicode_literals from django.apps import AppConfig from django.utils.translation import ugettext_lazy as _ diff --git a/symposion/proposals/forms.py b/symposion/proposals/forms.py index d42689cf..4dd40327 100644 --- a/symposion/proposals/forms.py +++ b/symposion/proposals/forms.py @@ -1,4 +1,3 @@ -from __future__ import unicode_literals from django import forms from django.db.models import Q from django.utils.translation import ugettext_lazy as _ diff --git a/symposion/proposals/migrations/0001_initial.py b/symposion/proposals/migrations/0001_initial.py index e66fc130..318becc2 100644 --- a/symposion/proposals/migrations/0001_initial.py +++ b/symposion/proposals/migrations/0001_initial.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.7 on 2016-09-17 03:35 -from __future__ import unicode_literals - from django.conf import settings from django.db import migrations, models import django.db.models.deletion diff --git a/symposion/proposals/models.py b/symposion/proposals/models.py index bdf43ffd..2b9d43a1 100644 --- a/symposion/proposals/models.py +++ b/symposion/proposals/models.py @@ -1,11 +1,9 @@ -from __future__ import unicode_literals import os import uuid from django.core.urlresolvers import reverse from django.db import models from django.db.models import Q -from django.utils.encoding import python_2_unicode_compatible from django.utils.translation import ugettext_lazy as _ from django.utils.timezone import now @@ -21,7 +19,6 @@ from symposion.conference.models import Section from symposion.speakers.models import Speaker -@python_2_unicode_compatible class ProposalSection(models.Model): """ configuration of proposal submissions for a specific Section. @@ -60,7 +57,6 @@ class ProposalSection(models.Model): return self.section.name -@python_2_unicode_compatible class ProposalKind(models.Model): """ e.g. talk vs panel vs tutorial vs poster @@ -78,7 +74,6 @@ class ProposalKind(models.Model): return self.name -@python_2_unicode_compatible class ProposalBase(models.Model): objects = InheritanceManager() @@ -209,7 +204,6 @@ class ProposalBase(models.Model): reversion.register(ProposalBase) -@python_2_unicode_compatible class AdditionalSpeaker(models.Model): SPEAKING_STATUS_PENDING = 1 diff --git a/symposion/proposals/views.py b/symposion/proposals/views.py index 7d5983b9..07e735a0 100644 --- a/symposion/proposals/views.py +++ b/symposion/proposals/views.py @@ -1,4 +1,3 @@ -from __future__ import unicode_literals import hashlib import random import sys diff --git a/symposion/reviews/apps.py b/symposion/reviews/apps.py index 4973fa43..80fab5c3 100644 --- a/symposion/reviews/apps.py +++ b/symposion/reviews/apps.py @@ -1,4 +1,3 @@ -from __future__ import unicode_literals from django.apps import AppConfig from django.utils.translation import ugettext_lazy as _ diff --git a/symposion/reviews/forms.py b/symposion/reviews/forms.py index 6e8c5701..03c4b6ef 100644 --- a/symposion/reviews/forms.py +++ b/symposion/reviews/forms.py @@ -1,4 +1,3 @@ -from __future__ import unicode_literals from django import forms from django.utils.translation import ugettext_lazy as _ diff --git a/symposion/reviews/migrations/0001_initial.py b/symposion/reviews/migrations/0001_initial.py index dbbece21..39af922a 100644 --- a/symposion/reviews/migrations/0001_initial.py +++ b/symposion/reviews/migrations/0001_initial.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.7 on 2016-09-17 03:35 -from __future__ import unicode_literals - import datetime from decimal import Decimal from django.conf import settings diff --git a/symposion/reviews/models.py b/symposion/reviews/models.py index 63c190cf..a92bf483 100644 --- a/symposion/reviews/models.py +++ b/symposion/reviews/models.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals from datetime import datetime from decimal import Decimal diff --git a/symposion/reviews/utils.py b/symposion/reviews/utils.py index 1d45e95a..80e87e49 100644 --- a/symposion/reviews/utils.py +++ b/symposion/reviews/utils.py @@ -1,6 +1,3 @@ -from __future__ import unicode_literals - - def has_permission(user, proposal, speaker=False, reviewer=False): """ Returns whether or not ther user has permission to review this proposal, diff --git a/symposion/schedule/admin.py b/symposion/schedule/admin.py index 7cf59ecb..148b611a 100644 --- a/symposion/schedule/admin.py +++ b/symposion/schedule/admin.py @@ -1,4 +1,3 @@ -from __future__ import unicode_literals from django.contrib import admin from symposion.schedule.models import Schedule, Day, Room, SlotKind, Slot, SlotRoom, Presentation, Session, SessionRole, Track diff --git a/symposion/schedule/apps.py b/symposion/schedule/apps.py index d1c0ecb0..03773bdc 100644 --- a/symposion/schedule/apps.py +++ b/symposion/schedule/apps.py @@ -1,4 +1,3 @@ -from __future__ import unicode_literals from django.apps import AppConfig diff --git a/symposion/schedule/forms.py b/symposion/schedule/forms.py index 41695259..60fe8c55 100644 --- a/symposion/schedule/forms.py +++ b/symposion/schedule/forms.py @@ -1,4 +1,3 @@ -from __future__ import unicode_literals import csv import time diff --git a/symposion/schedule/helpers.py b/symposion/schedule/helpers.py index 2802f489..02e964a7 100644 --- a/symposion/schedule/helpers.py +++ b/symposion/schedule/helpers.py @@ -2,7 +2,6 @@ This file contains functions that are useful to humans at the shell for manipulating the database in more natural ways. """ -from __future__ import unicode_literals from django.db import transaction from .models import Schedule, Day, Room, Slot, SlotKind, SlotRoom diff --git a/symposion/schedule/migrations/0001_initial.py b/symposion/schedule/migrations/0001_initial.py index 9750d3d3..fb2c3629 100644 --- a/symposion/schedule/migrations/0001_initial.py +++ b/symposion/schedule/migrations/0001_initial.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.7 on 2016-09-17 03:35 -from __future__ import unicode_literals - import datetime from django.conf import settings from django.db import migrations, models diff --git a/symposion/schedule/migrations/0002_presentation_unpublish.py b/symposion/schedule/migrations/0002_presentation_unpublish.py index 8e2a7cab..fdf576b7 100644 --- a/symposion/schedule/migrations/0002_presentation_unpublish.py +++ b/symposion/schedule/migrations/0002_presentation_unpublish.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.7 on 2016-09-18 00:43 -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/symposion/schedule/migrations/0003_auto_20161113_1530.py b/symposion/schedule/migrations/0003_auto_20161113_1530.py index 61f02b21..9277148f 100644 --- a/symposion/schedule/migrations/0003_auto_20161113_1530.py +++ b/symposion/schedule/migrations/0003_auto_20161113_1530.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.7 on 2016-11-13 04:30 -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/symposion/schedule/migrations/0003_slot_exclusive.py b/symposion/schedule/migrations/0003_slot_exclusive.py index 9fc0d847..359376d3 100644 --- a/symposion/schedule/migrations/0003_slot_exclusive.py +++ b/symposion/schedule/migrations/0003_slot_exclusive.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.7 on 2016-12-09 20:53 -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/symposion/schedule/migrations/0004_merge.py b/symposion/schedule/migrations/0004_merge.py index b89bc7cd..36096261 100644 --- a/symposion/schedule/migrations/0004_merge.py +++ b/symposion/schedule/migrations/0004_merge.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.7 on 2016-12-10 06:05 -from __future__ import unicode_literals - from django.db import migrations diff --git a/symposion/schedule/migrations/0005_auto_20161210_1736.py b/symposion/schedule/migrations/0005_auto_20161210_1736.py index e4b5e49d..f05a41d0 100644 --- a/symposion/schedule/migrations/0005_auto_20161210_1736.py +++ b/symposion/schedule/migrations/0005_auto_20161210_1736.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.7 on 2016-12-10 06:36 -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/symposion/schedule/migrations/0006_room_track.py b/symposion/schedule/migrations/0006_room_track.py index 3e88890f..67a70b08 100644 --- a/symposion/schedule/migrations/0006_room_track.py +++ b/symposion/schedule/migrations/0006_room_track.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.7 on 2016-12-24 00:10 -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/symposion/schedule/migrations/0007_auto_20161224_1709.py b/symposion/schedule/migrations/0007_auto_20161224_1709.py index 2ddbfafb..d6eeb236 100644 --- a/symposion/schedule/migrations/0007_auto_20161224_1709.py +++ b/symposion/schedule/migrations/0007_auto_20161224_1709.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.7 on 2016-12-24 06:09 -from __future__ import unicode_literals - from django.db import migrations, models import django.db.models.deletion diff --git a/symposion/schedule/models.py b/symposion/schedule/models.py index c4807df6..0e2307b2 100644 --- a/symposion/schedule/models.py +++ b/symposion/schedule/models.py @@ -1,11 +1,8 @@ -from __future__ import unicode_literals - import datetime from django.core.exceptions import ObjectDoesNotExist from django.contrib.auth.models import User from django.db import models -from django.utils.encoding import python_2_unicode_compatible from django.utils.translation import ugettext_lazy as _ from symposion.markdown_parser import parse @@ -14,7 +11,6 @@ from symposion.conference.models import Section from symposion.speakers.models import Speaker -@python_2_unicode_compatible class Schedule(models.Model): section = models.OneToOneField(Section, verbose_name=_("Section")) @@ -30,7 +26,6 @@ class Schedule(models.Model): verbose_name_plural = _('Schedules') -@python_2_unicode_compatible class Day(models.Model): schedule = models.ForeignKey(Schedule, verbose_name=_("Schedule")) @@ -46,7 +41,6 @@ class Day(models.Model): verbose_name_plural = _("dates") -@python_2_unicode_compatible class Room(models.Model): schedule = models.ForeignKey(Schedule, verbose_name=_("Schedule")) @@ -61,7 +55,6 @@ class Room(models.Model): verbose_name_plural = _("Rooms") -@python_2_unicode_compatible class Track(models.Model): name = models.CharField(max_length=80, verbose_name=_("Track")) room = models.ForeignKey(Room) @@ -76,7 +69,6 @@ class Track(models.Model): verbose_name_plural = _("Tracks") -@python_2_unicode_compatible class SlotKind(models.Model): """ A slot kind represents what kind a slot is. For example, a slot can be a @@ -94,7 +86,6 @@ class SlotKind(models.Model): verbose_name_plural = _("Slot kinds") -@python_2_unicode_compatible class Slot(models.Model): name = models.CharField(max_length=512, editable=False) @@ -181,7 +172,6 @@ class Slot(models.Model): verbose_name_plural = _("slots") -@python_2_unicode_compatible class SlotRoom(models.Model): """ Links a slot with a room. @@ -200,7 +190,6 @@ class SlotRoom(models.Model): verbose_name_plural = _("Slot rooms") -@python_2_unicode_compatible class Presentation(models.Model): slot = models.OneToOneField(Slot, null=True, blank=True, related_name="content_ptr", verbose_name=_("Slot")) @@ -247,7 +236,6 @@ class Presentation(models.Model): verbose_name_plural = _("presentations") -@python_2_unicode_compatible class Session(models.Model): day = models.ForeignKey(Day, related_name="sessions", verbose_name=_("Day")) @@ -292,7 +280,6 @@ class Session(models.Model): verbose_name_plural = _("Sessions") -@python_2_unicode_compatible class SessionRole(models.Model): SESSION_ROLE_CHAIR = 1 diff --git a/symposion/schedule/timetable.py b/symposion/schedule/timetable.py index d5f422b0..7ce03460 100644 --- a/symposion/schedule/timetable.py +++ b/symposion/schedule/timetable.py @@ -1,4 +1,3 @@ -from __future__ import unicode_literals import itertools from django.db.models import Count, Min diff --git a/symposion/schedule/urls.py b/symposion/schedule/urls.py index f6f3f900..1b8b5eee 100644 --- a/symposion/schedule/urls.py +++ b/symposion/schedule/urls.py @@ -1,4 +1,3 @@ -from __future__ import unicode_literals from django.conf.urls import url from .views import ( diff --git a/symposion/schedule/views.py b/symposion/schedule/views.py index f7375523..6983bdbb 100644 --- a/symposion/schedule/views.py +++ b/symposion/schedule/views.py @@ -1,4 +1,3 @@ -from __future__ import unicode_literals import json import pytz diff --git a/symposion/speakers/admin.py b/symposion/speakers/admin.py index 95d4b28a..6331ab7b 100644 --- a/symposion/speakers/admin.py +++ b/symposion/speakers/admin.py @@ -1,4 +1,3 @@ -from __future__ import unicode_literals from django.contrib import admin from symposion.speakers.models import Speaker diff --git a/symposion/speakers/apps.py b/symposion/speakers/apps.py index bd6024ba..c772a12a 100644 --- a/symposion/speakers/apps.py +++ b/symposion/speakers/apps.py @@ -1,4 +1,3 @@ -from __future__ import unicode_literals from django.apps import AppConfig from django.utils.translation import ugettext_lazy as _ diff --git a/symposion/speakers/forms.py b/symposion/speakers/forms.py index 9c82113a..5f2aece9 100644 --- a/symposion/speakers/forms.py +++ b/symposion/speakers/forms.py @@ -1,4 +1,3 @@ -from __future__ import unicode_literals from django import forms from symposion.speakers.models import Speaker diff --git a/symposion/speakers/migrations/0001_initial.py b/symposion/speakers/migrations/0001_initial.py index aacf963f..e6dc8deb 100644 --- a/symposion/speakers/migrations/0001_initial.py +++ b/symposion/speakers/migrations/0001_initial.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.7 on 2016-09-17 03:35 -from __future__ import unicode_literals - import datetime from django.conf import settings from django.db import migrations, models diff --git a/symposion/speakers/migrations/0002_auto_20161230_1900.py b/symposion/speakers/migrations/0002_auto_20161230_1900.py index 68444662..47eb7a77 100644 --- a/symposion/speakers/migrations/0002_auto_20161230_1900.py +++ b/symposion/speakers/migrations/0002_auto_20161230_1900.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.7 on 2016-12-30 08:09 -from __future__ import unicode_literals - from django.db import migrations diff --git a/symposion/speakers/models.py b/symposion/speakers/models.py index 98d0d8a8..5ed69866 100644 --- a/symposion/speakers/models.py +++ b/symposion/speakers/models.py @@ -1,10 +1,7 @@ -from __future__ import unicode_literals - import datetime from django.core.urlresolvers import reverse from django.db import models -from django.utils.encoding import python_2_unicode_compatible from django.utils.translation import ugettext_lazy as _ from django.contrib.auth.models import User @@ -12,7 +9,6 @@ from django.contrib.auth.models import User from symposion.markdown_parser import parse -@python_2_unicode_compatible class Speaker(models.Model): SESSION_COUNT_CHOICES = [ diff --git a/symposion/speakers/urls.py b/symposion/speakers/urls.py index dd1a7bd6..a3cf7e2c 100644 --- a/symposion/speakers/urls.py +++ b/symposion/speakers/urls.py @@ -1,4 +1,3 @@ -from __future__ import unicode_literals from django.conf.urls import url from .views import ( diff --git a/symposion/speakers/views.py b/symposion/speakers/views.py index 9ed32ec4..a0069c10 100644 --- a/symposion/speakers/views.py +++ b/symposion/speakers/views.py @@ -1,4 +1,3 @@ -from __future__ import unicode_literals from django.core.exceptions import ObjectDoesNotExist from django.http import Http404 from django.shortcuts import render, redirect, get_object_or_404 diff --git a/symposion/sponsorship/admin.py b/symposion/sponsorship/admin.py index 15b09439..0a9272ac 100644 --- a/symposion/sponsorship/admin.py +++ b/symposion/sponsorship/admin.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.utils.html import escape from django.utils.safestring import mark_safe from django.utils.translation import ugettext_lazy as _ diff --git a/symposion/sponsorship/apps.py b/symposion/sponsorship/apps.py index 92ee49cd..3d2d5d62 100644 --- a/symposion/sponsorship/apps.py +++ b/symposion/sponsorship/apps.py @@ -1,4 +1,3 @@ -from __future__ import unicode_literals from django.apps import AppConfig from django.utils.translation import ugettext_lazy as _ diff --git a/symposion/sponsorship/forms.py b/symposion/sponsorship/forms.py index 1ca959a5..100ea8db 100644 --- a/symposion/sponsorship/forms.py +++ b/symposion/sponsorship/forms.py @@ -1,4 +1,3 @@ -from __future__ import unicode_literals from django import forms from django.forms.models import inlineformset_factory, BaseInlineFormSet diff --git a/symposion/sponsorship/migrations/0001_initial.py b/symposion/sponsorship/migrations/0001_initial.py index 29a8cc05..1cbbc389 100644 --- a/symposion/sponsorship/migrations/0001_initial.py +++ b/symposion/sponsorship/migrations/0001_initial.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.7 on 2016-09-17 03:35 -from __future__ import unicode_literals - import datetime from django.conf import settings from django.db import migrations, models diff --git a/symposion/sponsorship/models.py b/symposion/sponsorship/models.py index f93bf8c2..cd256d11 100644 --- a/symposion/sponsorship/models.py +++ b/symposion/sponsorship/models.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import datetime from django.conf import settings @@ -7,7 +5,6 @@ from django.core.exceptions import ValidationError from django.core.urlresolvers import reverse from django.db import models from django.db.models.signals import post_init, post_save -from django.utils.encoding import python_2_unicode_compatible from django.utils.translation import ugettext_lazy as _ from django.contrib.auth.models import User @@ -45,7 +42,6 @@ BENEFITS = [ ] -@python_2_unicode_compatible class SponsorLevel(models.Model): conference = models.ForeignKey(Conference, verbose_name=_("Conference")) @@ -66,7 +62,6 @@ class SponsorLevel(models.Model): return self.sponsor_set.filter(active=True).order_by("added") -@python_2_unicode_compatible class Sponsor(models.Model): applicant = models.ForeignKey(User, related_name="sponsorships", verbose_name=_("Applicant"), @@ -232,7 +227,6 @@ CONTENT_TYPE_CHOICES = [ ] -@python_2_unicode_compatible class Benefit(models.Model): name = models.CharField(_("Name"), max_length=100) @@ -246,7 +240,6 @@ class Benefit(models.Model): return self.name -@python_2_unicode_compatible class BenefitLevel(models.Model): benefit = models.ForeignKey(Benefit, related_name="benefit_levels", verbose_name=_("Benefit")) @@ -265,7 +258,6 @@ class BenefitLevel(models.Model): return "%s - %s" % (self.level, self.benefit) -@python_2_unicode_compatible class SponsorBenefit(models.Model): sponsor = models.ForeignKey(Sponsor, related_name="sponsor_benefits", verbose_name=_("Sponsor")) diff --git a/symposion/sponsorship/views.py b/symposion/sponsorship/views.py index d3bd47fe..bc5acf8f 100644 --- a/symposion/sponsorship/views.py +++ b/symposion/sponsorship/views.py @@ -1,9 +1,4 @@ -from __future__ import unicode_literals -try: - from io import StringIO -except: - # Python 2 - from cStringIO import StringIO +from io import StringIO import itertools import logging import os diff --git a/symposion/teams/forms.py b/symposion/teams/forms.py index aea42279..5ba8ccae 100644 --- a/symposion/teams/forms.py +++ b/symposion/teams/forms.py @@ -1,4 +1,3 @@ -from __future__ import unicode_literals from django import forms from django.utils.html import escape diff --git a/symposion/teams/migrations/0001_initial.py b/symposion/teams/migrations/0001_initial.py index 3bdd4120..513093db 100644 --- a/symposion/teams/migrations/0001_initial.py +++ b/symposion/teams/migrations/0001_initial.py @@ -1,6 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import models, migrations import datetime from django.conf import settings diff --git a/symposion/teams/models.py b/symposion/teams/models.py index f6ab7253..ca197272 100644 --- a/symposion/teams/models.py +++ b/symposion/teams/models.py @@ -1,9 +1,6 @@ -from __future__ import unicode_literals - import datetime from django.db import models -from django.utils.encoding import python_2_unicode_compatible from django.utils.translation import ugettext_lazy as _ from django.contrib.auth.models import Permission, User @@ -18,7 +15,6 @@ TEAM_ACCESS_CHOICES = [ ] -@python_2_unicode_compatible class Team(models.Model): slug = models.SlugField(unique=True, verbose_name=_("Slug")) diff --git a/symposion/teams/views.py b/symposion/teams/views.py index be11be7f..9c85beeb 100644 --- a/symposion/teams/views.py +++ b/symposion/teams/views.py @@ -1,4 +1,3 @@ -from __future__ import unicode_literals from django.http import Http404, HttpResponseNotAllowed from django.shortcuts import render, redirect, get_object_or_404 diff --git a/symposion/views.py b/symposion/views.py index a03545eb..a94853b9 100644 --- a/symposion/views.py +++ b/symposion/views.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.contrib.auth.decorators import login_required from django.shortcuts import render, redirect From 997380152e45a6145e7e4d86f9583a17cff5b400 Mon Sep 17 00:00:00 2001 From: Sachi King Date: Sun, 23 Apr 2017 11:27:59 +1000 Subject: [PATCH 745/751] Silence MARKDOWN debug --- symposion/markdown_parser.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/symposion/markdown_parser.py b/symposion/markdown_parser.py index f5495336..f123be47 100644 --- a/symposion/markdown_parser.py +++ b/symposion/markdown_parser.py @@ -1,7 +1,12 @@ +import logging + import bleach import markdown +logger = logging.getLogger('MARKDOWN') +logger.setLevel(logging.INFO) + tags = bleach.sanitizer.ALLOWED_TAGS[:] tags.extend(['p', 'pre']) From fb5eaea880cbb5adb8034004a761bf7650bf7ccc Mon Sep 17 00:00:00 2001 From: Sachi King Date: Mon, 24 Apr 2017 22:50:48 +1000 Subject: [PATCH 746/751] Add a CSS class on required fields labels This makes it possible to add a ' *' required notifier to labels without needing a bunch of custom form code in templates. --- symposion/proposals/forms.py | 4 ++++ symposion/reviews/forms.py | 12 ++++++++++++ symposion/schedule/forms.py | 5 +++++ symposion/speakers/forms.py | 2 ++ symposion/sponsorship/forms.py | 9 +++++++++ symposion/teams/forms.py | 2 ++ 6 files changed, 34 insertions(+) diff --git a/symposion/proposals/forms.py b/symposion/proposals/forms.py index 4dd40327..ea0699c8 100644 --- a/symposion/proposals/forms.py +++ b/symposion/proposals/forms.py @@ -10,6 +10,8 @@ from symposion.proposals.models import SupportingDocument class AddSpeakerForm(forms.Form): + required_css_class = 'label-required' + email = forms.EmailField( label=_("Email address of new speaker (use their email address, not yours)") ) @@ -33,6 +35,8 @@ class AddSpeakerForm(forms.Form): class SupportingDocumentCreateForm(forms.ModelForm): + required_css_class = 'label-required' + class Meta: model = SupportingDocument fields = [ diff --git a/symposion/reviews/forms.py b/symposion/reviews/forms.py index 03c4b6ef..426bab88 100644 --- a/symposion/reviews/forms.py +++ b/symposion/reviews/forms.py @@ -5,6 +5,9 @@ from symposion.reviews.models import Review, Comment, ProposalMessage, VOTES class ReviewForm(forms.ModelForm): + + required_css_class = 'label-required' + class Meta: model = Review fields = ["vote", "comment"] @@ -18,18 +21,27 @@ class ReviewForm(forms.ModelForm): class ReviewCommentForm(forms.ModelForm): + + required_css_class = 'label-required' + class Meta: model = Comment fields = ["text"] class SpeakerCommentForm(forms.ModelForm): + + required_css_class = 'label-required' + class Meta: model = ProposalMessage fields = ["message"] class BulkPresentationForm(forms.Form): + + required_css_class = 'label-required' + talk_ids = forms.CharField( label=_("Talk ids"), max_length=500, diff --git a/symposion/schedule/forms.py b/symposion/schedule/forms.py index 60fe8c55..fd549cd3 100644 --- a/symposion/schedule/forms.py +++ b/symposion/schedule/forms.py @@ -14,6 +14,8 @@ from symposion.schedule.models import (Day, Presentation, Room, SlotKind, Slot, class SlotEditForm(forms.Form): + required_css_class = 'label-required' + def __init__(self, *args, **kwargs): self.slot = kwargs.pop("slot") super(SlotEditForm, self).__init__(*args, **kwargs) @@ -48,6 +50,9 @@ class SlotEditForm(forms.Form): class ScheduleSectionForm(forms.Form): + + required_css_class = 'label-required' + ROOM_KEY = 'room' DATE_KEY = 'date' START_KEY = 'time_start' diff --git a/symposion/speakers/forms.py b/symposion/speakers/forms.py index 5f2aece9..e4fddbc8 100644 --- a/symposion/speakers/forms.py +++ b/symposion/speakers/forms.py @@ -5,6 +5,8 @@ from symposion.speakers.models import Speaker class SpeakerForm(forms.ModelForm): + required_css_class = 'label-required' + class Meta: model = Speaker fields = [ diff --git a/symposion/sponsorship/forms.py b/symposion/sponsorship/forms.py index 100ea8db..418eb7c5 100644 --- a/symposion/sponsorship/forms.py +++ b/symposion/sponsorship/forms.py @@ -8,6 +8,9 @@ from symposion.sponsorship.models import Sponsor, SponsorBenefit class SponsorApplicationForm(forms.ModelForm): + + required_css_class = 'label-required' + def __init__(self, *args, **kwargs): self.user = kwargs.pop("user") kwargs.update({ @@ -37,6 +40,9 @@ class SponsorApplicationForm(forms.ModelForm): class SponsorDetailsForm(forms.ModelForm): + + required_css_class = 'label-required' + class Meta: model = Sponsor fields = [ @@ -48,6 +54,9 @@ class SponsorDetailsForm(forms.ModelForm): class SponsorBenefitsInlineFormSet(BaseInlineFormSet): + + required_css_class = 'label-required' + def __init__(self, *args, **kwargs): kwargs['queryset'] = kwargs.get('queryset', self.model._default_manager).exclude(benefit__type="option") super(SponsorBenefitsInlineFormSet, self).__init__(*args, **kwargs) diff --git a/symposion/teams/forms.py b/symposion/teams/forms.py index 5ba8ccae..5d06c748 100644 --- a/symposion/teams/forms.py +++ b/symposion/teams/forms.py @@ -11,6 +11,8 @@ from symposion.teams.models import Membership class TeamInvitationForm(forms.Form): + required_css_class = 'label-required' + email = forms.EmailField(label=_("Email"), help_text=_("email address must be that of an account on this " "conference site")) From a36ff64a82057946b3bc3b65468cac4698d52b60 Mon Sep 17 00:00:00 2001 From: Sachi King Date: Sun, 7 May 2017 13:22:28 +1000 Subject: [PATCH 747/751] Support monospace TextFields instead of Markdown Drop markdown for monospace With markdown, we're only allowing about 1/10 of what is possible. This creates a false reality and expectation. Lets not suggest this. Nor do we have in-editor preview. So the user has to save, look at it, then go back and edit. And seeing a bunch of sanitized HTML or just missing sections isn't firendly. Monospace, what you type, is what you're going to get. It gives the presenter enough power to build a readable abstract, but not so much that they can break the page and it's CSS, nor the ability to confuse onselve through not getting what you expect. We keep bleach sanitation and we should probably run linkify on this in the long term. (Turn links into clickable links) --- symposion/constants.py | 5 +++++ symposion/markdown_parser.py | 17 ----------------- symposion/proposals/models.py | 18 +++++++----------- symposion/reviews/models.py | 3 ++- symposion/schedule/models.py | 2 +- symposion/speakers/models.py | 20 ++++++++------------ symposion/text_parser.py | 9 +++++++++ 7 files changed, 32 insertions(+), 42 deletions(-) create mode 100644 symposion/constants.py delete mode 100644 symposion/markdown_parser.py create mode 100644 symposion/text_parser.py diff --git a/symposion/constants.py b/symposion/constants.py new file mode 100644 index 00000000..f49455c5 --- /dev/null +++ b/symposion/constants.py @@ -0,0 +1,5 @@ +TEXT_FIELD_MONOSPACE_NOTE=( + "This field is rendered with the monospace font " + "Hack with " + "whitespace preserved") + diff --git a/symposion/markdown_parser.py b/symposion/markdown_parser.py deleted file mode 100644 index f123be47..00000000 --- a/symposion/markdown_parser.py +++ /dev/null @@ -1,17 +0,0 @@ -import logging - -import bleach -import markdown - - -logger = logging.getLogger('MARKDOWN') -logger.setLevel(logging.INFO) - -tags = bleach.sanitizer.ALLOWED_TAGS[:] -tags.extend(['p', 'pre']) - - -def parse(text): - md = markdown.markdown(text, extensions=['extra']) - text = bleach.clean(md, tags=tags) - return text diff --git a/symposion/proposals/models.py b/symposion/proposals/models.py index 2b9d43a1..94568870 100644 --- a/symposion/proposals/models.py +++ b/symposion/proposals/models.py @@ -14,7 +14,8 @@ from django.core.exceptions import ValidationError from model_utils.managers import InheritanceManager from reversion import revisions as reversion -from symposion.markdown_parser import parse +from symposion import constants +from symposion.text_parser import parse from symposion.conference.models import Section from symposion.speakers.models import Speaker @@ -84,9 +85,7 @@ class ProposalBase(models.Model): abstract = models.TextField( _("Abstract"), help_text=_("This will appear in the conference programme. Up to about " - "500 words. Edit using Markdown.") + "500 words. " + constants.TEXT_FIELD_MONOSPACE_NOTE) ) abstract_html = models.TextField(blank=True) @@ -94,9 +93,8 @@ class ProposalBase(models.Model): _("Private Abstract"), help_text=_("This will only be shown to organisers and reviewers. You " "should provide any details about your proposal that you " - "don't want to be public here. Edit using Markdown.") + "don't want to be public here. " + + constants.TEXT_FIELD_MONOSPACE_NOTE) ) private_abstract_html = models.TextField(blank=True) @@ -107,10 +105,8 @@ class ProposalBase(models.Model): "projector, audio. If you require anything in addition, " "please list your technical requirements here. Such as: a " "static IP address, A/V equipment or will be demonstrating " - "security-related techniques on the conference network. " - "Edit using Markdown.") + "security-related techniques on the conference network. " + + constants.TEXT_FIELD_MONOSPACE_NOTE) ) technical_requirements_html = models.TextField(blank=True) diff --git a/symposion/reviews/models.py b/symposion/reviews/models.py index a92bf483..1fc1667b 100644 --- a/symposion/reviews/models.py +++ b/symposion/reviews/models.py @@ -13,7 +13,8 @@ from django.db.models.signals import post_save from django.contrib.auth.models import User from django.utils.translation import ugettext_lazy as _ -from symposion.markdown_parser import parse +from symposion import constants +from symposion.text_parser import parse from symposion.proposals.models import ProposalBase from symposion.schedule.models import Presentation diff --git a/symposion/schedule/models.py b/symposion/schedule/models.py index 0e2307b2..9c738c20 100644 --- a/symposion/schedule/models.py +++ b/symposion/schedule/models.py @@ -5,7 +5,7 @@ from django.contrib.auth.models import User from django.db import models from django.utils.translation import ugettext_lazy as _ -from symposion.markdown_parser import parse +from symposion.text_parser import parse from symposion.proposals.models import ProposalBase from symposion.conference.models import Section from symposion.speakers.models import Speaker diff --git a/symposion/speakers/models.py b/symposion/speakers/models.py index 5ed69866..55397506 100644 --- a/symposion/speakers/models.py +++ b/symposion/speakers/models.py @@ -6,7 +6,8 @@ from django.utils.translation import ugettext_lazy as _ from django.contrib.auth.models import User -from symposion.markdown_parser import parse +from symposion import constants +from symposion.text_parser import parse class Speaker(models.Model): @@ -24,10 +25,8 @@ class Speaker(models.Model): blank=True, help_text=_("This will appear on the conference website and in the " "programme. Please write in the third person, eg 'Alice " - "is a Moblin hacker...', 150-200 words. Edit using " - "" - "Markdown."), + "is a Moblin hacker...', 150-200 words. " + + constants.TEXT_FIELD_MONOSPACE_NOTE), verbose_name=_("Biography"), ) biography_html = models.TextField(blank=True) @@ -36,10 +35,8 @@ class Speaker(models.Model): help_text=_("Have you had any experience presenting elsewhere? If so, " "we'd like to know. Anything you put here will only be " "seen by the organisers and reviewers; use it to convince " - "them why they should accept your proposal. Edit using " - "" - "Markdown."), + "them why they should accept your proposal. " + + constants.TEXT_FIELD_MONOSPACE_NOTE), verbose_name=_("Speaking experience"), ) experience_html = models.TextField(blank=True) @@ -62,9 +59,8 @@ class Speaker(models.Model): accessibility = models.TextField( blank=True, help_text=_("Please describe any special accessibility requirements " - "that you may have. Edit using " - "Markdown."), + "that you may have. " + + constants.TEXT_FIELD_MONOSPACE_NOTE), verbose_name=_("Accessibility requirements")) accessibility_html = models.TextField(blank=True) travel_assistance = models.BooleanField( diff --git a/symposion/text_parser.py b/symposion/text_parser.py new file mode 100644 index 00000000..939d449e --- /dev/null +++ b/symposion/text_parser.py @@ -0,0 +1,9 @@ +import bleach + +tags = bleach.sanitizer.ALLOWED_TAGS[:] +tags.extend(['p', 'pre']) + + +def parse(text): + scrubbed_text = bleach.clean(text, tags=tags) + return scrubbed_text From e61d87d37c8fa454f22f46a1fd803bb435dae7d4 Mon Sep 17 00:00:00 2001 From: Sachi King Date: Sun, 7 May 2017 16:17:06 +1000 Subject: [PATCH 748/751] Py3 fixes for guest hash generation --- symposion/proposals/views.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/symposion/proposals/views.py b/symposion/proposals/views.py index 07e735a0..051a794b 100644 --- a/symposion/proposals/views.py +++ b/symposion/proposals/views.py @@ -123,8 +123,9 @@ def proposal_speaker_manage(request, pk): Q(user=None, invite_email=email_address) ) except Speaker.DoesNotExist: - salt = hashlib.sha1(str(random.random())).hexdigest()[:5] - token = hashlib.sha1(salt + email_address).hexdigest() + salt = hashlib.sha1(str(random.random()).encode('UTF-8')).hexdigest()[:5] + saltedemail = (salt + email_address).encode('UTF-8') + token = hashlib.sha1(saltedemail).hexdigest() pending = Speaker.objects.create( invite_email=email_address, invite_token=token, From 8cb7bcc02149ee2626ea0f5b9ad7dcc4915532d1 Mon Sep 17 00:00:00 2001 From: Sachi King Date: Sun, 7 May 2017 16:17:29 +1000 Subject: [PATCH 749/751] Link to GCP storage for files WARNING: We need to reflect in the webpage that these won't be behind a login. GitLab Issue #2 --- symposion/proposals/models.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/symposion/proposals/models.py b/symposion/proposals/models.py index 94568870..4a8650a0 100644 --- a/symposion/proposals/models.py +++ b/symposion/proposals/models.py @@ -248,5 +248,4 @@ class SupportingDocument(models.Model): description = models.CharField(max_length=140, verbose_name=_("Description")) def download_url(self): - return reverse("proposal_document_download", - args=[self.pk, os.path.basename(self.file.name).lower()]) + return self.file.url From 4a5e4dc6ea337932a8f8b7ed5115aef0d48d317a Mon Sep 17 00:00:00 2001 From: Sachi King Date: Sun, 7 May 2017 19:19:53 +1000 Subject: [PATCH 750/751] Use a standard login handler Is there any reason to not use the standard login decorator? --- symposion/speakers/views.py | 35 ++++++++++++++++------------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/symposion/speakers/views.py b/symposion/speakers/views.py index a0069c10..b087aa5a 100644 --- a/symposion/speakers/views.py +++ b/symposion/speakers/views.py @@ -71,29 +71,26 @@ def speaker_create_staff(request, pk): }) +@login_required def speaker_create_token(request, token): speaker = get_object_or_404(Speaker, invite_token=token) request.session["pending-token"] = token - if request.user.is_authenticated(): - # check for speaker profile - try: - existing_speaker = request.user.speaker_profile - except ObjectDoesNotExist: - pass - else: - del request.session["pending-token"] - additional_speakers = ProposalBase.additional_speakers.through - additional_speakers._default_manager.filter( - speaker=speaker - ).update( - speaker=existing_speaker - ) - messages.info(request, _("You have been associated with all pending " - "talk proposals")) - return redirect("dashboard") + # check for speaker profile + try: + existing_speaker = request.user.speaker_profile + except ObjectDoesNotExist: + pass else: - if not request.user.is_authenticated(): - return redirect("account_login") + del request.session["pending-token"] + additional_speakers = ProposalBase.additional_speakers.through + additional_speakers._default_manager.filter( + speaker=speaker + ).update( + speaker=existing_speaker + ) + messages.info(request, _("You have been associated with all pending " + "talk proposals")) + return redirect("dashboard") return redirect("speaker_create") From d95d66dac8b2ba44fc640a859e0e245216d35ebd Mon Sep 17 00:00:00 2001 From: Sachi King Date: Sat, 27 May 2017 20:05:44 +1000 Subject: [PATCH 751/751] Taking one out of PyCon's (US) book We're lock-step with symposion, and upstream is dead. Vendor it. --- .gitignore | 6 - .travis.yml | 11 - .tx/config | 7 - CONTRIBUTING.md | 162 ------------ MANIFEST.in | 4 - README.rst | 74 ------ docs/Makefile | 153 ----------- docs/conf.py | 242 ------------------ docs/conference.rst | 40 --- docs/index.rst | 34 --- docs/project.rst | 10 - docs/proposals.rst | 111 -------- docs/schedule.rst | 49 ---- docs/speakers.rst | 22 -- docs/sponsorship.rst | 82 ------ requirements/base.txt | 12 - requirements/docs.txt | 3 - setup.py | 36 --- tox.ini | 3 - LICENSE => vendor/symposion/LICENSE | 0 {symposion => vendor/symposion}/__init__.py | 0 {symposion => vendor/symposion}/conf.py | 0 .../symposion}/conference/__init__.py | 0 .../symposion}/conference/admin.py | 0 .../symposion}/conference/apps.py | 0 .../conference/migrations/0001_initial.py | 0 .../conference/migrations/__init__.py | 0 .../symposion}/conference/models.py | 0 .../symposion}/conference/urls.py | 0 .../symposion}/conference/views.py | 0 {symposion => vendor/symposion}/constants.py | 0 .../locale/en/LC_MESSAGES/django.mo | Bin .../locale/en/LC_MESSAGES/django.po | 0 .../locale/ja/LC_MESSAGES/django.mo | Bin .../locale/ja/LC_MESSAGES/django.po | 0 {symposion => vendor/symposion}/models.py | 0 .../symposion}/proposals/__init__.py | 0 .../symposion}/proposals/actions.py | 0 .../symposion}/proposals/admin.py | 0 .../symposion}/proposals/apps.py | 0 .../symposion}/proposals/forms.py | 0 .../proposals/migrations/0001_initial.py | 0 .../proposals/migrations/__init__.py | 0 .../symposion}/proposals/models.py | 0 .../proposals/templatetags/__init__.py | 0 .../proposals/templatetags/proposal_tags.py | 0 .../symposion}/proposals/urls.py | 0 .../symposion}/proposals/views.py | 0 .../symposion}/reviews/__init__.py | 0 .../symposion}/reviews/admin.py | 0 .../symposion}/reviews/apps.py | 0 .../symposion}/reviews/context_processors.py | 0 .../symposion}/reviews/forms.py | 0 .../symposion}/reviews/management/__init__.py | 0 .../reviews/management/commands/__init__.py | 0 .../management/commands/assign_reviewers.py | 0 .../management/commands/calculate_results.py | 0 .../commands/create_review_permissions.py | 0 .../management/commands/promoteproposals.py | 0 .../reviews/migrations/0001_initial.py | 0 .../symposion}/reviews/migrations/__init__.py | 0 .../symposion}/reviews/models.py | 0 .../reviews/templatetags/__init__.py | 0 .../reviews/templatetags/review_tags.py | 0 .../symposion}/reviews/urls.py | 0 .../symposion}/reviews/utils.py | 0 .../symposion}/reviews/views.py | 0 .../symposion}/schedule/__init__.py | 0 .../symposion}/schedule/admin.py | 0 .../symposion}/schedule/apps.py | 0 .../symposion}/schedule/forms.py | 0 .../symposion}/schedule/helpers.py | 0 .../schedule/migrations/0001_initial.py | 0 .../migrations/0002_presentation_unpublish.py | 0 .../migrations/0003_auto_20161113_1530.py | 0 .../migrations/0003_slot_exclusive.py | 0 .../schedule/migrations/0004_merge.py | 0 .../migrations/0005_auto_20161210_1736.py | 0 .../schedule/migrations/0006_room_track.py | 0 .../migrations/0007_auto_20161224_1709.py | 0 .../schedule/migrations/__init__.py | 0 .../symposion}/schedule/models.py | 0 .../symposion}/schedule/tests/__init__.py | 0 .../schedule/tests/data/schedule.csv | 0 .../schedule/tests/data/schedule_overlap.csv | 0 .../symposion}/schedule/tests/factories.py | 0 .../symposion}/schedule/tests/runtests.py | 0 .../symposion}/schedule/tests/test_forms.py | 0 .../symposion}/schedule/tests/test_views.py | 0 .../schedule/tests/test_views_session.py | 0 .../symposion}/schedule/timetable.py | 0 .../symposion}/schedule/urls.py | 0 .../symposion}/schedule/views.py | 0 .../symposion}/speakers/__init__.py | 0 .../symposion}/speakers/admin.py | 0 .../symposion}/speakers/apps.py | 0 .../symposion}/speakers/forms.py | 0 .../speakers/management/__init__.py | 0 .../speakers/management/commands/__init__.py | 0 .../commands/export_speaker_data.py | 0 .../speakers/migrations/0001_initial.py | 0 .../migrations/0002_auto_20161230_1900.py | 0 .../speakers/migrations/__init__.py | 0 .../symposion}/speakers/models.py | 0 .../symposion}/speakers/urls.py | 0 .../symposion}/speakers/views.py | 0 .../symposion}/sponsorship/__init__.py | 0 .../symposion}/sponsorship/admin.py | 0 .../symposion}/sponsorship/apps.py | 0 .../symposion}/sponsorship/forms.py | 0 .../sponsorship/management/__init__.py | 0 .../management/commands/__init__.py | 0 .../commands/export_sponsors_data.py | 0 .../commands/reset_sponsor_benefits.py | 0 .../symposion}/sponsorship/managers.py | 0 .../sponsorship/migrations/0001_initial.py | 0 .../sponsorship/migrations/__init__.py | 0 .../symposion}/sponsorship/models.py | 0 .../sponsorship/templatetags/__init__.py | 0 .../templatetags/sponsorship_tags.py | 0 .../symposion}/sponsorship/tests.py | 0 .../symposion}/sponsorship/urls.py | 0 .../symposion}/sponsorship/views.py | 0 .../datatables/js/dataTables.bootstrap.js | 0 .../datatables/js/jquery.dataTables.min.js | 0 .../static/tabletools/js/TableTools.js | 0 .../static/tabletools/js/TableTools.min.js | 0 .../static/tabletools/js/TableTools.min.js.gz | Bin .../static/tabletools/js/ZeroClipboard.js | 0 .../static/tabletools/swf/copy_csv_xls.swf | Bin .../tabletools/swf/copy_csv_xls_pdf.swf | Bin .../symposion}/teams/__init__.py | 0 .../symposion}/teams/admin.py | 0 .../symposion}/teams/backends.py | 0 .../symposion}/teams/forms.py | 0 .../teams/migrations/0001_initial.py | 0 .../symposion}/teams/migrations/__init__.py | 0 .../symposion}/teams/models.py | 0 .../symposion}/teams/templatetags/__init__.py | 0 .../teams/templatetags/teams_tags.py | 0 {symposion => vendor/symposion}/teams/urls.py | 0 .../symposion}/teams/views.py | 0 .../symposion}/text_parser.py | 0 .../symposion}/utils/__init__.py | 0 {symposion => vendor/symposion}/utils/mail.py | 0 {symposion => vendor/symposion}/views.py | 0 146 files changed, 1061 deletions(-) delete mode 100644 .gitignore delete mode 100644 .travis.yml delete mode 100644 .tx/config delete mode 100644 CONTRIBUTING.md delete mode 100644 MANIFEST.in delete mode 100644 README.rst delete mode 100644 docs/Makefile delete mode 100644 docs/conf.py delete mode 100644 docs/conference.rst delete mode 100644 docs/index.rst delete mode 100644 docs/project.rst delete mode 100644 docs/proposals.rst delete mode 100644 docs/schedule.rst delete mode 100644 docs/speakers.rst delete mode 100644 docs/sponsorship.rst delete mode 100644 requirements/base.txt delete mode 100644 requirements/docs.txt delete mode 100644 setup.py delete mode 100644 tox.ini rename LICENSE => vendor/symposion/LICENSE (100%) rename {symposion => vendor/symposion}/__init__.py (100%) rename {symposion => vendor/symposion}/conf.py (100%) rename {symposion => vendor/symposion}/conference/__init__.py (100%) rename {symposion => vendor/symposion}/conference/admin.py (100%) rename {symposion => vendor/symposion}/conference/apps.py (100%) rename {symposion => vendor/symposion}/conference/migrations/0001_initial.py (100%) rename {symposion => vendor/symposion}/conference/migrations/__init__.py (100%) rename {symposion => vendor/symposion}/conference/models.py (100%) rename {symposion => vendor/symposion}/conference/urls.py (100%) rename {symposion => vendor/symposion}/conference/views.py (100%) rename {symposion => vendor/symposion}/constants.py (100%) rename {symposion => vendor/symposion}/locale/en/LC_MESSAGES/django.mo (100%) rename {symposion => vendor/symposion}/locale/en/LC_MESSAGES/django.po (100%) rename {symposion => vendor/symposion}/locale/ja/LC_MESSAGES/django.mo (100%) rename {symposion => vendor/symposion}/locale/ja/LC_MESSAGES/django.po (100%) rename {symposion => vendor/symposion}/models.py (100%) rename {symposion => vendor/symposion}/proposals/__init__.py (100%) rename {symposion => vendor/symposion}/proposals/actions.py (100%) rename {symposion => vendor/symposion}/proposals/admin.py (100%) rename {symposion => vendor/symposion}/proposals/apps.py (100%) rename {symposion => vendor/symposion}/proposals/forms.py (100%) rename {symposion => vendor/symposion}/proposals/migrations/0001_initial.py (100%) rename {symposion => vendor/symposion}/proposals/migrations/__init__.py (100%) rename {symposion => vendor/symposion}/proposals/models.py (100%) rename {symposion => vendor/symposion}/proposals/templatetags/__init__.py (100%) rename {symposion => vendor/symposion}/proposals/templatetags/proposal_tags.py (100%) rename {symposion => vendor/symposion}/proposals/urls.py (100%) rename {symposion => vendor/symposion}/proposals/views.py (100%) rename {symposion => vendor/symposion}/reviews/__init__.py (100%) rename {symposion => vendor/symposion}/reviews/admin.py (100%) rename {symposion => vendor/symposion}/reviews/apps.py (100%) rename {symposion => vendor/symposion}/reviews/context_processors.py (100%) rename {symposion => vendor/symposion}/reviews/forms.py (100%) rename {symposion => vendor/symposion}/reviews/management/__init__.py (100%) rename {symposion => vendor/symposion}/reviews/management/commands/__init__.py (100%) rename {symposion => vendor/symposion}/reviews/management/commands/assign_reviewers.py (100%) rename {symposion => vendor/symposion}/reviews/management/commands/calculate_results.py (100%) rename {symposion => vendor/symposion}/reviews/management/commands/create_review_permissions.py (100%) rename {symposion => vendor/symposion}/reviews/management/commands/promoteproposals.py (100%) rename {symposion => vendor/symposion}/reviews/migrations/0001_initial.py (100%) rename {symposion => vendor/symposion}/reviews/migrations/__init__.py (100%) rename {symposion => vendor/symposion}/reviews/models.py (100%) rename {symposion => vendor/symposion}/reviews/templatetags/__init__.py (100%) rename {symposion => vendor/symposion}/reviews/templatetags/review_tags.py (100%) rename {symposion => vendor/symposion}/reviews/urls.py (100%) rename {symposion => vendor/symposion}/reviews/utils.py (100%) rename {symposion => vendor/symposion}/reviews/views.py (100%) rename {symposion => vendor/symposion}/schedule/__init__.py (100%) rename {symposion => vendor/symposion}/schedule/admin.py (100%) rename {symposion => vendor/symposion}/schedule/apps.py (100%) rename {symposion => vendor/symposion}/schedule/forms.py (100%) rename {symposion => vendor/symposion}/schedule/helpers.py (100%) rename {symposion => vendor/symposion}/schedule/migrations/0001_initial.py (100%) rename {symposion => vendor/symposion}/schedule/migrations/0002_presentation_unpublish.py (100%) rename {symposion => vendor/symposion}/schedule/migrations/0003_auto_20161113_1530.py (100%) rename {symposion => vendor/symposion}/schedule/migrations/0003_slot_exclusive.py (100%) rename {symposion => vendor/symposion}/schedule/migrations/0004_merge.py (100%) rename {symposion => vendor/symposion}/schedule/migrations/0005_auto_20161210_1736.py (100%) rename {symposion => vendor/symposion}/schedule/migrations/0006_room_track.py (100%) rename {symposion => vendor/symposion}/schedule/migrations/0007_auto_20161224_1709.py (100%) rename {symposion => vendor/symposion}/schedule/migrations/__init__.py (100%) rename {symposion => vendor/symposion}/schedule/models.py (100%) rename {symposion => vendor/symposion}/schedule/tests/__init__.py (100%) rename {symposion => vendor/symposion}/schedule/tests/data/schedule.csv (100%) rename {symposion => vendor/symposion}/schedule/tests/data/schedule_overlap.csv (100%) rename {symposion => vendor/symposion}/schedule/tests/factories.py (100%) rename {symposion => vendor/symposion}/schedule/tests/runtests.py (100%) rename {symposion => vendor/symposion}/schedule/tests/test_forms.py (100%) rename {symposion => vendor/symposion}/schedule/tests/test_views.py (100%) rename {symposion => vendor/symposion}/schedule/tests/test_views_session.py (100%) rename {symposion => vendor/symposion}/schedule/timetable.py (100%) rename {symposion => vendor/symposion}/schedule/urls.py (100%) rename {symposion => vendor/symposion}/schedule/views.py (100%) rename {symposion => vendor/symposion}/speakers/__init__.py (100%) rename {symposion => vendor/symposion}/speakers/admin.py (100%) rename {symposion => vendor/symposion}/speakers/apps.py (100%) rename {symposion => vendor/symposion}/speakers/forms.py (100%) rename {symposion => vendor/symposion}/speakers/management/__init__.py (100%) rename {symposion => vendor/symposion}/speakers/management/commands/__init__.py (100%) rename {symposion => vendor/symposion}/speakers/management/commands/export_speaker_data.py (100%) rename {symposion => vendor/symposion}/speakers/migrations/0001_initial.py (100%) rename {symposion => vendor/symposion}/speakers/migrations/0002_auto_20161230_1900.py (100%) rename {symposion => vendor/symposion}/speakers/migrations/__init__.py (100%) rename {symposion => vendor/symposion}/speakers/models.py (100%) rename {symposion => vendor/symposion}/speakers/urls.py (100%) rename {symposion => vendor/symposion}/speakers/views.py (100%) rename {symposion => vendor/symposion}/sponsorship/__init__.py (100%) rename {symposion => vendor/symposion}/sponsorship/admin.py (100%) rename {symposion => vendor/symposion}/sponsorship/apps.py (100%) rename {symposion => vendor/symposion}/sponsorship/forms.py (100%) rename {symposion => vendor/symposion}/sponsorship/management/__init__.py (100%) rename {symposion => vendor/symposion}/sponsorship/management/commands/__init__.py (100%) rename {symposion => vendor/symposion}/sponsorship/management/commands/export_sponsors_data.py (100%) rename {symposion => vendor/symposion}/sponsorship/management/commands/reset_sponsor_benefits.py (100%) rename {symposion => vendor/symposion}/sponsorship/managers.py (100%) rename {symposion => vendor/symposion}/sponsorship/migrations/0001_initial.py (100%) rename {symposion => vendor/symposion}/sponsorship/migrations/__init__.py (100%) rename {symposion => vendor/symposion}/sponsorship/models.py (100%) rename {symposion => vendor/symposion}/sponsorship/templatetags/__init__.py (100%) rename {symposion => vendor/symposion}/sponsorship/templatetags/sponsorship_tags.py (100%) rename {symposion => vendor/symposion}/sponsorship/tests.py (100%) rename {symposion => vendor/symposion}/sponsorship/urls.py (100%) rename {symposion => vendor/symposion}/sponsorship/views.py (100%) rename {symposion => vendor/symposion}/static/datatables/js/dataTables.bootstrap.js (100%) rename {symposion => vendor/symposion}/static/datatables/js/jquery.dataTables.min.js (100%) rename {symposion => vendor/symposion}/static/tabletools/js/TableTools.js (100%) rename {symposion => vendor/symposion}/static/tabletools/js/TableTools.min.js (100%) rename {symposion => vendor/symposion}/static/tabletools/js/TableTools.min.js.gz (100%) rename {symposion => vendor/symposion}/static/tabletools/js/ZeroClipboard.js (100%) rename {symposion => vendor/symposion}/static/tabletools/swf/copy_csv_xls.swf (100%) rename {symposion => vendor/symposion}/static/tabletools/swf/copy_csv_xls_pdf.swf (100%) rename {symposion => vendor/symposion}/teams/__init__.py (100%) rename {symposion => vendor/symposion}/teams/admin.py (100%) rename {symposion => vendor/symposion}/teams/backends.py (100%) rename {symposion => vendor/symposion}/teams/forms.py (100%) rename {symposion => vendor/symposion}/teams/migrations/0001_initial.py (100%) rename {symposion => vendor/symposion}/teams/migrations/__init__.py (100%) rename {symposion => vendor/symposion}/teams/models.py (100%) rename {symposion => vendor/symposion}/teams/templatetags/__init__.py (100%) rename {symposion => vendor/symposion}/teams/templatetags/teams_tags.py (100%) rename {symposion => vendor/symposion}/teams/urls.py (100%) rename {symposion => vendor/symposion}/teams/views.py (100%) rename {symposion => vendor/symposion}/text_parser.py (100%) rename {symposion => vendor/symposion}/utils/__init__.py (100%) rename {symposion => vendor/symposion}/utils/mail.py (100%) rename {symposion => vendor/symposion}/views.py (100%) diff --git a/.gitignore b/.gitignore deleted file mode 100644 index f0594b31..00000000 --- a/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -_build -*.pyc -dev.db -site_media -*.egg-info -.DS_Store diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index a62fc3cf..00000000 --- a/.travis.yml +++ /dev/null @@ -1,11 +0,0 @@ -language: python - -python: - - 2.7 - -install: - - pip install flake8 - - pip install -e . - -script: - - flake8 symposion diff --git a/.tx/config b/.tx/config deleted file mode 100644 index d8ebca05..00000000 --- a/.tx/config +++ /dev/null @@ -1,7 +0,0 @@ -[main] -host = https://www.transifex.com - -[symposion.djangopo] -file_filter = symposion/locale//LC_MESSAGES/django.po -source_lang = en -source_file = symposion/locale/en/LC_MESSAGES/django.po diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index 17eef319..00000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,162 +0,0 @@ -# How to Contribute - -There are many ways you can help contribute to symposion and the various apps, -themes, and starter projects that it is made up of. Contributing code, writing -documentation, reporting bugs, as well as reading and providing feedback on -issues and pull requests, all are valid and necessary ways to -help. - -## Committing Code - -The great thing about using a distributed versioning control system like git -is that everyone becomes a committer. When other people write good patches -it makes it very easy to include their fixes/features and give them proper -credit for the work. - -We recommend that you do all your work in a separate branch. When you -are ready to work on a bug or a new feature create yourself a new branch. The -reason why this is important is you can commit as often you like. When you are -ready you can merge in the change. Let's take a look at a common workflow: - - git checkout -b task-566 - ... fix and git commit often ... - git push origin task-566 - -The reason we have created two new branches is to stay off of `master`. -Keeping master clean of only upstream changes makes yours and ours lives -easier. You can then send us a pull request for the fix/feature. Then we can -easily review it and merge it when ready. - - -### Writing Commit Messages - -Writing a good commit message makes it simple for us to identify what your -commit does from a high-level. There are some basic guidelines we'd like to -ask you to follow. - -A critical part is that you keep the **first** line as short and sweet -as possible. This line is important because when git shows commits and it has -limited space or a different formatting option is used the first line becomes -all someone might see. If your change isn't something non-trivial or there -reasoning behind the change is not obvious, then please write up an extended -message explaining the fix, your rationale, and anything else relevant for -someone else that might be reviewing the change. Lastly, if there is a -corresponding issue in Github issues for it, use the final line to provide -a message that will link the commit message to the issue and auto-close it -if appropriate. - - Add ability to travel back in time - - You need to be driving 88 miles per hour to generate 1.21 gigawatts of - power to properly use this feature. - - Fixes #88 - - -## Coding style - -When writing code to be included in symposion keep our style in mind: - -* Follow [PEP8](http://www.python.org/dev/peps/pep-0008/) there are some - cases where we do not follow PEP8. It is an excellent starting point. -* Follow [Django's coding style](http://docs.djangoproject.com/en/dev/internals/contributing/#coding-style) - we're pretty much in agreement on Django style outlined there. - -We would like to enforce a few more strict guides not outlined by PEP8 or -Django's coding style: - -* PEP8 tries to keep line length at 80 characters. We follow it when we can, - but not when it makes a line harder to read. It is okay to go a little bit - over 80 characters if not breaking the line improves readability. -* Use double quotes not single quotes. Single quotes are allowed in cases - where a double quote is needed in the string. This makes the code read - cleaner in those cases. -* Blank lines are indented to the appropriate level for the block they are in. -* Docstrings always use three double quotes on a line of their own, so, for - example, a single line docstring should take up three lines not one. -* Imports are grouped specifically and ordered alphabetically. This is shown - in the example below. -* Always use `reverse` and never `@models.permalink`. -* Tuples should be reserved for positional data structures and not used - where a list is more appropriate. -* URL patterns should use the `url()` function rather than a tuple. - -Here is an example of these rules applied: - - # first set of imports are stdlib imports - # non-from imports go first then from style import in their own group - import csv - - # second set of imports are Django imports with contrib in their own - # group. - from django.core.urlresolvers import reverse - from django.db import models - from django.utils import timezone - from django.utils.translation import ugettext_lazy as _ - - from django.contrib.auth.models import User - - # third set of imports are external apps (if applicable) - from tagging.fields import TagField - - # fourth set of imports are local apps - from .fields import MarkupField - - - class Task(models.Model): - """ - A model for storing a task. - """ - - creator = models.ForeignKey(User) - created = models.DateTimeField(default=timezone.now) - modified = models.DateTimeField(default=timezone.now) - - objects = models.Manager() - - class Meta: - verbose_name = _("task") - verbose_name_plural = _("tasks") - - def __unicode__(self): - return self.summary - - def save(self, **kwargs): - self.modified = datetime.now() - super(Task, self).save(**kwargs) - - def get_absolute_url(self): - return reverse("task_detail", kwargs={"task_id": self.pk}) - - # custom methods - - - class TaskComment(models.Model): - # ... you get the point ... - pass - - -## Pull Requests - -Please keep your pull requests focused on one specific thing only. If you -have a number of contributions to make, then please send seperate pull -requests. It is much easier on maintainers to receive small, well defined, -pull requests, than it is to have a single large one that batches up a -lot of unrelated commits. - -If you ended up making multiple commits for one logical change, please -rebase into a single commit. - - git rebase -i HEAD~10 # where 10 is the number of commits back you need - -This will pop up an editor with your commits and some instructions you want -to squash commits down by replacing 'pick' with 's' to have it combined with -the commit before it. You can squash multiple ones at the same time. - -When you save and exit the text editor where you were squashing commits, git -will squash them down and then present you with another editor with commit -messages. Choose the one to apply to the squashed commit (or write a new -one entirely.) Save and exit will complete the rebase. Use a forced push to -your fork. - - git push -f diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 55bb6eaa..00000000 --- a/MANIFEST.in +++ /dev/null @@ -1,4 +0,0 @@ -include README LICENSE -recursive-include symposion/templates *.html *.txt -recursive-include symposion/static * -recursive-include symposion/locale * diff --git a/README.rst b/README.rst deleted file mode 100644 index 95448359..00000000 --- a/README.rst +++ /dev/null @@ -1,74 +0,0 @@ -Symposion ---------- - -.. image:: http://slack.pinaxproject.com/badge.svg - :target: http://slack.pinaxproject.com/ - -.. image:: https://img.shields.io/travis/pinax/symposion.svg - :target: https://travis-ci.org/pinax/symposion - -.. image:: https://img.shields.io/coveralls/pinax/symposion.svg - :target: https://coveralls.io/r/pinax/symposion - -.. image:: https://img.shields.io/pypi/dm/symposion.svg - :target: https://pypi.python.org/pypi/symposion/ - -.. image:: https://img.shields.io/pypi/v/symposion.svg - :target: https://pypi.python.org/pypi/symposion/ - -.. image:: https://img.shields.io/badge/license-MIT-blue.svg - :target: https://pypi.python.org/pypi/symposion/ - - -Pinax ------- - -Pinax is an open-source platform built on the Django Web Framework. It is an ecosystem of reusable Django apps, themes, and starter project templates. -This collection can be found at http://pinaxproject.com. - - -symposion ----------- - -``symposion`` is a conference management solution from Eldarion. It was built with the generous support of the Python Software Foundation. See http://eldarion.com/symposion/ for commercial support, customization and hosting. - - -Quickstart -========== - -To install ``symposion``, run: - - pip install symposion - -``symposion`` is a Django app. You will need to create a Django project to -customize and manage your Symposion installation. We have built a basic -Django startproject template that includes ``symposion`` (https://github.com/pinax/pinax-project-symposion). - - -Documentation ---------------- -The ``symposion`` documentation is available at https://symposion.readthedocs.org/en/latest/. -The Pinax documentation is available at http://pinaxproject.com/pinax/. - -Contribute ----------------- - -See this blog post http://blog.pinaxproject.com/2016/02/26/recap-february-pinax-hangout/ including a video, or our How to Contribute (http://pinaxproject.com/pinax/how_to_contribute/) section for an overview on how contributing to Pinax works. For concrete contribution ideas, please see our Ways to Contribute/What We Need Help With (http://pinaxproject.com/pinax/ways_to_contribute/) section. - -In case of any questions, we recommend you join our Pinax Slack team (http://slack.pinaxproject.com) and ping us there instead of creating an issue on GitHub. Creating issues on GitHub is of course also valid but we are usually able to help you faster if you ping us in Slack. - -We also highly recommend reading our Open Source and Self-Care blog post (http://blog.pinaxproject.com/2016/01/19/open-source-and-self-care/). - - -Code of Conduct ----------------- - -In order to foster a kind, inclusive, and harassment-free community, the Pinax Project has a code of conduct, which can be found here http://pinaxproject.com/pinax/code_of_conduct/. We ask you to treat everyone as a smart human programmer that shares an interest in Python, Django, and Pinax with you. - - -Pinax Project Blog and Twitter -------------------------------- - -For updates and news regarding the Pinax Project, please follow us on Twitter at @pinaxproject and check out our blog http://blog.pinaxproject.com. - - diff --git a/docs/Makefile b/docs/Makefile deleted file mode 100644 index affca18b..00000000 --- a/docs/Makefile +++ /dev/null @@ -1,153 +0,0 @@ -# Makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -PAPER = -BUILDDIR = _build - -# Internal variables. -PAPEROPT_a4 = -D latex_paper_size=a4 -PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . -# the i18n builder cannot share the environment and doctrees with the others -I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . - -.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext - -help: - @echo "Please use \`make ' where is one of" - @echo " html to make standalone HTML files" - @echo " dirhtml to make HTML files named index.html in directories" - @echo " singlehtml to make a single large HTML file" - @echo " pickle to make pickle files" - @echo " json to make JSON files" - @echo " htmlhelp to make HTML files and a HTML help project" - @echo " qthelp to make HTML files and a qthelp project" - @echo " devhelp to make HTML files and a Devhelp project" - @echo " epub to make an epub" - @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " latexpdf to make LaTeX files and run them through pdflatex" - @echo " text to make text files" - @echo " man to make manual pages" - @echo " texinfo to make Texinfo files" - @echo " info to make Texinfo files and run them through makeinfo" - @echo " gettext to make PO message catalogs" - @echo " changes to make an overview of all changed/added/deprecated items" - @echo " linkcheck to check all external links for integrity" - @echo " doctest to run all doctests embedded in the documentation (if enabled)" - -clean: - -rm -rf $(BUILDDIR)/* - -html: - $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." - -dirhtml: - $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." - -singlehtml: - $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml - @echo - @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." - -pickle: - $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle - @echo - @echo "Build finished; now you can process the pickle files." - -json: - $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json - @echo - @echo "Build finished; now you can process the JSON files." - -htmlhelp: - $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp - @echo - @echo "Build finished; now you can run HTML Help Workshop with the" \ - ".hhp project file in $(BUILDDIR)/htmlhelp." - -qthelp: - $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp - @echo - @echo "Build finished; now you can run "qcollectiongenerator" with the" \ - ".qhcp project file in $(BUILDDIR)/qthelp, like this:" - @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/PinaxSymposion.qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/PinaxSymposion.qhc" - -devhelp: - $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp - @echo - @echo "Build finished." - @echo "To view the help file:" - @echo "# mkdir -p $$HOME/.local/share/devhelp/PinaxSymposion" - @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/PinaxSymposion" - @echo "# devhelp" - -epub: - $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub - @echo - @echo "Build finished. The epub file is in $(BUILDDIR)/epub." - -latex: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo - @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." - @echo "Run \`make' in that directory to run these through (pdf)latex" \ - "(use \`make latexpdf' here to do that automatically)." - -latexpdf: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through pdflatex..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -text: - $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text - @echo - @echo "Build finished. The text files are in $(BUILDDIR)/text." - -man: - $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man - @echo - @echo "Build finished. The manual pages are in $(BUILDDIR)/man." - -texinfo: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo - @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." - @echo "Run \`make' in that directory to run these through makeinfo" \ - "(use \`make info' here to do that automatically)." - -info: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo "Running Texinfo files through makeinfo..." - make -C $(BUILDDIR)/texinfo info - @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." - -gettext: - $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale - @echo - @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." - -changes: - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes - @echo - @echo "The overview file is in $(BUILDDIR)/changes." - -linkcheck: - $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck - @echo - @echo "Link check complete; look for any errors in the above output " \ - "or in $(BUILDDIR)/linkcheck/output.txt." - -doctest: - $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest - @echo "Testing of doctests in the sources finished, look at the " \ - "results in $(BUILDDIR)/doctest/output.txt." diff --git a/docs/conf.py b/docs/conf.py deleted file mode 100644 index a6153576..00000000 --- a/docs/conf.py +++ /dev/null @@ -1,242 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Pinax Symposion documentation build configuration file, created by -# sphinx-quickstart on Sun Feb 5 17:31:13 2012. -# -# This file is execfile()d with the current directory set to its containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -import sys, os - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -#sys.path.insert(0, os.path.abspath('.')) - -# -- General configuration ----------------------------------------------------- - -# If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = '1.0' - -# Add any Sphinx extension module names here, as strings. They can be extensions -# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.viewcode'] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix of source filenames. -source_suffix = '.rst' - -# The encoding of source files. -#source_encoding = 'utf-8-sig' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = u'Pinax Symposion' -copyright = u'2012, Eldarion Team' - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -version = '0.5' -# The full version, including alpha/beta/rc tags. -release = '0.5dev' - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -#language = None - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -#today = '' -# Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -exclude_patterns = ['_build'] - -# The reST default role (used for this markup: `text`) to use for all documents. -#default_role = None - -# If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -#add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -#show_authors = False - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] - - -# -- Options for HTML output --------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -html_theme = 'default' - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -#html_theme_options = {} - -# Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -#html_title = None - -# A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -#html_logo = None - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -#html_favicon = None - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -#html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -#html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -#html_additional_pages = {} - -# If false, no module index is generated. -#html_domain_indices = True - -# If false, no index is generated. -#html_use_index = True - -# If true, the index is split into individual pages for each letter. -#html_split_index = False - -# If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True - -# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True - -# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -#html_use_opensearch = '' - -# This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None - -# Output file base name for HTML help builder. -htmlhelp_basename = 'PinaxSymposiondoc' - - -# -- Options for LaTeX output -------------------------------------------------- - -latex_elements = { -# The paper size ('letterpaper' or 'a4paper'). -#'papersize': 'letterpaper', - -# The font size ('10pt', '11pt' or '12pt'). -#'pointsize': '10pt', - -# Additional stuff for the LaTeX preamble. -#'preamble': '', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, author, documentclass [howto/manual]). -latex_documents = [ - ('index', 'PinaxSymposion.tex', u'Pinax Symposion Documentation', - u'Eldarion Team', 'manual'), -] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -#latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -#latex_use_parts = False - -# If true, show page references after internal links. -#latex_show_pagerefs = False - -# If true, show URL addresses after external links. -#latex_show_urls = False - -# Documents to append as an appendix to all manuals. -#latex_appendices = [] - -# If false, no module index is generated. -#latex_domain_indices = True - - -# -- Options for manual page output -------------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - ('index', 'pinaxsymposion', u'Pinax Symposion Documentation', - [u'Eldarion Team'], 1) -] - -# If true, show URL addresses after external links. -#man_show_urls = False - - -# -- Options for Texinfo output ------------------------------------------------ - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - ('index', 'PinaxSymposion', u'Pinax Symposion Documentation', - u'Eldarion Team', 'PinaxSymposion', 'One line description of project.', - 'Miscellaneous'), -] - -# Documents to append as an appendix to all manuals. -#texinfo_appendices = [] - -# If false, no module index is generated. -#texinfo_domain_indices = True - -# How to display URL addresses: 'footnote', 'no', or 'inline'. -#texinfo_show_urls = 'footnote' diff --git a/docs/conference.rst b/docs/conference.rst deleted file mode 100644 index 213a626b..00000000 --- a/docs/conference.rst +++ /dev/null @@ -1,40 +0,0 @@ -Conference App -============== - -The overall conference settings are managed via the ``conference`` app. - -Conferences and their sections are added and configured via the Django admin. - - -Models ------- - -Each conference needs an instance of a ``Conference`` model. In most cases you -will only need one of these but Symposion does support multiple conferences -sharing a database. Similar to the Django Sites framework, the conference your -project is for is selected by the ``CONFERENCE_ID`` setting which defaults to -``1`` but can be changed to the pk of another conference if you have more than -one. - -The conference model has an optional ``start_date`` and ``end_date`` -indicating when the conference will run. These are optional so you can begin -to configure your conference even if you don't know the exact dates. - -The conference model also has a ``timezone`` field which you should set to the -timezone your conference will be in. - -There is also a ``Section`` model. This is useful if your conference has -different parts to it that run of different days with a different management, -review or scheduling process. Example of distinct sections might be -"Tutorials", "Talks", "Workshops", "Sprints", "Expo". Many aspects of -Symposion can be configured on a per-section basis. - -Each section has an optional ``start_date`` and ``end_date`` similar to the -overall conference. - - -Helper Functions ----------------- - -A ``conference.models.current_conference()`` function exists to retrieve the -``Conference`` selected by ``CONFERENCE_ID``. diff --git a/docs/index.rst b/docs/index.rst deleted file mode 100644 index 0b7abda1..00000000 --- a/docs/index.rst +++ /dev/null @@ -1,34 +0,0 @@ -Pinax Symposion -=============== - -Pinax Symposion is an open-source conference management system written in -Django. Symposion includes support for content management, proposal -submission, reviews, scheduling and sponsor management. - - -.. toctree:: - :maxdepth: 2 - - project - conference - content - proposals - sponsorship - speakers - schedule - - -About ------ - -Symposion came out of development done by Eldarion for DjangoCon US and US PyCon -but has been independently used for a number of other conferences. -The project homepage is http://eldarion.com/symposion/ - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` - diff --git a/docs/project.rst b/docs/project.rst deleted file mode 100644 index 8a59b8db..00000000 --- a/docs/project.rst +++ /dev/null @@ -1,10 +0,0 @@ -Project Template -================ - -The `pinax-project-symposion `_ repository -is a starter project demonstrating how to create a minimal symposion instance. - -TODO: - * initial data - * overriding templates - * deployment diff --git a/docs/proposals.rst b/docs/proposals.rst deleted file mode 100644 index bb578ee8..00000000 --- a/docs/proposals.rst +++ /dev/null @@ -1,111 +0,0 @@ -Proposals App -============= - - -Models ------- - - -ProposalSection -~~~~~~~~~~~~~~~ - -Recall that a symposion instance consists of one or more ``Conference``s each -made up of one or more ``Section``s. - -Different sections can have different open / close dates for proposals. -This is managed through a ``ProposalSection`` which is a one-to-one with -``Section`` where you can define a ``start`` date, an ``end`` date and/or -simply toggle proposals for the section ``closed``. - -A section is available for proposals iff: - * it is after the ``start`` (if there is one) and - * it is before the ``end`` (if there is one) and - * ``closed`` is NULL or False - -In other words, ``closed`` can be used as an override, regardless of ``start`` -and ``end`` and, if you want, you can just manually use ``closed`` rather than -setting dates. - -This model is currently managed by conference staff via the Django admin -although given it's part of "conference setup", it may often just be a -fixture that's loaded. - - -ProposalKind -~~~~~~~~~~~~ - -A conference, even within a section, may have different kinds of -presentations, e.g. talks, panels, tutorials, posters. - -If these have different requirements for what fields should be in the -proposal form, they should be modeled as different ``ProposalKind``s. For -example, you may want talk proposals to include an intended audience level -but not require that for poster submissions. - -Note that if you have different deadlines, reviews, etc. you'll want to -distinguish the **section** as well as the kind. - -This model is currently managed by conference staff via the Django admin -although given it's part of "conference setup", it may often just be a -fixture that's loaded. - - -ProposalBase -~~~~~~~~~~~~ - -Each proposal kind should have a subclass of ``ProposalBase`` defining the -fields for proposals of that kind. We discuss below how that association is -made. - -``ProposalBase`` provides fields for a ``title``, a single-paragraph -plain-text ``description`` and an ``abstract`` which can contain markup. - -There is also an ``additional_notes`` field which can be used for speakers to -communicate additional information about their proposal to reviewers that is -not intended to be shared with others. - -This base model supports each proposal having multiple speakers (although -the submitting speaker is always treated differently) and also supports -the attachments of supporting documents for reviewers that are, like the -``additional_notes`` not intended to be shared with others. - -A ``cancelled`` boolean field is also provided to indicate that a proposal -has been cancelled or withdrawn. - - -AdditionalSpeaker -~~~~~~~~~~~~~~~~~ - -Used for modeling the additional speakers on a proposal in additional to the -submitting speaker. The status of an additional speaker may be ``Pending``, -``Accepted`` or ``Declined``. - -.. todo:: see note in speakers docs about explaining the flow - - -SupportingDocument -~~~~~~~~~~~~~~~~~~ - -Used for modeling the supporting documents that can be attached to a proposal. - - -How to Add Custom Proposal Kinds --------------------------------- - -For each kind: - - * create a ``ProposalKind`` instance - * subclass ``ProposalBase`` and add the fields you want - * define a Django ``ModelForm`` for proposals of that kind - * make sure your settings file has a ``PROPOSAL_FORMS`` dictionary - that maps the slug of your ``ProposalKind`` to the fully-qualified - name of your ``ModelForm``. - -For example:: - - PROPOSAL_FORMS = { - "tutorial": "pycon.forms.PyConTutorialProposalForm", - "talk": "pycon.forms.PyConTalkProposalForm", - "poster": "pycon.forms.PyConPosterProposalForm", - } - diff --git a/docs/schedule.rst b/docs/schedule.rst deleted file mode 100644 index 15d37fa1..00000000 --- a/docs/schedule.rst +++ /dev/null @@ -1,49 +0,0 @@ -Schedule App -=========== - -The ``schedule`` app allows staff members to create the schedule for the -conference's presentations, breaks, lunches, etc. - -The ```schedule``` app has a number of models that facilitate building the -structured schedule: - - * Schedule: A high level container that maps to each Conference Section. - * Day: A Day associated with a Schedule. - * Room: A Room associated with a Schedule. - * Slot Kind: A type of Slot associated with a Schedule. - * Slot: A discrete time period for a Schedule. - * Slot Room: A mapping of a Room and Slot for a given Schedule. - * Presentation: A mapping of a Slot to an approved Proposal from the ```proposals``` app. - -Schedule Builder Form ---------------------- - -It can be cumbersome to generate a schedule through the admin. With that in mind, -a generic schedule builder is available via a Schedule's edit view. For instance, -if a Conference site has a Talks Section and Schedule, the form would be -available for Staff at:: - -/schedule/talks/edit - -The form consumes a structured CSV file, from which it will build the schedule. -Sample CSV data is included below:: - -"date","time_start","time_end","kind"," room " -"12/12/2013","10:00 AM","11:00 AM","plenary","Room2" -"12/12/2013","10:00 AM","11:00 AM","plenary","Room1" -"12/12/2013","11:00 AM","12:00 PM","talk","Room1" -"12/12/2013","11:00 AM","12:00 PM","talk","Room2" -"12/12/2013","12:00 PM","12:45 PM","plenary","Room1" -"12/12/2013","12:00 PM","12:45 PM","plenary","Room2" -"12/13/2013","10:00 AM","11:00 AM","plenary","Room2" -"12/13/2013","10:00 AM","11:00 AM","plenary","Room1" -"12/13/2013","11:00 AM","12:00 PM","talk","Room1" -"12/13/2013","11:00 AM","12:00 PM","talk","Room2" -"12/13/2013","12:00 PM","12:45 PM","plenary","Room1" -"12/13/2013","12:00 PM","12:45 PM","plenary","Room2" - -It is worth noting that this generates the **structure** of the schedule. It -does not create Presentation objects. This will need to be done manually. - -One can also **delete** an existing schedule via the delete action. This is -irreversible (save for a database restore). diff --git a/docs/speakers.rst b/docs/speakers.rst deleted file mode 100644 index affaa198..00000000 --- a/docs/speakers.rst +++ /dev/null @@ -1,22 +0,0 @@ -Speaker App -=========== - -The ``speaker`` app allows speakers to set up their profile, prior to or as -part of the proposal submission phase. The **dashboard** is the means through -which speakers manage their own profiles. - -We are planning to make the Speaker model more pluggable so, if you have -particular fields you'd like your speakers to fill out, you'll be able to -customize things more easily. - -Additional Speakers -------------------- - -Because ``symposion`` supports additional speakers being attached to a -proposal or actual presentation, it has the notion of a ``Speaker`` that is -not yet a ``User`` on the site. For this reason, a ``Speaker`` may have a -NULL ``user`` field (hopefully temporarily) as well as an ``invite_email`` -and ``invite_token`` field for the invitation sent to the additional speaker -to join. - -.. todo:: perhaps explain the invitation flow diff --git a/docs/sponsorship.rst b/docs/sponsorship.rst deleted file mode 100644 index 5907a1a3..00000000 --- a/docs/sponsorship.rst +++ /dev/null @@ -1,82 +0,0 @@ -Sponsorship App -=============== - -Sponsorship is managed via the ``sponsorship`` app. - -Sponsorship levels and sponsors are added via the Django admin. - - -Models ------- - -Each sponsor level has a ``name`` (e.g. "Gold", "Silver") and an ``order`` -field which is an integer that is used to sort levels (lowest first). Each -level also has a ``description`` which is not currently exposed anywhere -but can be used for private annotation. - -Each sponsor has a ``name``, ``external_url`` (i.e. link to the sponsor's -website), ``contact_name`` and ``contact_email``, ``logo``, and ``level``. - -A sponsor may also have a private ``annotation`` that can be used by -organizers to take notes about the sponsor. - -A sponsor will not appear on the site until the ``active`` flag is set true. - - -Template Snippets ------------------ - -The easiest way to include sponsor logos, grouped by level, is to either:: - - {% include "sponsorship/_vertical_by_level.html" %} - -or:: - - {% include "sponsorship/_horizontal_by_level.html" %} - -You can get a wall of sponsors (without level designation) with:: - - {% include "sponsorship/_wall.html" %} - - -You can always tweak these templates or use them as the basis for your own. -This is often all you'll need to do to display sponsors on your site. - -If you want to display a specific sponsor logo you can use:: - - {% include "sponsorship/_sponsor_link.html" with sponsor=sponsor %} - -or:: - - {% include "sponsorship/_sponsor_link.html" with sponsor=sponsor dimensions="100x100" %} - -if you want different dimensions than the default 150 x 150. - - -Template Tags -------------- - -If you want to retrieve the sponsors and traverse them yourself, you can use -the provided template tags:: - - {% load sponsorship_tags %} - - {% sponsors as all_sponsors %} - -or:: - - {% load sponsorship_tags %} - - {% sponsors "Gold" as gold_sponsors %} - -if you want to just get a specific level. - - -You can get the levels with:: - - {% load sponsorship_tags %} - - {% sponsor_levels as levels %} - -and you can always iterate over those levels, calling ``level.sponsors`` to -get the sponsors at that level. diff --git a/requirements/base.txt b/requirements/base.txt deleted file mode 100644 index 830c9b29..00000000 --- a/requirements/base.txt +++ /dev/null @@ -1,12 +0,0 @@ -Django==1.9.7 -django-appconf==1.0.1 -django-model-utils>=2.6.1 -django-reversion==1.10.1 -django-sitetree>=1.7.0 -django-taggit==0.18.0 -django-timezone-field>=2.0 -easy-thumbnails==2.3 -bleach -markdown==2.6.5 -pytz==2015.7 -django-ical==1.4 diff --git a/requirements/docs.txt b/requirements/docs.txt deleted file mode 100644 index 366ee161..00000000 --- a/requirements/docs.txt +++ /dev/null @@ -1,3 +0,0 @@ -# requirements needed to build the docs - -Sphinx==1.1.3 diff --git a/setup.py b/setup.py deleted file mode 100644 index e19058cb..00000000 --- a/setup.py +++ /dev/null @@ -1,36 +0,0 @@ -#!/usr/bin/env python -import os -from setuptools import setup, find_packages - -import symposion - - -def read_file(filename): - """Read a file into a string.""" - path = os.path.abspath(os.path.dirname(__file__)) - filepath = os.path.join(path, filename) - try: - return open(filepath).read() - except IOError: - return '' - - -setup( - name="symposion", - author="James Tauber", - author_email="jtauber@jtauber.com", - version=symposion.__version__, - description="A collection of Django apps for conference websites.", - url="http://eldarion.com/symposion/", - packages=find_packages(), - include_package_data=True, - classifiers=( - "Development Status :: 4 - Beta", - "Programming Language :: Python :: 3 :: Only", - "Framework :: Django", - "Intended Audience :: Developers", - "Natural Language :: English", - "License :: OSI Approved :: MIT License", - ), - install_requires=read_file("requirements/base.txt").splitlines(), -) diff --git a/tox.ini b/tox.ini deleted file mode 100644 index db9b7171..00000000 --- a/tox.ini +++ /dev/null @@ -1,3 +0,0 @@ -[flake8] -ignore = E501,E265 -exclude = migrations diff --git a/LICENSE b/vendor/symposion/LICENSE similarity index 100% rename from LICENSE rename to vendor/symposion/LICENSE diff --git a/symposion/__init__.py b/vendor/symposion/__init__.py similarity index 100% rename from symposion/__init__.py rename to vendor/symposion/__init__.py diff --git a/symposion/conf.py b/vendor/symposion/conf.py similarity index 100% rename from symposion/conf.py rename to vendor/symposion/conf.py diff --git a/symposion/conference/__init__.py b/vendor/symposion/conference/__init__.py similarity index 100% rename from symposion/conference/__init__.py rename to vendor/symposion/conference/__init__.py diff --git a/symposion/conference/admin.py b/vendor/symposion/conference/admin.py similarity index 100% rename from symposion/conference/admin.py rename to vendor/symposion/conference/admin.py diff --git a/symposion/conference/apps.py b/vendor/symposion/conference/apps.py similarity index 100% rename from symposion/conference/apps.py rename to vendor/symposion/conference/apps.py diff --git a/symposion/conference/migrations/0001_initial.py b/vendor/symposion/conference/migrations/0001_initial.py similarity index 100% rename from symposion/conference/migrations/0001_initial.py rename to vendor/symposion/conference/migrations/0001_initial.py diff --git a/symposion/conference/migrations/__init__.py b/vendor/symposion/conference/migrations/__init__.py similarity index 100% rename from symposion/conference/migrations/__init__.py rename to vendor/symposion/conference/migrations/__init__.py diff --git a/symposion/conference/models.py b/vendor/symposion/conference/models.py similarity index 100% rename from symposion/conference/models.py rename to vendor/symposion/conference/models.py diff --git a/symposion/conference/urls.py b/vendor/symposion/conference/urls.py similarity index 100% rename from symposion/conference/urls.py rename to vendor/symposion/conference/urls.py diff --git a/symposion/conference/views.py b/vendor/symposion/conference/views.py similarity index 100% rename from symposion/conference/views.py rename to vendor/symposion/conference/views.py diff --git a/symposion/constants.py b/vendor/symposion/constants.py similarity index 100% rename from symposion/constants.py rename to vendor/symposion/constants.py diff --git a/symposion/locale/en/LC_MESSAGES/django.mo b/vendor/symposion/locale/en/LC_MESSAGES/django.mo similarity index 100% rename from symposion/locale/en/LC_MESSAGES/django.mo rename to vendor/symposion/locale/en/LC_MESSAGES/django.mo diff --git a/symposion/locale/en/LC_MESSAGES/django.po b/vendor/symposion/locale/en/LC_MESSAGES/django.po similarity index 100% rename from symposion/locale/en/LC_MESSAGES/django.po rename to vendor/symposion/locale/en/LC_MESSAGES/django.po diff --git a/symposion/locale/ja/LC_MESSAGES/django.mo b/vendor/symposion/locale/ja/LC_MESSAGES/django.mo similarity index 100% rename from symposion/locale/ja/LC_MESSAGES/django.mo rename to vendor/symposion/locale/ja/LC_MESSAGES/django.mo diff --git a/symposion/locale/ja/LC_MESSAGES/django.po b/vendor/symposion/locale/ja/LC_MESSAGES/django.po similarity index 100% rename from symposion/locale/ja/LC_MESSAGES/django.po rename to vendor/symposion/locale/ja/LC_MESSAGES/django.po diff --git a/symposion/models.py b/vendor/symposion/models.py similarity index 100% rename from symposion/models.py rename to vendor/symposion/models.py diff --git a/symposion/proposals/__init__.py b/vendor/symposion/proposals/__init__.py similarity index 100% rename from symposion/proposals/__init__.py rename to vendor/symposion/proposals/__init__.py diff --git a/symposion/proposals/actions.py b/vendor/symposion/proposals/actions.py similarity index 100% rename from symposion/proposals/actions.py rename to vendor/symposion/proposals/actions.py diff --git a/symposion/proposals/admin.py b/vendor/symposion/proposals/admin.py similarity index 100% rename from symposion/proposals/admin.py rename to vendor/symposion/proposals/admin.py diff --git a/symposion/proposals/apps.py b/vendor/symposion/proposals/apps.py similarity index 100% rename from symposion/proposals/apps.py rename to vendor/symposion/proposals/apps.py diff --git a/symposion/proposals/forms.py b/vendor/symposion/proposals/forms.py similarity index 100% rename from symposion/proposals/forms.py rename to vendor/symposion/proposals/forms.py diff --git a/symposion/proposals/migrations/0001_initial.py b/vendor/symposion/proposals/migrations/0001_initial.py similarity index 100% rename from symposion/proposals/migrations/0001_initial.py rename to vendor/symposion/proposals/migrations/0001_initial.py diff --git a/symposion/proposals/migrations/__init__.py b/vendor/symposion/proposals/migrations/__init__.py similarity index 100% rename from symposion/proposals/migrations/__init__.py rename to vendor/symposion/proposals/migrations/__init__.py diff --git a/symposion/proposals/models.py b/vendor/symposion/proposals/models.py similarity index 100% rename from symposion/proposals/models.py rename to vendor/symposion/proposals/models.py diff --git a/symposion/proposals/templatetags/__init__.py b/vendor/symposion/proposals/templatetags/__init__.py similarity index 100% rename from symposion/proposals/templatetags/__init__.py rename to vendor/symposion/proposals/templatetags/__init__.py diff --git a/symposion/proposals/templatetags/proposal_tags.py b/vendor/symposion/proposals/templatetags/proposal_tags.py similarity index 100% rename from symposion/proposals/templatetags/proposal_tags.py rename to vendor/symposion/proposals/templatetags/proposal_tags.py diff --git a/symposion/proposals/urls.py b/vendor/symposion/proposals/urls.py similarity index 100% rename from symposion/proposals/urls.py rename to vendor/symposion/proposals/urls.py diff --git a/symposion/proposals/views.py b/vendor/symposion/proposals/views.py similarity index 100% rename from symposion/proposals/views.py rename to vendor/symposion/proposals/views.py diff --git a/symposion/reviews/__init__.py b/vendor/symposion/reviews/__init__.py similarity index 100% rename from symposion/reviews/__init__.py rename to vendor/symposion/reviews/__init__.py diff --git a/symposion/reviews/admin.py b/vendor/symposion/reviews/admin.py similarity index 100% rename from symposion/reviews/admin.py rename to vendor/symposion/reviews/admin.py diff --git a/symposion/reviews/apps.py b/vendor/symposion/reviews/apps.py similarity index 100% rename from symposion/reviews/apps.py rename to vendor/symposion/reviews/apps.py diff --git a/symposion/reviews/context_processors.py b/vendor/symposion/reviews/context_processors.py similarity index 100% rename from symposion/reviews/context_processors.py rename to vendor/symposion/reviews/context_processors.py diff --git a/symposion/reviews/forms.py b/vendor/symposion/reviews/forms.py similarity index 100% rename from symposion/reviews/forms.py rename to vendor/symposion/reviews/forms.py diff --git a/symposion/reviews/management/__init__.py b/vendor/symposion/reviews/management/__init__.py similarity index 100% rename from symposion/reviews/management/__init__.py rename to vendor/symposion/reviews/management/__init__.py diff --git a/symposion/reviews/management/commands/__init__.py b/vendor/symposion/reviews/management/commands/__init__.py similarity index 100% rename from symposion/reviews/management/commands/__init__.py rename to vendor/symposion/reviews/management/commands/__init__.py diff --git a/symposion/reviews/management/commands/assign_reviewers.py b/vendor/symposion/reviews/management/commands/assign_reviewers.py similarity index 100% rename from symposion/reviews/management/commands/assign_reviewers.py rename to vendor/symposion/reviews/management/commands/assign_reviewers.py diff --git a/symposion/reviews/management/commands/calculate_results.py b/vendor/symposion/reviews/management/commands/calculate_results.py similarity index 100% rename from symposion/reviews/management/commands/calculate_results.py rename to vendor/symposion/reviews/management/commands/calculate_results.py diff --git a/symposion/reviews/management/commands/create_review_permissions.py b/vendor/symposion/reviews/management/commands/create_review_permissions.py similarity index 100% rename from symposion/reviews/management/commands/create_review_permissions.py rename to vendor/symposion/reviews/management/commands/create_review_permissions.py diff --git a/symposion/reviews/management/commands/promoteproposals.py b/vendor/symposion/reviews/management/commands/promoteproposals.py similarity index 100% rename from symposion/reviews/management/commands/promoteproposals.py rename to vendor/symposion/reviews/management/commands/promoteproposals.py diff --git a/symposion/reviews/migrations/0001_initial.py b/vendor/symposion/reviews/migrations/0001_initial.py similarity index 100% rename from symposion/reviews/migrations/0001_initial.py rename to vendor/symposion/reviews/migrations/0001_initial.py diff --git a/symposion/reviews/migrations/__init__.py b/vendor/symposion/reviews/migrations/__init__.py similarity index 100% rename from symposion/reviews/migrations/__init__.py rename to vendor/symposion/reviews/migrations/__init__.py diff --git a/symposion/reviews/models.py b/vendor/symposion/reviews/models.py similarity index 100% rename from symposion/reviews/models.py rename to vendor/symposion/reviews/models.py diff --git a/symposion/reviews/templatetags/__init__.py b/vendor/symposion/reviews/templatetags/__init__.py similarity index 100% rename from symposion/reviews/templatetags/__init__.py rename to vendor/symposion/reviews/templatetags/__init__.py diff --git a/symposion/reviews/templatetags/review_tags.py b/vendor/symposion/reviews/templatetags/review_tags.py similarity index 100% rename from symposion/reviews/templatetags/review_tags.py rename to vendor/symposion/reviews/templatetags/review_tags.py diff --git a/symposion/reviews/urls.py b/vendor/symposion/reviews/urls.py similarity index 100% rename from symposion/reviews/urls.py rename to vendor/symposion/reviews/urls.py diff --git a/symposion/reviews/utils.py b/vendor/symposion/reviews/utils.py similarity index 100% rename from symposion/reviews/utils.py rename to vendor/symposion/reviews/utils.py diff --git a/symposion/reviews/views.py b/vendor/symposion/reviews/views.py similarity index 100% rename from symposion/reviews/views.py rename to vendor/symposion/reviews/views.py diff --git a/symposion/schedule/__init__.py b/vendor/symposion/schedule/__init__.py similarity index 100% rename from symposion/schedule/__init__.py rename to vendor/symposion/schedule/__init__.py diff --git a/symposion/schedule/admin.py b/vendor/symposion/schedule/admin.py similarity index 100% rename from symposion/schedule/admin.py rename to vendor/symposion/schedule/admin.py diff --git a/symposion/schedule/apps.py b/vendor/symposion/schedule/apps.py similarity index 100% rename from symposion/schedule/apps.py rename to vendor/symposion/schedule/apps.py diff --git a/symposion/schedule/forms.py b/vendor/symposion/schedule/forms.py similarity index 100% rename from symposion/schedule/forms.py rename to vendor/symposion/schedule/forms.py diff --git a/symposion/schedule/helpers.py b/vendor/symposion/schedule/helpers.py similarity index 100% rename from symposion/schedule/helpers.py rename to vendor/symposion/schedule/helpers.py diff --git a/symposion/schedule/migrations/0001_initial.py b/vendor/symposion/schedule/migrations/0001_initial.py similarity index 100% rename from symposion/schedule/migrations/0001_initial.py rename to vendor/symposion/schedule/migrations/0001_initial.py diff --git a/symposion/schedule/migrations/0002_presentation_unpublish.py b/vendor/symposion/schedule/migrations/0002_presentation_unpublish.py similarity index 100% rename from symposion/schedule/migrations/0002_presentation_unpublish.py rename to vendor/symposion/schedule/migrations/0002_presentation_unpublish.py diff --git a/symposion/schedule/migrations/0003_auto_20161113_1530.py b/vendor/symposion/schedule/migrations/0003_auto_20161113_1530.py similarity index 100% rename from symposion/schedule/migrations/0003_auto_20161113_1530.py rename to vendor/symposion/schedule/migrations/0003_auto_20161113_1530.py diff --git a/symposion/schedule/migrations/0003_slot_exclusive.py b/vendor/symposion/schedule/migrations/0003_slot_exclusive.py similarity index 100% rename from symposion/schedule/migrations/0003_slot_exclusive.py rename to vendor/symposion/schedule/migrations/0003_slot_exclusive.py diff --git a/symposion/schedule/migrations/0004_merge.py b/vendor/symposion/schedule/migrations/0004_merge.py similarity index 100% rename from symposion/schedule/migrations/0004_merge.py rename to vendor/symposion/schedule/migrations/0004_merge.py diff --git a/symposion/schedule/migrations/0005_auto_20161210_1736.py b/vendor/symposion/schedule/migrations/0005_auto_20161210_1736.py similarity index 100% rename from symposion/schedule/migrations/0005_auto_20161210_1736.py rename to vendor/symposion/schedule/migrations/0005_auto_20161210_1736.py diff --git a/symposion/schedule/migrations/0006_room_track.py b/vendor/symposion/schedule/migrations/0006_room_track.py similarity index 100% rename from symposion/schedule/migrations/0006_room_track.py rename to vendor/symposion/schedule/migrations/0006_room_track.py diff --git a/symposion/schedule/migrations/0007_auto_20161224_1709.py b/vendor/symposion/schedule/migrations/0007_auto_20161224_1709.py similarity index 100% rename from symposion/schedule/migrations/0007_auto_20161224_1709.py rename to vendor/symposion/schedule/migrations/0007_auto_20161224_1709.py diff --git a/symposion/schedule/migrations/__init__.py b/vendor/symposion/schedule/migrations/__init__.py similarity index 100% rename from symposion/schedule/migrations/__init__.py rename to vendor/symposion/schedule/migrations/__init__.py diff --git a/symposion/schedule/models.py b/vendor/symposion/schedule/models.py similarity index 100% rename from symposion/schedule/models.py rename to vendor/symposion/schedule/models.py diff --git a/symposion/schedule/tests/__init__.py b/vendor/symposion/schedule/tests/__init__.py similarity index 100% rename from symposion/schedule/tests/__init__.py rename to vendor/symposion/schedule/tests/__init__.py diff --git a/symposion/schedule/tests/data/schedule.csv b/vendor/symposion/schedule/tests/data/schedule.csv similarity index 100% rename from symposion/schedule/tests/data/schedule.csv rename to vendor/symposion/schedule/tests/data/schedule.csv diff --git a/symposion/schedule/tests/data/schedule_overlap.csv b/vendor/symposion/schedule/tests/data/schedule_overlap.csv similarity index 100% rename from symposion/schedule/tests/data/schedule_overlap.csv rename to vendor/symposion/schedule/tests/data/schedule_overlap.csv diff --git a/symposion/schedule/tests/factories.py b/vendor/symposion/schedule/tests/factories.py similarity index 100% rename from symposion/schedule/tests/factories.py rename to vendor/symposion/schedule/tests/factories.py diff --git a/symposion/schedule/tests/runtests.py b/vendor/symposion/schedule/tests/runtests.py similarity index 100% rename from symposion/schedule/tests/runtests.py rename to vendor/symposion/schedule/tests/runtests.py diff --git a/symposion/schedule/tests/test_forms.py b/vendor/symposion/schedule/tests/test_forms.py similarity index 100% rename from symposion/schedule/tests/test_forms.py rename to vendor/symposion/schedule/tests/test_forms.py diff --git a/symposion/schedule/tests/test_views.py b/vendor/symposion/schedule/tests/test_views.py similarity index 100% rename from symposion/schedule/tests/test_views.py rename to vendor/symposion/schedule/tests/test_views.py diff --git a/symposion/schedule/tests/test_views_session.py b/vendor/symposion/schedule/tests/test_views_session.py similarity index 100% rename from symposion/schedule/tests/test_views_session.py rename to vendor/symposion/schedule/tests/test_views_session.py diff --git a/symposion/schedule/timetable.py b/vendor/symposion/schedule/timetable.py similarity index 100% rename from symposion/schedule/timetable.py rename to vendor/symposion/schedule/timetable.py diff --git a/symposion/schedule/urls.py b/vendor/symposion/schedule/urls.py similarity index 100% rename from symposion/schedule/urls.py rename to vendor/symposion/schedule/urls.py diff --git a/symposion/schedule/views.py b/vendor/symposion/schedule/views.py similarity index 100% rename from symposion/schedule/views.py rename to vendor/symposion/schedule/views.py diff --git a/symposion/speakers/__init__.py b/vendor/symposion/speakers/__init__.py similarity index 100% rename from symposion/speakers/__init__.py rename to vendor/symposion/speakers/__init__.py diff --git a/symposion/speakers/admin.py b/vendor/symposion/speakers/admin.py similarity index 100% rename from symposion/speakers/admin.py rename to vendor/symposion/speakers/admin.py diff --git a/symposion/speakers/apps.py b/vendor/symposion/speakers/apps.py similarity index 100% rename from symposion/speakers/apps.py rename to vendor/symposion/speakers/apps.py diff --git a/symposion/speakers/forms.py b/vendor/symposion/speakers/forms.py similarity index 100% rename from symposion/speakers/forms.py rename to vendor/symposion/speakers/forms.py diff --git a/symposion/speakers/management/__init__.py b/vendor/symposion/speakers/management/__init__.py similarity index 100% rename from symposion/speakers/management/__init__.py rename to vendor/symposion/speakers/management/__init__.py diff --git a/symposion/speakers/management/commands/__init__.py b/vendor/symposion/speakers/management/commands/__init__.py similarity index 100% rename from symposion/speakers/management/commands/__init__.py rename to vendor/symposion/speakers/management/commands/__init__.py diff --git a/symposion/speakers/management/commands/export_speaker_data.py b/vendor/symposion/speakers/management/commands/export_speaker_data.py similarity index 100% rename from symposion/speakers/management/commands/export_speaker_data.py rename to vendor/symposion/speakers/management/commands/export_speaker_data.py diff --git a/symposion/speakers/migrations/0001_initial.py b/vendor/symposion/speakers/migrations/0001_initial.py similarity index 100% rename from symposion/speakers/migrations/0001_initial.py rename to vendor/symposion/speakers/migrations/0001_initial.py diff --git a/symposion/speakers/migrations/0002_auto_20161230_1900.py b/vendor/symposion/speakers/migrations/0002_auto_20161230_1900.py similarity index 100% rename from symposion/speakers/migrations/0002_auto_20161230_1900.py rename to vendor/symposion/speakers/migrations/0002_auto_20161230_1900.py diff --git a/symposion/speakers/migrations/__init__.py b/vendor/symposion/speakers/migrations/__init__.py similarity index 100% rename from symposion/speakers/migrations/__init__.py rename to vendor/symposion/speakers/migrations/__init__.py diff --git a/symposion/speakers/models.py b/vendor/symposion/speakers/models.py similarity index 100% rename from symposion/speakers/models.py rename to vendor/symposion/speakers/models.py diff --git a/symposion/speakers/urls.py b/vendor/symposion/speakers/urls.py similarity index 100% rename from symposion/speakers/urls.py rename to vendor/symposion/speakers/urls.py diff --git a/symposion/speakers/views.py b/vendor/symposion/speakers/views.py similarity index 100% rename from symposion/speakers/views.py rename to vendor/symposion/speakers/views.py diff --git a/symposion/sponsorship/__init__.py b/vendor/symposion/sponsorship/__init__.py similarity index 100% rename from symposion/sponsorship/__init__.py rename to vendor/symposion/sponsorship/__init__.py diff --git a/symposion/sponsorship/admin.py b/vendor/symposion/sponsorship/admin.py similarity index 100% rename from symposion/sponsorship/admin.py rename to vendor/symposion/sponsorship/admin.py diff --git a/symposion/sponsorship/apps.py b/vendor/symposion/sponsorship/apps.py similarity index 100% rename from symposion/sponsorship/apps.py rename to vendor/symposion/sponsorship/apps.py diff --git a/symposion/sponsorship/forms.py b/vendor/symposion/sponsorship/forms.py similarity index 100% rename from symposion/sponsorship/forms.py rename to vendor/symposion/sponsorship/forms.py diff --git a/symposion/sponsorship/management/__init__.py b/vendor/symposion/sponsorship/management/__init__.py similarity index 100% rename from symposion/sponsorship/management/__init__.py rename to vendor/symposion/sponsorship/management/__init__.py diff --git a/symposion/sponsorship/management/commands/__init__.py b/vendor/symposion/sponsorship/management/commands/__init__.py similarity index 100% rename from symposion/sponsorship/management/commands/__init__.py rename to vendor/symposion/sponsorship/management/commands/__init__.py diff --git a/symposion/sponsorship/management/commands/export_sponsors_data.py b/vendor/symposion/sponsorship/management/commands/export_sponsors_data.py similarity index 100% rename from symposion/sponsorship/management/commands/export_sponsors_data.py rename to vendor/symposion/sponsorship/management/commands/export_sponsors_data.py diff --git a/symposion/sponsorship/management/commands/reset_sponsor_benefits.py b/vendor/symposion/sponsorship/management/commands/reset_sponsor_benefits.py similarity index 100% rename from symposion/sponsorship/management/commands/reset_sponsor_benefits.py rename to vendor/symposion/sponsorship/management/commands/reset_sponsor_benefits.py diff --git a/symposion/sponsorship/managers.py b/vendor/symposion/sponsorship/managers.py similarity index 100% rename from symposion/sponsorship/managers.py rename to vendor/symposion/sponsorship/managers.py diff --git a/symposion/sponsorship/migrations/0001_initial.py b/vendor/symposion/sponsorship/migrations/0001_initial.py similarity index 100% rename from symposion/sponsorship/migrations/0001_initial.py rename to vendor/symposion/sponsorship/migrations/0001_initial.py diff --git a/symposion/sponsorship/migrations/__init__.py b/vendor/symposion/sponsorship/migrations/__init__.py similarity index 100% rename from symposion/sponsorship/migrations/__init__.py rename to vendor/symposion/sponsorship/migrations/__init__.py diff --git a/symposion/sponsorship/models.py b/vendor/symposion/sponsorship/models.py similarity index 100% rename from symposion/sponsorship/models.py rename to vendor/symposion/sponsorship/models.py diff --git a/symposion/sponsorship/templatetags/__init__.py b/vendor/symposion/sponsorship/templatetags/__init__.py similarity index 100% rename from symposion/sponsorship/templatetags/__init__.py rename to vendor/symposion/sponsorship/templatetags/__init__.py diff --git a/symposion/sponsorship/templatetags/sponsorship_tags.py b/vendor/symposion/sponsorship/templatetags/sponsorship_tags.py similarity index 100% rename from symposion/sponsorship/templatetags/sponsorship_tags.py rename to vendor/symposion/sponsorship/templatetags/sponsorship_tags.py diff --git a/symposion/sponsorship/tests.py b/vendor/symposion/sponsorship/tests.py similarity index 100% rename from symposion/sponsorship/tests.py rename to vendor/symposion/sponsorship/tests.py diff --git a/symposion/sponsorship/urls.py b/vendor/symposion/sponsorship/urls.py similarity index 100% rename from symposion/sponsorship/urls.py rename to vendor/symposion/sponsorship/urls.py diff --git a/symposion/sponsorship/views.py b/vendor/symposion/sponsorship/views.py similarity index 100% rename from symposion/sponsorship/views.py rename to vendor/symposion/sponsorship/views.py diff --git a/symposion/static/datatables/js/dataTables.bootstrap.js b/vendor/symposion/static/datatables/js/dataTables.bootstrap.js similarity index 100% rename from symposion/static/datatables/js/dataTables.bootstrap.js rename to vendor/symposion/static/datatables/js/dataTables.bootstrap.js diff --git a/symposion/static/datatables/js/jquery.dataTables.min.js b/vendor/symposion/static/datatables/js/jquery.dataTables.min.js similarity index 100% rename from symposion/static/datatables/js/jquery.dataTables.min.js rename to vendor/symposion/static/datatables/js/jquery.dataTables.min.js diff --git a/symposion/static/tabletools/js/TableTools.js b/vendor/symposion/static/tabletools/js/TableTools.js similarity index 100% rename from symposion/static/tabletools/js/TableTools.js rename to vendor/symposion/static/tabletools/js/TableTools.js diff --git a/symposion/static/tabletools/js/TableTools.min.js b/vendor/symposion/static/tabletools/js/TableTools.min.js similarity index 100% rename from symposion/static/tabletools/js/TableTools.min.js rename to vendor/symposion/static/tabletools/js/TableTools.min.js diff --git a/symposion/static/tabletools/js/TableTools.min.js.gz b/vendor/symposion/static/tabletools/js/TableTools.min.js.gz similarity index 100% rename from symposion/static/tabletools/js/TableTools.min.js.gz rename to vendor/symposion/static/tabletools/js/TableTools.min.js.gz diff --git a/symposion/static/tabletools/js/ZeroClipboard.js b/vendor/symposion/static/tabletools/js/ZeroClipboard.js similarity index 100% rename from symposion/static/tabletools/js/ZeroClipboard.js rename to vendor/symposion/static/tabletools/js/ZeroClipboard.js diff --git a/symposion/static/tabletools/swf/copy_csv_xls.swf b/vendor/symposion/static/tabletools/swf/copy_csv_xls.swf similarity index 100% rename from symposion/static/tabletools/swf/copy_csv_xls.swf rename to vendor/symposion/static/tabletools/swf/copy_csv_xls.swf diff --git a/symposion/static/tabletools/swf/copy_csv_xls_pdf.swf b/vendor/symposion/static/tabletools/swf/copy_csv_xls_pdf.swf similarity index 100% rename from symposion/static/tabletools/swf/copy_csv_xls_pdf.swf rename to vendor/symposion/static/tabletools/swf/copy_csv_xls_pdf.swf diff --git a/symposion/teams/__init__.py b/vendor/symposion/teams/__init__.py similarity index 100% rename from symposion/teams/__init__.py rename to vendor/symposion/teams/__init__.py diff --git a/symposion/teams/admin.py b/vendor/symposion/teams/admin.py similarity index 100% rename from symposion/teams/admin.py rename to vendor/symposion/teams/admin.py diff --git a/symposion/teams/backends.py b/vendor/symposion/teams/backends.py similarity index 100% rename from symposion/teams/backends.py rename to vendor/symposion/teams/backends.py diff --git a/symposion/teams/forms.py b/vendor/symposion/teams/forms.py similarity index 100% rename from symposion/teams/forms.py rename to vendor/symposion/teams/forms.py diff --git a/symposion/teams/migrations/0001_initial.py b/vendor/symposion/teams/migrations/0001_initial.py similarity index 100% rename from symposion/teams/migrations/0001_initial.py rename to vendor/symposion/teams/migrations/0001_initial.py diff --git a/symposion/teams/migrations/__init__.py b/vendor/symposion/teams/migrations/__init__.py similarity index 100% rename from symposion/teams/migrations/__init__.py rename to vendor/symposion/teams/migrations/__init__.py diff --git a/symposion/teams/models.py b/vendor/symposion/teams/models.py similarity index 100% rename from symposion/teams/models.py rename to vendor/symposion/teams/models.py diff --git a/symposion/teams/templatetags/__init__.py b/vendor/symposion/teams/templatetags/__init__.py similarity index 100% rename from symposion/teams/templatetags/__init__.py rename to vendor/symposion/teams/templatetags/__init__.py diff --git a/symposion/teams/templatetags/teams_tags.py b/vendor/symposion/teams/templatetags/teams_tags.py similarity index 100% rename from symposion/teams/templatetags/teams_tags.py rename to vendor/symposion/teams/templatetags/teams_tags.py diff --git a/symposion/teams/urls.py b/vendor/symposion/teams/urls.py similarity index 100% rename from symposion/teams/urls.py rename to vendor/symposion/teams/urls.py diff --git a/symposion/teams/views.py b/vendor/symposion/teams/views.py similarity index 100% rename from symposion/teams/views.py rename to vendor/symposion/teams/views.py diff --git a/symposion/text_parser.py b/vendor/symposion/text_parser.py similarity index 100% rename from symposion/text_parser.py rename to vendor/symposion/text_parser.py diff --git a/symposion/utils/__init__.py b/vendor/symposion/utils/__init__.py similarity index 100% rename from symposion/utils/__init__.py rename to vendor/symposion/utils/__init__.py diff --git a/symposion/utils/mail.py b/vendor/symposion/utils/mail.py similarity index 100% rename from symposion/utils/mail.py rename to vendor/symposion/utils/mail.py diff --git a/symposion/views.py b/vendor/symposion/views.py similarity index 100% rename from symposion/views.py rename to vendor/symposion/views.py