Changeset - 6d0e5bc50884
[Not reviewed]
Merge
4 16 20
Patrick Altman - 10 years ago 2014-12-15 22:08:12
paltman@gmail.com
Merge branch 'pyohio-master'
39 files changed with 1533 insertions and 248 deletions:
setup.py
13
2
0 comments (0 inline, 0 general)
README.rst
Show inline comments
 
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
 
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
requirements/base.txt
Show inline comments
 
new file 100644
 
Django>=1.5,<=1.6
 
django-appconf==0.5
 
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-timezones==0.2
 
django-user-accounts==1.0b13
 
easy-thumbnails==1.4
 
html5lib==0.95
 
markdown==2.3.1
requirements/docs.txt
Show inline comments
 
file renamed from requirements-docs.txt to requirements/docs.txt
setup.py
Show inline comments
 
#!/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(exclude=["symposion_project"]),
 
    packages=find_packages(),
 
    include_package_data=True,
 
    classifiers=(
 
        "Development Status :: 4 - Beta",
 
        "Programming Language :: Python",
 
        "Framework :: Django",
 
        "Intended Audience :: Developers",
 
        "Natural Language :: English",
 
        "License :: OSI Approved :: MIT License",
 
    ),
 
    install_requires=read_file("requirements/base.txt").splitlines(),
 
)
symposion/boxes/migrations/0001_initial.py
Show inline comments
 
new file 100644
 
# -*- 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
symposion/boxes/migrations/__init__.py
Show inline comments
 
new file 100644
symposion/boxes/models.py
Show inline comments
 
from django.db import models
 

	
 
from django.contrib.auth.models import User
 

	
 
import reversion
 

	
 
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"
 

	
 

	
 
reversion.register(Box)
symposion/boxes/urls.py
Show inline comments
 
from django.conf.urls import url, patterns
 
from django.conf.urls import patterns, url
 

	
 

	
 
urlpatterns = patterns(
 
    "symposion.boxes.views",
 
    url(r"^([-\w]+)/edit/$", "box_edit", name="box_edit"),
 
)
symposion/cms/migrations/0001_initial.py
Show inline comments
 
new file 100644
 
# -*- 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
symposion/cms/migrations/__init__.py
Show inline comments
 
new file 100644
symposion/conference/migrations/0001_initial.py
Show inline comments
 
new file 100644
 
# -*- 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
symposion/conference/migrations/__init__.py
Show inline comments
 
new file 100644
symposion/conference/models.py
Show inline comments
 
from django.db import models
 
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 = {}
 

	
 

	
 
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()
 
        try:
 
            del CONFERENCE_CACHE[pk]
 
        except KeyError:
 
            pass
 

	
 
    class Meta(object):
 
        verbose_name = _("conference")
 
        verbose_name_plural = _("conferences")
 

	
 

	
 
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, 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")
 
        ordering = ["start_date"]
 

	
 

	
 
def current_conference():
 
    from django.conf import settings
 
    try:
 
        conf_id = settings.CONFERENCE_ID
 
    except AttributeError:
 
        from django.core.exceptions import ImproperlyConfigured
 
        raise ImproperlyConfigured("You must set the CONFERENCE_ID setting.")
 
    try:
 
        current_conf = CONFERENCE_CACHE[conf_id]
 
    except KeyError:
 
        current_conf = Conference.objects.get(pk=conf_id)
 
        CONFERENCE_CACHE[conf_id] = current_conf
 
    return current_conf
symposion/proposals/actions.py
Show inline comments
 
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(".", "_")
 
        response = HttpResponse(content_type="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
symposion/proposals/migrations/0001_initial.py
Show inline comments
 
new file 100644
 
# -*- 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']
symposion/proposals/migrations/__init__.py
Show inline comments
 
new file 100644
symposion/proposals/urls.py
Show inline comments
 
from django.conf.urls import patterns, 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+)/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+)/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"),
 
)
symposion/proposals/views.py
Show inline comments
 
import hashlib
 
import random
 
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
 
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
 

	
 
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():
 
        messages.info(request, "To submit a proposal, please "
 
                      "<a href='{0}'>log in</a> 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 "
 
                          "<a href='{0}'>create a speaker "
 
                          "profile</a>.".format(url))
 
            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")
 

	
 
    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():
 
            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 = sha1(str(random.random())).hexdigest()[:5]
 
                    token = sha1(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,
 
                    )
 
                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()
 
            if hasattr(proposal, "reviews"):
 
                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 = {
 
                        "user": request.user,
 
                        "proposal": proposal,
 
                    }
 
                    send_email(
 
                        [user.email], "proposal_updated",
 
                        context=ctx
 
                    )
 
            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()
 

	
 
    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(
 
                        proposal=proposal
 
                    ).exclude(
 
                        user=request.user
 
                    ).distinct().values_list("user", flat=True)
 
                )
 

	
 
                for reviewer in reviewers:
 
                    ctx = {
 
                        "proposal": proposal,
 
                        "message": message,
 
                        "reviewer": True,
 
                    }
 
                    send_email(
 
                        [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
 
    })
 

	
 

	
 
@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("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 proposal.cancelled:
 
        return HttpResponseForbidden()
 

	
 
    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 getattr(settings, "USE_X_ACCEL_REDIRECT", False):
 
        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)
symposion/reviews/fixture_gen.py
Show inline comments
 
deleted file
symposion/reviews/migrations/0001_initial.py
Show inline comments
 
new file 100644
 
# -*- 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
symposion/reviews/migrations/__init__.py
Show inline comments
 
new file 100644
symposion/reviews/tests.py
Show inline comments
 
deleted file
symposion/reviews/urls.py
Show inline comments
 
from django.conf.urls import patterns, url
 

	
 

	
 
urlpatterns = patterns(
 
    "symposion.reviews.views",
 
    url(r"^section/(?P<section_slug>[\w\-]+)/all/$", "review_section",
 
        {"reviewed": "all"}, name="review_section"),
 
    url(r"^section/(?P<section_slug>[\w\-]+)/reviewed/$", "review_section",
 
        {"reviewed": "reviewed"}, name="user_reviewed"),
 
    url(r"^section/(?P<section_slug>[\w\-]+)/not_reviewed/$", "review_section",
 
        {"reviewed": "not_reviewed"}, name="user_not_reviewed"),
 
    url(r"^section/(?P<section_slug>[\w\-]+)/assignments/$", "review_section",
 
        {"assigned": True}, name="review_section_assignments"),
 
    url(r"^section/(?P<section_slug>[\w\-]+)/status/$", "review_status",
 
        name="review_status"),
 
    url(r"^section/(?P<section_slug>[\w\-]+)/status/(?P<key>\w+)/$",
 
        "review_status", name="review_status"),
 
    url(r"^section/(?P<section_slug>[\w\-]+)/list/(?P<user_pk>\d+)/$",
 
        "review_list", name="review_list_user"),
 
    url(r"^section/(?P<section_slug>[\w\-]+)/admin/$", "review_admin",
 
        name="review_admin"),
 
    url(r"^section/(?P<section_slug>[\w\-]+)/admin/accept/$",
 
        "review_bulk_accept", name="review_bulk_accept"),
 
    url(r"^section/(?P<section_slug>[\w\-]+)/notification/(?P<status>\w+)/$",
 
        "result_notification", name="result_notification"),
 
    url(r"^section/(?P<section_slug>[\w\-]+)/notification/(?P<status>\w+)/prepare/$",
 
        "result_notification_prepare", name="result_notification_prepare"),
 
    url(r"^section/(?P<section_slug>[\w\-]+)/notification/(?P<status>\w+)/send/$",
 
        "result_notification_send", name="result_notification_send"),
 
    url(r"^section/(?P<section_slug>[\w\-]+)/all/$", "review_section", {"reviewed": "all"}, name="review_section"),
 
    url(r"^section/(?P<section_slug>[\w\-]+)/reviewed/$", "review_section", {"reviewed": "reviewed"}, name="user_reviewed"),
 
    url(r"^section/(?P<section_slug>[\w\-]+)/not_reviewed/$", "review_section", {"reviewed": "not_reviewed"}, name="user_not_reviewed"),
 
    url(r"^section/(?P<section_slug>[\w\-]+)/assignments/$", "review_section", {"assigned": True}, name="review_section_assignments"),
 
    url(r"^section/(?P<section_slug>[\w\-]+)/status/$", "review_status", name="review_status"),
 
    url(r"^section/(?P<section_slug>[\w\-]+)/status/(?P<key>\w+)/$", "review_status", name="review_status"),
 
    url(r"^section/(?P<section_slug>[\w\-]+)/list/(?P<user_pk>\d+)/$", "review_list", name="review_list_user"),
 
    url(r"^section/(?P<section_slug>[\w\-]+)/admin/$", "review_admin", name="review_admin"),
 
    url(r"^section/(?P<section_slug>[\w\-]+)/admin/accept/$", "review_bulk_accept", name="review_bulk_accept"),
 
    url(r"^section/(?P<section_slug>[\w\-]+)/notification/(?P<status>\w+)/$", "result_notification", name="result_notification"),
 
    url(r"^section/(?P<section_slug>[\w\-]+)/notification/(?P<status>\w+)/prepare/$", "result_notification_prepare", name="result_notification_prepare"),
 
    url(r"^section/(?P<section_slug>[\w\-]+)/notification/(?P<status>\w+)/send/$", "result_notification_send", name="result_notification_send"),
 

	
 
    url(r"^review/(?P<pk>\d+)/$", "review_detail", name="review_detail"),
 

	
 
    url(r"^(?P<pk>\d+)/delete/$", "review_delete", name="review_delete"),
 
    url(r"^assignments/$", "review_assignments", name="review_assignments"),
 
    url(r"^assignment/(?P<pk>\d+)/opt-out/$", "review_assignment_opt_out",
 
        name="review_assignment_opt_out"),
 
)
symposion/reviews/views.py
Show inline comments
 
from django.core.mail import send_mass_mail
 
from django.db.models import Q
 
from django.http import HttpResponseBadRequest, HttpResponseNotAllowed
 
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 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
 
from symposion.reviews.forms import BulkPresentationForm
 
from symposion.reviews.models import (
 
    ReviewAssignment, Review, LatestVote, ProposalResult, NotificationTemplate,
 
    ResultNotification
 
)
 

	
 

	
 
def access_not_permitted(request):
 
    return render(request, "reviews/access_not_permitted.html")
 

	
 

	
 
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
 
        obj.plus_zero = obj.result.plus_zero
 
        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
 
@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)
 
    queryset = ProposalBase.objects.filter(kind__section=section.section)
 

	
 
    if assigned:
 
        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":
 
        queryset = queryset.select_related("result").select_subclasses()
 
        reviewed = "all_reviews"
 
    elif reviewed == "reviewed":
 
        queryset = queryset.filter(reviews__user=request.user)
 
        reviewed = "user_reviewed"
 
    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,
 
    }
 
    return render(request, "reviews/review_list.html", ctx)
 

	
 

	
 
@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
 
                ).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()
 

	
 
                yield user
 

	
 
    ctx = {
 
        "section_slug": section_slug,
 
        "reviewers": reviewers(),
 
    }
 
    return render(request, "reviews/review_admin.html", ctx)
 

	
 

	
 
# FIXME: This view is too complex according to flake8
 
@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 = {
 
                            "proposal": proposal,
 
                            "message": message,
 
                            "reviewer": False,
 
                        }
 
                        send_email(
 
                            [speaker.email], "proposal_new_message",
 
                            context=ctx
 
                        )
 

	
 
                return redirect(request.path)
 
            else:
 
                initial = {}
 
                if latest_vote:
 
                    initial["vote"] = latest_vote.vote
 
                if request.user in speakers:
 
                    review_form = None
 
                else:
 
                    review_form = ReviewForm(initial=initial)
 
        elif "result_submit" in request.POST:
 
            if admin:
 
                result = request.POST["result_submit"]
 

	
 
                if result == "accept":
 
                    proposal.result.status = "accepted"
 
                    proposal.result.save()
 
                elif result == "reject":
 
                    proposal.result.status = "rejected"
 
                    proposal.result.save()
 
                elif result == "undecide":
 
                    proposal.result.status = "undecided"
 
                    proposal.result.save()
 
                elif result == "standby":
 
                    proposal.result.status = "standby"
 
                    proposal.result.save()
 

	
 
            return redirect(request.path)
 
    else:
 
        initial = {}
 
        if latest_vote:
 
            initial["vote"] = latest_vote.vote
 
        if request.user in speakers:
 
            review_form = None
 
        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,
 
        "reviews": reviews,
 
        "review_messages": messages,
 
        "review_form": review_form,
 
        "message_form": message_form
 
    })
 

	
 

	
 
@login_required
 
@require_POST
 
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 fewer than VOTE_THRESHOLD reviews