Massively upgrade symposion

* Remove markitup (to be replaced with Ace editor)
* Use DUA decorators
* Removed custom signup bits
* Upgraded dependencies
* Added migrations
* Namespaced template locations
* Removed html5parser/sanitizer (for now) - parsing functionality
should be moved out entirely to a hooks
* Replaced ProposalScoreExpression object with a  function that returns
F() expressions
This commit is contained in:
Patrick Altman 2015-10-16 12:36:58 -05:00
parent 8b4282a48e
commit 11f697d137
36 changed files with 821 additions and 222 deletions

View file

@ -1,13 +1,12 @@
Django>=1.7.1 Django==1.8.5
django-appconf==0.6 django-appconf==1.0.1
django-markitup==2.2.2 django-model-utils==2.3.1
django-model-utils==2.2 django-reversion==1.9.3
django-reversion==1.8.5 django-sitetree==1.4.0
django-sitetree==1.2.1 django-taggit==0.17.1
django-taggit==0.12.2 django-timezone-field==1.3
django-timezone-field==1.2 django-user-accounts==1.2.0
django-user-accounts==1.0
easy-thumbnails==2.2 easy-thumbnails==2.2
html5lib==0.999 html5lib==0.9999999
markdown==2.5.2 markdown==2.6.2
pytz==2014.10 pytz==2015.6

View file

@ -0,0 +1,44 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import timezone_field.fields
class Migration(migrations.Migration):
dependencies = [
]
operations = [
migrations.CreateModel(
name='Conference',
fields=[
('id', models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=100, verbose_name='Title')),
('start_date', models.DateField(null=True, blank=True, verbose_name='Start date')),
('end_date', models.DateField(null=True, blank=True, verbose_name='End date')),
('timezone', timezone_field.fields.TimeZoneField(blank=True, verbose_name='timezone')),
],
options={
'verbose_name_plural': 'conferences',
'verbose_name': 'conference',
},
),
migrations.CreateModel(
name='Section',
fields=[
('id', models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=100, verbose_name='Name')),
('slug', models.SlugField(verbose_name='Slug')),
('start_date', models.DateField(null=True, blank=True, verbose_name='Start date')),
('end_date', models.DateField(null=True, blank=True, verbose_name='End date')),
('conference', models.ForeignKey(to='symposion_conference.Conference', verbose_name='Conference')),
],
options={
'ordering': ['start_date'],
'verbose_name_plural': 'sections',
'verbose_name': 'section',
},
),
]

View file

@ -1,9 +1,10 @@
from django.http import Http404 from django.http import Http404
from django.shortcuts import render from django.shortcuts import render
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User from django.contrib.auth.models import User
from account.decorators import login_required
@login_required @login_required
def user_list(request): def user_list(request):
@ -11,6 +12,6 @@ def user_list(request):
if not request.user.is_staff: if not request.user.is_staff:
raise Http404() raise Http404()
return render(request, "conference/user_list.html", { return render(request, "symposion/conference/user_list.html", {
"users": User.objects.all(), "users": User.objects.all(),
}) })

View file

@ -1,58 +0,0 @@
try:
from collections import OrderedDict
except ImportError:
OrderedDict = None
from django import forms
import account.forms
from django.utils.translation import ugettext_lazy as _
class SignupForm(account.forms.SignupForm):
first_name = forms.CharField(label=_("First name"))
last_name = forms.CharField(label=_("Last name"))
email_confirm = forms.EmailField(label=_("Confirm Email"))
def __init__(self, *args, **kwargs):
super(SignupForm, self).__init__(*args, **kwargs)
key_order = [
"email",
"email_confirm",
"first_name",
"last_name",
"password",
"password_confirm"
]
self.fields = reorder_fields(self.fields, key_order)
def clean_email_confirm(self):
email = self.cleaned_data.get("email")
email_confirm = self.cleaned_data["email_confirm"]
if email:
if email != email_confirm:
raise forms.ValidationError(
_("Email address must match previously typed email address"))
return email_confirm
def reorder_fields(fields, order):
"""Reorder form fields by order, removing items not in order.
>>> reorder_fields(
... OrderedDict([('a', 1), ('b', 2), ('c', 3)]),
... ['b', 'c', 'a'])
OrderedDict([('b', 2), ('c', 3), ('a', 1)])
"""
for key, v in fields.items():
if key not in order:
del fields[key]
if not OrderedDict or hasattr(fields, "keyOrder"):
# fields is SortedDict
fields.keyOrder.sort(key=lambda k: order.index(k[0]))
return fields
else:
# fields is OrderedDict
return OrderedDict(sorted(fields.items(), key=lambda k: order.index(k[0])))

View file

@ -10,8 +10,9 @@ def parse(text):
text = markdown.markdown(text, extensions=["extra"], safe_mode=False) text = markdown.markdown(text, extensions=["extra"], safe_mode=False)
# Sanitize using html5lib # Sanitize using html5lib
bits = [] # bits = []
parser = html5parser.HTMLParser(tokenizer=sanitizer.HTMLSanitizer) # parser = html5parser.HTMLParser(tokenizer=sanitizer.HTMLSanitizer)
for token in parser.parseFragment(text).childNodes: # for token in parser.parseFragment(text).childNodes:
bits.append(token.toxml()) # bits.append(token.toxml())
return "".join(bits) # return "".join(bits)
return text

View file

@ -4,7 +4,6 @@ from django.db.models import Q
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from symposion.proposals.models import SupportingDocument from symposion.proposals.models import SupportingDocument
# from markitup.widgets import MarkItUpWidget
# @@@ generic proposal form # @@@ generic proposal form

View file

@ -0,0 +1,100 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
from django.conf import settings
import django.utils.timezone
import symposion.proposals.models
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('symposion_speakers', '__first__'),
('symposion_conference', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='AdditionalSpeaker',
fields=[
('id', models.AutoField(verbose_name='ID', primary_key=True, serialize=False, auto_created=True)),
('status', models.IntegerField(verbose_name='Status', default=1, choices=[(1, 'Pending'), (2, 'Accepted'), (3, 'Declined')])),
],
options={
'verbose_name': 'Addtional speaker',
'verbose_name_plural': 'Additional speakers',
},
),
migrations.CreateModel(
name='ProposalBase',
fields=[
('id', models.AutoField(verbose_name='ID', primary_key=True, serialize=False, auto_created=True)),
('title', models.CharField(verbose_name='Title', max_length=100)),
('description', models.TextField(verbose_name='Brief Description', max_length=400, help_text='If your proposal is accepted this will be made public and printed in the program. Should be one paragraph, maximum 400 characters.')),
('abstract', models.TextField(verbose_name='Detailed Abstract', help_text="Detailed outline. Will be made public if your proposal is accepted. Edit using <a href='http://daringfireball.net/projects/markdown/basics' target='_blank'>Markdown</a>.")),
('abstract_html', models.TextField(blank=True)),
('additional_notes', models.TextField(blank=True, verbose_name='Addtional Notes', help_text="Anything else you'd like the program committee to know when making their selection: your past experience, etc. This is not made public. Edit using <a href='http://daringfireball.net/projects/markdown/basics' target='_blank'>Markdown</a>.")),
('additional_notes_html', models.TextField(blank=True)),
('submitted', models.DateTimeField(editable=False, default=django.utils.timezone.now, verbose_name='Submitted')),
('cancelled', models.BooleanField(verbose_name='Cancelled', default=False)),
('additional_speakers', models.ManyToManyField(blank=True, verbose_name='Addtional speakers', through='symposion_proposals.AdditionalSpeaker', to='symposion_speakers.Speaker')),
],
),
migrations.CreateModel(
name='ProposalKind',
fields=[
('id', models.AutoField(verbose_name='ID', primary_key=True, serialize=False, auto_created=True)),
('name', models.CharField(verbose_name='Name', max_length=100)),
('slug', models.SlugField(verbose_name='Slug')),
('section', models.ForeignKey(to='symposion_conference.Section', verbose_name='Section', related_name='proposal_kinds')),
],
),
migrations.CreateModel(
name='ProposalSection',
fields=[
('id', models.AutoField(verbose_name='ID', primary_key=True, serialize=False, auto_created=True)),
('start', models.DateTimeField(blank=True, verbose_name='Start', null=True)),
('end', models.DateTimeField(blank=True, verbose_name='End', null=True)),
('closed', models.NullBooleanField(verbose_name='Closed')),
('published', models.NullBooleanField(verbose_name='Published')),
('section', models.OneToOneField(to='symposion_conference.Section', verbose_name='Section')),
],
),
migrations.CreateModel(
name='SupportingDocument',
fields=[
('id', models.AutoField(verbose_name='ID', primary_key=True, serialize=False, auto_created=True)),
('created_at', models.DateTimeField(verbose_name='Created at', default=django.utils.timezone.now)),
('file', models.FileField(verbose_name='File', upload_to=symposion.proposals.models.uuid_filename)),
('description', models.CharField(verbose_name='Description', max_length=140)),
('proposal', models.ForeignKey(to='symposion_proposals.ProposalBase', verbose_name='Proposal', related_name='supporting_documents')),
('uploaded_by', models.ForeignKey(to=settings.AUTH_USER_MODEL, verbose_name='Uploaded by')),
],
),
migrations.AddField(
model_name='proposalbase',
name='kind',
field=models.ForeignKey(to='symposion_proposals.ProposalKind', verbose_name='Kind'),
),
migrations.AddField(
model_name='proposalbase',
name='speaker',
field=models.ForeignKey(to='symposion_speakers.Speaker', verbose_name='Speaker', related_name='proposals'),
),
migrations.AddField(
model_name='additionalspeaker',
name='proposalbase',
field=models.ForeignKey(to='symposion_proposals.ProposalBase', verbose_name='Proposalbase'),
),
migrations.AddField(
model_name='additionalspeaker',
name='speaker',
field=models.ForeignKey(to='symposion_speakers.Speaker', verbose_name='Speaker'),
),
migrations.AlterUniqueTogether(
name='additionalspeaker',
unique_together=set([('speaker', 'proposalbase')]),
),
]

View file

@ -15,10 +15,9 @@ from django.core.exceptions import ValidationError
import reversion import reversion
from markitup.fields import MarkupField
from model_utils.managers import InheritanceManager from model_utils.managers import InheritanceManager
from symposion.markdown_parser import parse
from symposion.conference.models import Section from symposion.conference.models import Section
from symposion.speakers.models import Speaker from symposion.speakers.models import Speaker
@ -94,13 +93,14 @@ class ProposalBase(models.Model):
help_text=_("If your proposal is accepted this will be made public and printed in the " help_text=_("If your proposal is accepted this will be made public and printed in the "
"program. Should be one paragraph, maximum 400 characters.") "program. Should be one paragraph, maximum 400 characters.")
) )
abstract = MarkupField( abstract = models.TextField(
_("Detailed Abstract"), _("Detailed Abstract"),
help_text=_("Detailed outline. Will be made public if your proposal is accepted. Edit " help_text=_("Detailed outline. Will be made public if your proposal is accepted. Edit "
"using <a href='http://daringfireball.net/projects/markdown/basics' " "using <a href='http://daringfireball.net/projects/markdown/basics' "
"target='_blank'>Markdown</a>.") "target='_blank'>Markdown</a>.")
) )
additional_notes = MarkupField( abstract_html = models.TextField(blank=True)
additional_notes = models.TextField(
_("Addtional Notes"), _("Addtional Notes"),
blank=True, blank=True,
help_text=_("Anything else you'd like the program committee to know when making their " help_text=_("Anything else you'd like the program committee to know when making their "
@ -108,6 +108,7 @@ class ProposalBase(models.Model):
"<a href='http://daringfireball.net/projects/markdown/basics' " "<a href='http://daringfireball.net/projects/markdown/basics' "
"target='_blank'>Markdown</a>.") "target='_blank'>Markdown</a>.")
) )
additional_notes_html = models.TextField(blank=True)
submitted = models.DateTimeField( submitted = models.DateTimeField(
default=now, default=now,
editable=False, editable=False,
@ -115,6 +116,9 @@ class ProposalBase(models.Model):
) )
speaker = models.ForeignKey(Speaker, related_name="proposals", verbose_name=_("Speaker")) speaker = models.ForeignKey(Speaker, related_name="proposals", verbose_name=_("Speaker"))
# @@@ this validation used to exist as a validators keyword on additional_speakers
# M2M field but that is no longer supported by Django. Should be moved to
# the form level
def additional_speaker_validator(self, a_speaker): def additional_speaker_validator(self, a_speaker):
if a_speaker.speaker.email == self.speaker.email: if a_speaker.speaker.email == self.speaker.email:
raise ValidationError(_("%s is same as primary speaker.") % a_speaker.speaker.email) raise ValidationError(_("%s is same as primary speaker.") % a_speaker.speaker.email)
@ -122,10 +126,14 @@ class ProposalBase(models.Model):
raise ValidationError(_("%s has already been in speakers.") % a_speaker.speaker.email) raise ValidationError(_("%s has already been in speakers.") % a_speaker.speaker.email)
additional_speakers = models.ManyToManyField(Speaker, through="AdditionalSpeaker", additional_speakers = models.ManyToManyField(Speaker, through="AdditionalSpeaker",
blank=True, verbose_name=_("Addtional speakers"), blank=True, verbose_name=_("Addtional speakers"))
validators=[additional_speaker_validator])
cancelled = models.BooleanField(default=False, verbose_name=_("Cancelled")) cancelled = models.BooleanField(default=False, verbose_name=_("Cancelled"))
def save(self, *args, **kwargs):
self.abstract_html = parse(self.abstract)
self.additional_notes_html = parse(self.additional_notes)
return super(ProposalBase, self).save(*args, **kwargs)
def can_edit(self): def can_edit(self):
return True return True

View file

@ -13,11 +13,12 @@ from django.views import static
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.contrib.auth.decorators import login_required
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from account.decorators import login_required
from account.models import EmailAddress from account.models import EmailAddress
from symposion.proposals.models import ( from symposion.proposals.models import (
ProposalBase, ProposalSection, ProposalKind ProposalBase, ProposalSection, ProposalKind
) )
@ -58,7 +59,7 @@ def proposal_submit(request):
for kind in proposal_section.section.proposal_kinds.all(): for kind in proposal_section.section.proposal_kinds.all():
kinds.append(kind) kinds.append(kind)
return render(request, "proposals/proposal_submit.html", { return render(request, "symposion/proposals/proposal_submit.html", {
"kinds": kinds, "kinds": kinds,
}) })
@ -95,9 +96,9 @@ def proposal_submit_kind(request, kind_slug):
else: else:
form = form_class() form = form_class()
return render(request, "proposals/proposal_submit_kind.html", { return render(request, "symposion/proposals/proposal_submit_kind.html", {
"kind": kind, "kind": kind,
"form": form, "proposal_form": form,
}) })
@ -178,7 +179,7 @@ def proposal_speaker_manage(request, pk):
"speakers": proposal.speakers(), "speakers": proposal.speakers(),
"add_speaker_form": add_speaker_form, "add_speaker_form": add_speaker_form,
} }
return render(request, "proposals/proposal_speaker_manage.html", ctx) return render(request, "symposion/proposals/proposal_speaker_manage.html", ctx)
@login_required @login_required
@ -195,7 +196,7 @@ def proposal_edit(request, pk):
"title": "Proposal editing closed", "title": "Proposal editing closed",
"body": "Proposal editing is closed for this session type." "body": "Proposal editing is closed for this session type."
} }
return render(request, "proposals/proposal_error.html", ctx) return render(request, "symposion/proposals/proposal_error.html", ctx)
form_class = get_form(settings.PROPOSAL_FORMS[proposal.kind.slug]) form_class = get_form(settings.PROPOSAL_FORMS[proposal.kind.slug])
@ -223,7 +224,7 @@ def proposal_edit(request, pk):
else: else:
form = form_class(instance=proposal) form = form_class(instance=proposal)
return render(request, "proposals/proposal_edit.html", { return render(request, "symposion/proposals/proposal_edit.html", {
"proposal": proposal, "proposal": proposal,
"form": form, "form": form,
}) })
@ -276,7 +277,7 @@ def proposal_detail(request, pk):
else: else:
message_form = None message_form = None
return render(request, "proposals/proposal_detail.html", { return render(request, "symposion/proposals/proposal_detail.html", {
"proposal": proposal, "proposal": proposal,
"message_form": message_form "message_form": message_form
}) })
@ -298,7 +299,7 @@ def proposal_cancel(request, pk):
messages.success(request, "%s has been cancelled" % proposal.title) messages.success(request, "%s has been cancelled" % proposal.title)
return redirect("dashboard") return redirect("dashboard")
return render(request, "proposals/proposal_cancel.html", { return render(request, "symposion/proposals/proposal_cancel.html", {
"proposal": proposal, "proposal": proposal,
}) })
@ -321,7 +322,7 @@ def proposal_leave(request, pk):
ctx = { ctx = {
"proposal": proposal, "proposal": proposal,
} }
return render(request, "proposals/proposal_leave.html", ctx) return render(request, "symposion/proposals/proposal_leave.html", ctx)
@login_required @login_required
@ -372,7 +373,7 @@ def document_create(request, proposal_pk):
else: else:
form = SupportingDocumentCreateForm() form = SupportingDocumentCreateForm()
return render(request, "proposals/document_create.html", { return render(request, "symposion/proposals/document_create.html", {
"proposal": proposal, "proposal": proposal,
"form": form, "form": form,
}) })

View file

@ -2,8 +2,6 @@ from __future__ import unicode_literals
from django import forms from django import forms
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from markitup.widgets import MarkItUpWidget
from symposion.reviews.models import Review, Comment, ProposalMessage, VOTES from symposion.reviews.models import Review, Comment, ProposalMessage, VOTES
@ -11,7 +9,6 @@ class ReviewForm(forms.ModelForm):
class Meta: class Meta:
model = Review model = Review
fields = ["vote", "comment"] fields = ["vote", "comment"]
widgets = {"comment": MarkItUpWidget()}
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(ReviewForm, self).__init__(*args, **kwargs) super(ReviewForm, self).__init__(*args, **kwargs)
@ -25,14 +22,12 @@ class ReviewCommentForm(forms.ModelForm):
class Meta: class Meta:
model = Comment model = Comment
fields = ["text"] fields = ["text"]
widgets = {"text": MarkItUpWidget()}
class SpeakerCommentForm(forms.ModelForm): class SpeakerCommentForm(forms.ModelForm):
class Meta: class Meta:
model = ProposalMessage model = ProposalMessage
fields = ["message"] fields = ["message"]
widgets = {"message": MarkItUpWidget()}
class BulkPresentationForm(forms.Form): class BulkPresentationForm(forms.Form):

View file

@ -0,0 +1,143 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import django.db.models.deletion
from django.conf import settings
from decimal import Decimal
import datetime
class Migration(migrations.Migration):
dependencies = [
('symposion_proposals', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Comment',
fields=[
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True, serialize=False)),
('text', models.TextField(verbose_name='Text')),
('text_html', models.TextField(blank=True)),
('public', models.BooleanField(verbose_name='Public', default=False, choices=[(True, 'public'), (False, 'private')])),
('commented_at', models.DateTimeField(verbose_name='Commented at', default=datetime.datetime.now)),
('commenter', models.ForeignKey(verbose_name='Commenter', to=settings.AUTH_USER_MODEL)),
('proposal', models.ForeignKey(to='symposion_proposals.ProposalBase', verbose_name='Proposal', related_name='comments')),
],
options={
'verbose_name_plural': 'comments',
'verbose_name': 'comment',
},
),
migrations.CreateModel(
name='LatestVote',
fields=[
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True, serialize=False)),
('vote', models.CharField(choices=[('+1', '+1 \u2014 Good proposal and I will argue for it to be accepted.'), ('+0', '+0 \u2014 OK proposal, but I will not argue for it to be accepted.'), ('\u22120', '\u22120 \u2014 Weak proposal, but I will not argue strongly against acceptance.'), ('\u22121', '\u22121 \u2014 Serious issues and I will argue to reject this proposal.')], verbose_name='Vote', max_length=2)),
('submitted_at', models.DateTimeField(editable=False, verbose_name='Submitted at', default=datetime.datetime.now)),
('proposal', models.ForeignKey(to='symposion_proposals.ProposalBase', verbose_name='Proposal', related_name='votes')),
('user', models.ForeignKey(verbose_name='User', to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name_plural': 'latest votes',
'verbose_name': 'latest vote',
},
),
migrations.CreateModel(
name='NotificationTemplate',
fields=[
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True, serialize=False)),
('label', models.CharField(verbose_name='Label', max_length=100)),
('from_address', models.EmailField(verbose_name='From address', max_length=254)),
('subject', models.CharField(verbose_name='Subject', max_length=100)),
('body', models.TextField(verbose_name='Body')),
],
options={
'verbose_name_plural': 'notification templates',
'verbose_name': 'notification template',
},
),
migrations.CreateModel(
name='ProposalMessage',
fields=[
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True, serialize=False)),
('message', models.TextField(verbose_name='Message')),
('message_html', models.TextField(blank=True)),
('submitted_at', models.DateTimeField(editable=False, verbose_name='Submitted at', default=datetime.datetime.now)),
('proposal', models.ForeignKey(to='symposion_proposals.ProposalBase', verbose_name='Proposal', related_name='messages')),
('user', models.ForeignKey(verbose_name='User', to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name_plural': 'proposal messages',
'verbose_name': 'proposal message',
'ordering': ['submitted_at'],
},
),
migrations.CreateModel(
name='ProposalResult',
fields=[
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True, serialize=False)),
('score', models.DecimalField(decimal_places=2, verbose_name='Score', max_digits=5, default=Decimal('0.00'))),
('comment_count', models.PositiveIntegerField(verbose_name='Comment count', default=0)),
('vote_count', models.PositiveIntegerField(verbose_name='Vote count', default=0)),
('plus_one', models.PositiveIntegerField(verbose_name='Plus one', default=0)),
('plus_zero', models.PositiveIntegerField(verbose_name='Plus zero', default=0)),
('minus_zero', models.PositiveIntegerField(verbose_name='Minus zero', default=0)),
('minus_one', models.PositiveIntegerField(verbose_name='Minus one', default=0)),
('accepted', models.NullBooleanField(verbose_name='Accepted', default=None, choices=[(True, 'accepted'), (False, 'rejected'), (None, 'undecided')])),
('status', models.CharField(choices=[('accepted', 'accepted'), ('rejected', 'rejected'), ('undecided', 'undecided'), ('standby', 'standby')], verbose_name='Status', max_length=20, default='undecided')),
('proposal', models.OneToOneField(to='symposion_proposals.ProposalBase', verbose_name='Proposal', related_name='result')),
],
options={
'verbose_name_plural': 'proposal_results',
'verbose_name': 'proposal_result',
},
),
migrations.CreateModel(
name='ResultNotification',
fields=[
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True, serialize=False)),
('timestamp', models.DateTimeField(verbose_name='Timestamp', default=datetime.datetime.now)),
('to_address', models.EmailField(verbose_name='To address', max_length=254)),
('from_address', models.EmailField(verbose_name='From address', max_length=254)),
('subject', models.CharField(verbose_name='Subject', max_length=100)),
('body', models.TextField(verbose_name='Body')),
('proposal', models.ForeignKey(to='symposion_proposals.ProposalBase', verbose_name='Proposal', related_name='notifications')),
('template', models.ForeignKey(to='symposion_reviews.NotificationTemplate', blank=True, verbose_name='Template', null=True, on_delete=django.db.models.deletion.SET_NULL)),
],
),
migrations.CreateModel(
name='Review',
fields=[
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True, serialize=False)),
('vote', models.CharField(blank=True, verbose_name='Vote', max_length=2, choices=[('+1', '+1 \u2014 Good proposal and I will argue for it to be accepted.'), ('+0', '+0 \u2014 OK proposal, but I will not argue for it to be accepted.'), ('\u22120', '\u22120 \u2014 Weak proposal, but I will not argue strongly against acceptance.'), ('\u22121', '\u22121 \u2014 Serious issues and I will argue to reject this proposal.')])),
('comment', models.TextField(verbose_name='Comment')),
('comment_html', models.TextField(blank=True)),
('submitted_at', models.DateTimeField(editable=False, verbose_name='Submitted at', default=datetime.datetime.now)),
('proposal', models.ForeignKey(to='symposion_proposals.ProposalBase', verbose_name='Proposal', related_name='reviews')),
('user', models.ForeignKey(verbose_name='User', to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name_plural': 'reviews',
'verbose_name': 'review',
},
),
migrations.CreateModel(
name='ReviewAssignment',
fields=[
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True, serialize=False)),
('origin', models.IntegerField(choices=[(0, 'auto-assigned, initial'), (1, 'opted-in'), (2, 'auto-assigned, later')], verbose_name='Origin')),
('assigned_at', models.DateTimeField(verbose_name='Assigned at', default=datetime.datetime.now)),
('opted_out', models.BooleanField(verbose_name='Opted out', default=False)),
('proposal', models.ForeignKey(verbose_name='Proposal', to='symposion_proposals.ProposalBase')),
('user', models.ForeignKey(verbose_name='User', to=settings.AUTH_USER_MODEL)),
],
),
migrations.AlterUniqueTogether(
name='latestvote',
unique_together=set([('proposal', 'user')]),
),
]

View file

View file

@ -4,26 +4,22 @@ from datetime import datetime
from decimal import Decimal from decimal import Decimal
from django.db import models from django.db import models
from django.db.models import Q from django.db.models import Q, F
from django.db.models.signals import post_save from django.db.models.signals import post_save
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from markitup.fields import MarkupField from symposion.markdown_parser import parse
from symposion.proposals.models import ProposalBase from symposion.proposals.models import ProposalBase
from symposion.schedule.models import Presentation from symposion.schedule.models import Presentation
class ProposalScoreExpression(object): def score_expression():
return (
def as_sql(self, qn, connection=None): (3 * F("plus_one") + F("plus_zero")) -
sql = "((3 * plus_one + plus_zero) - (minus_zero + 3 * minus_one))" (F("minus_zero") + 3 * F("minus_one"))
return sql, [] )
def prepare_database_save(self, unused):
return self
class Votes(object): class Votes(object):
@ -97,9 +93,14 @@ class ProposalMessage(models.Model):
proposal = models.ForeignKey(ProposalBase, related_name="messages", verbose_name=_("Proposal")) proposal = models.ForeignKey(ProposalBase, related_name="messages", verbose_name=_("Proposal"))
user = models.ForeignKey(User, verbose_name=_("User")) user = models.ForeignKey(User, verbose_name=_("User"))
message = MarkupField(verbose_name=_("Message")) message = models.TextField(verbose_name=_("Message"))
message_html = models.TextField(blank=True)
submitted_at = models.DateTimeField(default=datetime.now, editable=False, verbose_name=_("Submitted at")) submitted_at = models.DateTimeField(default=datetime.now, editable=False, verbose_name=_("Submitted at"))
def save(self, *args, **kwargs):
self.message_html = parse(self.message)
return super(ProposalMessage, self).save(*args, **kwargs)
class Meta: class Meta:
ordering = ["submitted_at"] ordering = ["submitted_at"]
verbose_name = _("proposal message") verbose_name = _("proposal message")
@ -115,10 +116,12 @@ class Review(models.Model):
# No way to encode "-0" vs. "+0" into an IntegerField, and I don't feel # No way to encode "-0" vs. "+0" into an IntegerField, and I don't feel
# like some complicated encoding system. # like some complicated encoding system.
vote = models.CharField(max_length=2, blank=True, choices=VOTES.CHOICES, verbose_name=_("Vote")) vote = models.CharField(max_length=2, blank=True, choices=VOTES.CHOICES, verbose_name=_("Vote"))
comment = MarkupField(verbose_name=_("Comment")) comment = models.TextField(verbose_name=_("Comment"))
comment_html = models.TextField(blank=True)
submitted_at = models.DateTimeField(default=datetime.now, editable=False, verbose_name=_("Submitted at")) submitted_at = models.DateTimeField(default=datetime.now, editable=False, verbose_name=_("Submitted at"))
def save(self, **kwargs): def save(self, **kwargs):
self.comment_html = parse(self.comment)
if self.vote: if self.vote:
vote, created = LatestVote.objects.get_or_create( vote, created = LatestVote.objects.get_or_create(
proposal=self.proposal, proposal=self.proposal,
@ -258,7 +261,7 @@ class ProposalResult(models.Model):
vote=VOTES.MINUS_ONE vote=VOTES.MINUS_ONE
).count() ).count()
result.save() result.save()
cls._default_manager.filter(pk=result.pk).update(score=ProposalScoreExpression()) cls._default_manager.filter(pk=result.pk).update(score=score_expression())
def update_vote(self, vote, previous=None, removal=False): def update_vote(self, vote, previous=None, removal=False):
mapping = { mapping = {
@ -287,7 +290,7 @@ class ProposalResult(models.Model):
self.comment_count = models.F("comment_count") + 1 self.comment_count = models.F("comment_count") + 1
self.save() self.save()
model = self.__class__ model = self.__class__
model._default_manager.filter(pk=self.pk).update(score=ProposalScoreExpression()) model._default_manager.filter(pk=self.pk).update(score=score_expression())
class Meta: class Meta:
verbose_name = _("proposal_result") verbose_name = _("proposal_result")
@ -297,7 +300,8 @@ class ProposalResult(models.Model):
class Comment(models.Model): class Comment(models.Model):
proposal = models.ForeignKey(ProposalBase, related_name="comments", verbose_name=_("Proposal")) proposal = models.ForeignKey(ProposalBase, related_name="comments", verbose_name=_("Proposal"))
commenter = models.ForeignKey(User, verbose_name=_("Commenter")) commenter = models.ForeignKey(User, verbose_name=_("Commenter"))
text = MarkupField(verbose_name=_("Text")) text = models.TextField(verbose_name=_("Text"))
text_html = models.TextField(blank=True)
# Or perhaps more accurately, can the user see this comment. # Or perhaps more accurately, can the user see this comment.
public = models.BooleanField(choices=[(True, _("public")), (False, _("private"))], default=False, verbose_name=_("Public")) public = models.BooleanField(choices=[(True, _("public")), (False, _("private"))], default=False, verbose_name=_("Public"))
@ -307,6 +311,10 @@ class Comment(models.Model):
verbose_name = _("comment") verbose_name = _("comment")
verbose_name_plural = _("comments") verbose_name_plural = _("comments")
def save(self, *args, **kwargs):
self.comment_html = parse(self.comment)
return super(Comment, self).save(*args, **kwargs)
class NotificationTemplate(models.Model): class NotificationTemplate(models.Model):

View file

@ -5,11 +5,13 @@ from django.shortcuts import render, redirect, get_object_or_404
from django.template import Context, Template from django.template import Context, Template
from django.views.decorators.http import require_POST from django.views.decorators.http import require_POST
from django.contrib.auth.decorators import login_required from account.decorators import login_required
# @@@ switch to pinax-teams
from symposion.teams.models import Team
from symposion.conf import settings from symposion.conf import settings
from symposion.proposals.models import ProposalBase, ProposalSection from symposion.proposals.models import ProposalBase, ProposalSection
from symposion.teams.models import Team
from symposion.utils.mail import send_email from symposion.utils.mail import send_email
from symposion.reviews.forms import ReviewForm, SpeakerCommentForm from symposion.reviews.forms import ReviewForm, SpeakerCommentForm
@ -21,7 +23,7 @@ from symposion.reviews.models import (
def access_not_permitted(request): def access_not_permitted(request):
return render(request, "reviews/access_not_permitted.html") return render(request, "symposion/reviews/access_not_permitted.html")
def proposals_generator(request, queryset, user_pk=None, check_speaker=True): def proposals_generator(request, queryset, user_pk=None, check_speaker=True):
@ -96,7 +98,7 @@ def review_section(request, section_slug, assigned=False, reviewed="all"):
"reviewed": reviewed, "reviewed": reviewed,
} }
return render(request, "reviews/review_list.html", ctx) return render(request, "symposion/reviews/review_list.html", ctx)
@login_required @login_required
@ -120,7 +122,7 @@ def review_list(request, section_slug, user_pk):
ctx = { ctx = {
"proposals": proposals, "proposals": proposals,
} }
return render(request, "reviews/review_list.html", ctx) return render(request, "symposion/reviews/review_list.html", ctx)
@login_required @login_required
@ -164,7 +166,7 @@ def review_admin(request, section_slug):
"section_slug": section_slug, "section_slug": section_slug,
"reviewers": reviewers(), "reviewers": reviewers(),
} }
return render(request, "reviews/review_admin.html", ctx) return render(request, "symposion/reviews/review_admin.html", ctx)
# FIXME: This view is too complex according to flake8 # FIXME: This view is too complex according to flake8
@ -273,7 +275,7 @@ def review_detail(request, pk):
reviews = Review.objects.filter(proposal=proposal).order_by("-submitted_at") reviews = Review.objects.filter(proposal=proposal).order_by("-submitted_at")
messages = proposal.messages.order_by("submitted_at") messages = proposal.messages.order_by("submitted_at")
return render(request, "reviews/review_detail.html", { return render(request, "symposion/reviews/review_detail.html", {
"proposal": proposal, "proposal": proposal,
"latest_vote": latest_vote, "latest_vote": latest_vote,
"reviews": reviews, "reviews": reviews,
@ -353,7 +355,7 @@ def review_status(request, section_slug=None, key=None):
else: else:
ctx["proposals"] = proposals ctx["proposals"] = proposals
return render(request, "reviews/review_stats.html", ctx) return render(request, "symposion/reviews/review_stats.html", ctx)
@login_required @login_required
@ -364,7 +366,7 @@ def review_assignments(request):
user=request.user, user=request.user,
opted_out=False opted_out=False
) )
return render(request, "reviews/review_assignment.html", { return render(request, "symposion/reviews/review_assignment.html", {
"assignments": assignments, "assignments": assignments,
}) })
@ -398,7 +400,7 @@ def review_bulk_accept(request, section_slug):
else: else:
form = BulkPresentationForm() form = BulkPresentationForm()
return render(request, "reviews/review_bulk_accept.html", { return render(request, "symposion/reviews/review_bulk_accept.html", {
"form": form, "form": form,
}) })
@ -417,7 +419,7 @@ def result_notification(request, section_slug, status):
"proposals": proposals, "proposals": proposals,
"notification_templates": notification_templates, "notification_templates": notification_templates,
} }
return render(request, "reviews/result_notification.html", ctx) return render(request, "symposion/reviews/result_notification.html", ctx)
@login_required @login_required
@ -455,7 +457,7 @@ def result_notification_prepare(request, section_slug, status):
"proposals": proposals, "proposals": proposals,
"proposal_pks": ",".join([str(pk) for pk in proposal_pks]), "proposal_pks": ",".join([str(pk) for pk in proposal_pks]),
} }
return render(request, "reviews/result_notification_prepare.html", ctx) return render(request, "symposion/reviews/result_notification_prepare.html", ctx)
@login_required @login_required

View file

@ -9,8 +9,6 @@ from django.contrib import messages
from django.db import IntegrityError, transaction from django.db import IntegrityError, transaction
from django.db.models import Q from django.db.models import Q
from markitup.widgets import MarkItUpWidget
from symposion.schedule.models import (Day, Presentation, Room, SlotKind, Slot, from symposion.schedule.models import (Day, Presentation, Room, SlotKind, Slot,
SlotRoom) SlotRoom)
@ -44,7 +42,6 @@ class SlotEditForm(forms.Form):
def build_content_override_field(self): def build_content_override_field(self):
kwargs = { kwargs = {
"label": "Content", "label": "Content",
"widget": MarkItUpWidget(),
"required": False, "required": False,
"initial": self.slot.content_override, "initial": self.slot.content_override,
} }

View file

@ -0,0 +1,186 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import datetime
from django.conf import settings
class Migration(migrations.Migration):
dependencies = [
('symposion_speakers', '__first__'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('symposion_conference', '0001_initial'),
('symposion_proposals', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='Day',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('date', models.DateField(verbose_name='Date')),
],
options={
'ordering': ['date'],
'verbose_name': 'date',
'verbose_name_plural': 'dates',
},
),
migrations.CreateModel(
name='Presentation',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('title', models.CharField(max_length=100, verbose_name='Title')),
('description', models.TextField(verbose_name='Description')),
('description_html', models.TextField(blank=True)),
('abstract', models.TextField(verbose_name='Abstract')),
('abstract_html', models.TextField(blank=True)),
('cancelled', models.BooleanField(default=False, verbose_name='Cancelled')),
('additional_speakers', models.ManyToManyField(related_name='copresentations', to='symposion_speakers.Speaker', verbose_name='Additional speakers', blank=True)),
('proposal_base', models.OneToOneField(to='symposion_proposals.ProposalBase', related_name='presentation', verbose_name='Proposal base')),
('section', models.ForeignKey(to='symposion_conference.Section', related_name='presentations', verbose_name='Section')),
],
options={
'ordering': ['slot'],
'verbose_name': 'presentation',
'verbose_name_plural': 'presentations',
},
),
migrations.CreateModel(
name='Room',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('name', models.CharField(max_length=65, verbose_name='Name')),
('order', models.PositiveIntegerField(verbose_name='Order')),
],
options={
'verbose_name': 'Room',
'verbose_name_plural': 'Rooms',
},
),
migrations.CreateModel(
name='Schedule',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('published', models.BooleanField(default=True, verbose_name='Published')),
('hidden', models.BooleanField(default=False, verbose_name='Hide schedule from overall conference view')),
('section', models.OneToOneField(to='symposion_conference.Section', verbose_name='Section')),
],
options={
'ordering': ['section'],
'verbose_name': 'Schedule',
'verbose_name_plural': 'Schedules',
},
),
migrations.CreateModel(
name='Session',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('day', models.ForeignKey(to='symposion_schedule.Day', related_name='sessions', verbose_name='Day')),
],
options={
'verbose_name': 'Session',
'verbose_name_plural': 'Sessions',
},
),
migrations.CreateModel(
name='SessionRole',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('role', models.IntegerField(verbose_name='Role', choices=[(1, 'Session Chair'), (2, 'Session Runner')])),
('status', models.NullBooleanField(verbose_name='Status')),
('submitted', models.DateTimeField(default=datetime.datetime.now)),
('session', models.ForeignKey(to='symposion_schedule.Session', verbose_name='Session')),
('user', models.ForeignKey(to=settings.AUTH_USER_MODEL, verbose_name='User')),
],
options={
'verbose_name': 'Session role',
'verbose_name_plural': 'Session roles',
},
),
migrations.CreateModel(
name='Slot',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('start', models.TimeField(verbose_name='Start')),
('end', models.TimeField(verbose_name='End')),
('content_override', models.TextField(verbose_name='Content override', blank=True)),
('content_override_html', models.TextField(blank=True)),
('day', models.ForeignKey(to='symposion_schedule.Day', verbose_name='Day')),
],
options={
'ordering': ['day', 'start', 'end'],
'verbose_name': 'slot',
'verbose_name_plural': 'slots',
},
),
migrations.CreateModel(
name='SlotKind',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('label', models.CharField(max_length=50, verbose_name='Label')),
('schedule', models.ForeignKey(to='symposion_schedule.Schedule', verbose_name='schedule')),
],
options={
'verbose_name': 'Slot kind',
'verbose_name_plural': 'Slot kinds',
},
),
migrations.CreateModel(
name='SlotRoom',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('room', models.ForeignKey(to='symposion_schedule.Room', verbose_name='Room')),
('slot', models.ForeignKey(to='symposion_schedule.Slot', verbose_name='Slot')),
],
options={
'ordering': ['slot', 'room__order'],
'verbose_name': 'Slot room',
'verbose_name_plural': 'Slot rooms',
},
),
migrations.AddField(
model_name='slot',
name='kind',
field=models.ForeignKey(to='symposion_schedule.SlotKind', verbose_name='Kind'),
),
migrations.AddField(
model_name='session',
name='slots',
field=models.ManyToManyField(related_name='sessions', verbose_name='Slots', to='symposion_schedule.Slot'),
),
migrations.AddField(
model_name='room',
name='schedule',
field=models.ForeignKey(to='symposion_schedule.Schedule', verbose_name='Schedule'),
),
migrations.AddField(
model_name='presentation',
name='slot',
field=models.OneToOneField(to='symposion_schedule.Slot', related_name='content_ptr', blank=True, null=True, verbose_name='Slot'),
),
migrations.AddField(
model_name='presentation',
name='speaker',
field=models.ForeignKey(to='symposion_speakers.Speaker', related_name='presentations', verbose_name='Speaker'),
),
migrations.AddField(
model_name='day',
name='schedule',
field=models.ForeignKey(to='symposion_schedule.Schedule', verbose_name='Schedule'),
),
migrations.AlterUniqueTogether(
name='slotroom',
unique_together=set([('slot', 'room')]),
),
migrations.AlterUniqueTogether(
name='sessionrole',
unique_together=set([('session', 'user', 'role')]),
),
migrations.AlterUniqueTogether(
name='day',
unique_together=set([('schedule', 'date')]),
),
]

View file

@ -8,8 +8,7 @@ from django.db import models
from django.utils.encoding import python_2_unicode_compatible from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from markitup.fields import MarkupField from symposion.markdown_parser import parse
from symposion.proposals.models import ProposalBase from symposion.proposals.models import ProposalBase
from symposion.conference.models import Section from symposion.conference.models import Section
from symposion.speakers.models import Speaker from symposion.speakers.models import Speaker
@ -87,7 +86,12 @@ class Slot(models.Model):
kind = models.ForeignKey(SlotKind, verbose_name=_("Kind")) kind = models.ForeignKey(SlotKind, verbose_name=_("Kind"))
start = models.TimeField(verbose_name=_("Start")) start = models.TimeField(verbose_name=_("Start"))
end = models.TimeField(verbose_name=_("End")) end = models.TimeField(verbose_name=_("End"))
content_override = MarkupField(blank=True, verbose_name=_("Content override")) content_override = models.TextField(blank=True, verbose_name=_("Content override"))
content_override_html = models.TextField(blank=True)
def save(self, *args, **kwargs):
self.content_override_html = parse(self.content_override)
return super(Slot, self).save(*args, **kwargs)
def assign(self, content): def assign(self, content):
""" """
@ -179,8 +183,10 @@ class Presentation(models.Model):
slot = models.OneToOneField(Slot, null=True, blank=True, related_name="content_ptr", verbose_name=_("Slot")) slot = models.OneToOneField(Slot, null=True, blank=True, related_name="content_ptr", verbose_name=_("Slot"))
title = models.CharField(max_length=100, verbose_name=_("Title")) title = models.CharField(max_length=100, verbose_name=_("Title"))
description = MarkupField(verbose_name=_("Description")) description = models.TextField(verbose_name=_("Description"))
abstract = MarkupField(verbose_name=_("Abstract")) description_html = models.TextField(blank=True)
abstract = models.TextField(verbose_name=_("Abstract"))
abstract_html = models.TextField(blank=True)
speaker = models.ForeignKey(Speaker, related_name="presentations", verbose_name=_("Speaker")) speaker = models.ForeignKey(Speaker, related_name="presentations", verbose_name=_("Speaker"))
additional_speakers = models.ManyToManyField(Speaker, related_name="copresentations", additional_speakers = models.ManyToManyField(Speaker, related_name="copresentations",
blank=True, verbose_name=_("Additional speakers")) blank=True, verbose_name=_("Additional speakers"))
@ -188,6 +194,11 @@ class Presentation(models.Model):
proposal_base = models.OneToOneField(ProposalBase, related_name="presentation", verbose_name=_("Proposal base")) proposal_base = models.OneToOneField(ProposalBase, related_name="presentation", verbose_name=_("Proposal base"))
section = models.ForeignKey(Section, related_name="presentations", verbose_name=_("Section")) section = models.ForeignKey(Section, related_name="presentations", verbose_name=_("Section"))
def save(self, *args, **kwargs):
self.description_html = parse(self.description)
self.abstract_html = parse(self.abstract)
return super(Presentation, self).save(*args, **kwargs)
@property @property
def number(self): def number(self):
return self.proposal.number return self.proposal.number

View file

@ -22,7 +22,6 @@ try:
"django.contrib.contenttypes", "django.contrib.contenttypes",
"django.contrib.sites", "django.contrib.sites",
"markitup",
"reversion", "reversion",
"symposion", "symposion",
@ -34,8 +33,6 @@ try:
], ],
SITE_ID=1, SITE_ID=1,
NOSE_ARGS=['-s'], NOSE_ARGS=['-s'],
MARKITUP_FILTER=('django.contrib.markup.templatetags.markup.textile', {}),
) )
try: try:

View file

@ -6,11 +6,12 @@ from django.http import Http404, HttpResponse
from django.shortcuts import render, get_object_or_404, redirect from django.shortcuts import render, get_object_or_404, redirect
from django.template import loader, Context from django.template import loader, Context
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.contrib import messages from django.contrib import messages
from django.contrib.sites.models import Site from django.contrib.sites.models import Site
from account.decorators import login_required
from symposion.schedule.forms import SlotEditForm, ScheduleSectionForm from symposion.schedule.forms import SlotEditForm, ScheduleSectionForm
from symposion.schedule.models import Schedule, Day, Slot, Presentation, Session, SessionRole from symposion.schedule.models import Schedule, Day, Slot, Presentation, Session, SessionRole
from symposion.schedule.timetable import TimeTable from symposion.schedule.timetable import TimeTable
@ -47,7 +48,7 @@ def schedule_conference(request):
ctx = { ctx = {
"sections": sections, "sections": sections,
} }
return render(request, "schedule/schedule_conference.html", ctx) return render(request, "symposion/schedule/schedule_conference.html", ctx)
def schedule_detail(request, slug=None): def schedule_detail(request, slug=None):
@ -63,7 +64,7 @@ def schedule_detail(request, slug=None):
"schedule": schedule, "schedule": schedule,
"days": days, "days": days,
} }
return render(request, "schedule/schedule_detail.html", ctx) return render(request, "symposion/schedule/schedule_detail.html", ctx)
def schedule_list(request, slug=None): def schedule_list(request, slug=None):
@ -76,7 +77,7 @@ def schedule_list(request, slug=None):
"schedule": schedule, "schedule": schedule,
"presentations": presentations, "presentations": presentations,
} }
return render(request, "schedule/schedule_list.html", ctx) return render(request, "symposion/schedule/schedule_list.html", ctx)
def schedule_list_csv(request, slug=None): def schedule_list_csv(request, slug=None):
@ -92,7 +93,7 @@ def schedule_list_csv(request, slug=None):
file_slug = "presentations" file_slug = "presentations"
response["Content-Disposition"] = 'attachment; filename="%s.csv"' % file_slug response["Content-Disposition"] = 'attachment; filename="%s.csv"' % file_slug
response.write(loader.get_template("schedule/schedule_list.csv").render(Context({ response.write(loader.get_template("symposion/schedule/schedule_list.csv").render(Context({
"presentations": presentations, "presentations": presentations,
}))) })))
@ -126,7 +127,7 @@ def schedule_edit(request, slug=None):
"days": days, "days": days,
"form": form "form": form
} }
return render(request, "schedule/schedule_edit.html", ctx) return render(request, "symposion/schedule/schedule_edit.html", ctx)
@login_required @login_required
@ -160,7 +161,7 @@ def schedule_slot_edit(request, slug, slot_pk):
"form": form, "form": form,
"slot": slot, "slot": slot,
} }
return render(request, "schedule/_slot_edit.html", ctx) return render(request, "symposion/schedule/_slot_edit.html", ctx)
def schedule_presentation_detail(request, pk): def schedule_presentation_detail(request, pk):
@ -175,7 +176,7 @@ def schedule_presentation_detail(request, pk):
"presentation": presentation, "presentation": presentation,
"schedule": schedule, "schedule": schedule,
} }
return render(request, "schedule/presentation_detail.html", ctx) return render(request, "symposion/schedule/presentation_detail.html", ctx)
def schedule_json(request): def schedule_json(request):
@ -230,7 +231,7 @@ def schedule_json(request):
data.append(slot_data) data.append(slot_data)
return HttpResponse( return HttpResponse(
json.dumps({'schedule': data}), json.dumps({"schedule": data}),
content_type="application/json" content_type="application/json"
) )
@ -238,7 +239,7 @@ def schedule_json(request):
def session_list(request): def session_list(request):
sessions = Session.objects.all().order_by('pk') sessions = Session.objects.all().order_by('pk')
return render(request, "schedule/session_list.html", { return render(request, "symposion/schedule/session_list.html", {
"sessions": sessions, "sessions": sessions,
}) })
@ -306,7 +307,7 @@ def session_detail(request, session_id):
return redirect("schedule_session_detail", session_id) return redirect("schedule_session_detail", session_id)
return render(request, "schedule/session_detail.html", { return render(request, "symposion/schedule/session_detail.html", {
"session": session, "session": session,
"chair": chair, "chair": chair,
"chair_denied": chair_denied, "chair_denied": chair_denied,

View file

@ -1,8 +1,6 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from django import forms from django import forms
from markitup.widgets import MarkItUpWidget
from symposion.speakers.models import Speaker from symposion.speakers.models import Speaker
@ -15,6 +13,3 @@ class SpeakerForm(forms.ModelForm):
"biography", "biography",
"photo", "photo",
] ]
widgets = {
"biography": MarkItUpWidget(),
}

View file

@ -0,0 +1,36 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import datetime
from django.conf import settings
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Speaker',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, verbose_name='ID', serialize=False)),
('name', models.CharField(verbose_name='Name', help_text='As you would like it to appear in the conference program.', max_length=100)),
('biography', models.TextField(verbose_name='Biography', blank=True, help_text="A little bit about you. Edit using <a href='http://warpedvisions.org/projects/markdown-cheat-sheet/target='_blank'>Markdown</a>.")),
('biography_html', models.TextField(blank=True)),
('photo', models.ImageField(verbose_name='Photo', upload_to='speaker_photos', blank=True)),
('annotation', models.TextField(verbose_name='Annotation')),
('invite_email', models.CharField(verbose_name='Invite_email', unique=True, db_index=True, max_length=200, null=True)),
('invite_token', models.CharField(verbose_name='Invite token', db_index=True, max_length=40)),
('created', models.DateTimeField(editable=False, verbose_name='Created', default=datetime.datetime.now)),
('user', models.OneToOneField(null=True, related_name='speaker_profile', verbose_name='User', to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'Speaker',
'verbose_name_plural': 'Speakers',
'ordering': ['name'],
},
),
]

View file

@ -9,7 +9,7 @@ from django.utils.translation import ugettext_lazy as _
from django.contrib.auth.models import User from django.contrib.auth.models import User
from markitup.fields import MarkupField from symposion.markdown_parser import parse
@python_2_unicode_compatible @python_2_unicode_compatible
@ -24,10 +24,11 @@ class Speaker(models.Model):
name = models.CharField(verbose_name=_("Name"), max_length=100, name = models.CharField(verbose_name=_("Name"), max_length=100,
help_text=_("As you would like it to appear in the" help_text=_("As you would like it to appear in the"
" conference program.")) " conference program."))
biography = MarkupField(blank=True, help_text=_("A little bit about you. Edit using " biography = models.TextField(blank=True, help_text=_("A little bit about you. Edit using "
"<a href='http://warpedvisions.org/projects/" "<a href='http://warpedvisions.org/projects/"
"markdown-cheat-sheet/target='_blank'>" "markdown-cheat-sheet/target='_blank'>"
"Markdown</a>."), verbose_name=_("Biography")) "Markdown</a>."), verbose_name=_("Biography"))
biography_html = models.TextField(blank=True)
photo = models.ImageField(upload_to="speaker_photos", blank=True, verbose_name=_("Photo")) photo = models.ImageField(upload_to="speaker_photos", blank=True, verbose_name=_("Photo"))
annotation = models.TextField(verbose_name=_("Annotation")) # staff only annotation = models.TextField(verbose_name=_("Annotation")) # staff only
invite_email = models.CharField(max_length=200, unique=True, null=True, db_index=True, verbose_name=_("Invite_email")) invite_email = models.CharField(max_length=200, unique=True, null=True, db_index=True, verbose_name=_("Invite_email"))
@ -43,6 +44,10 @@ class Speaker(models.Model):
verbose_name = _("Speaker") verbose_name = _("Speaker")
verbose_name_plural = _("Speakers") verbose_name_plural = _("Speakers")
def save(self, *args, **kwargs):
self.biography_html = parse(self.biography)
return super(Speaker, self).save(*args, **kwargs)
def __str__(self): def __str__(self):
if self.user: if self.user:
return self.name return self.name

View file

@ -4,10 +4,11 @@ from django.http import Http404
from django.shortcuts import render, redirect, get_object_or_404 from django.shortcuts import render, redirect, get_object_or_404
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from account.decorators import login_required
from symposion.proposals.models import ProposalBase from symposion.proposals.models import ProposalBase
from symposion.speakers.forms import SpeakerForm from symposion.speakers.forms import SpeakerForm
from symposion.speakers.models import Speaker from symposion.speakers.models import Speaker
@ -39,9 +40,8 @@ def speaker_create(request):
return redirect("dashboard") return redirect("dashboard")
else: else:
form = SpeakerForm(initial={"name": request.user.get_full_name()}) form = SpeakerForm(initial={"name": request.user.get_full_name()})
return render(request, "symposion/speakers/speaker_create.html", {
return render(request, "speakers/speaker_create.html", { "speaker_form": form,
"form": form,
}) })
@ -68,8 +68,8 @@ def speaker_create_staff(request, pk):
else: else:
form = SpeakerForm(initial={"name": user.get_full_name()}) form = SpeakerForm(initial={"name": user.get_full_name()})
return render(request, "speakers/speaker_create.html", { return render(request, "symposion/speakers/speaker_create.html", {
"form": form, "speaker_form": form,
}) })
@ -121,8 +121,8 @@ def speaker_edit(request, pk=None):
else: else:
form = SpeakerForm(instance=speaker) form = SpeakerForm(instance=speaker)
return render(request, "speakers/speaker_edit.html", { return render(request, "symposion/speakers/speaker_edit.html", {
"form": form, "speaker_form": form,
}) })
@ -132,7 +132,7 @@ def speaker_profile(request, pk):
if not presentations and not request.user.is_staff: if not presentations and not request.user.is_staff:
raise Http404() raise Http404()
return render(request, "speakers/speaker_profile.html", { return render(request, "symposion/speakers/speaker_profile.html", {
"speaker": speaker, "speaker": speaker,
"presentations": presentations, "presentations": presentations,
}) })

View file

@ -0,0 +1,115 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
from django.conf import settings
import datetime
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('symposion_conference', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='Benefit',
fields=[
('id', models.AutoField(serialize=False, auto_created=True, primary_key=True, verbose_name='ID')),
('name', models.CharField(verbose_name='Name', max_length=100)),
('description', models.TextField(blank=True, verbose_name='Description')),
('type', models.CharField(default='simple', choices=[('text', 'Text'), ('file', 'File'), ('richtext', 'Rich Text'), ('weblogo', 'Web Logo'), ('simple', 'Simple'), ('option', 'Option')], verbose_name='Type', max_length=10)),
('content_type', models.CharField(default='simple', choices=[('simple', 'Simple'), ('listing_text_af', 'Listing Text (Afrikaans)'), ('listing_text_ar', 'Listing Text (Arabic)'), ('listing_text_ast', 'Listing Text (Asturian)'), ('listing_text_az', 'Listing Text (Azerbaijani)'), ('listing_text_bg', 'Listing Text (Bulgarian)'), ('listing_text_be', 'Listing Text (Belarusian)'), ('listing_text_bn', 'Listing Text (Bengali)'), ('listing_text_br', 'Listing Text (Breton)'), ('listing_text_bs', 'Listing Text (Bosnian)'), ('listing_text_ca', 'Listing Text (Catalan)'), ('listing_text_cs', 'Listing Text (Czech)'), ('listing_text_cy', 'Listing Text (Welsh)'), ('listing_text_da', 'Listing Text (Danish)'), ('listing_text_de', 'Listing Text (German)'), ('listing_text_el', 'Listing Text (Greek)'), ('listing_text_en', 'Listing Text (English)'), ('listing_text_en-au', 'Listing Text (Australian English)'), ('listing_text_en-gb', 'Listing Text (British English)'), ('listing_text_eo', 'Listing Text (Esperanto)'), ('listing_text_es', 'Listing Text (Spanish)'), ('listing_text_es-ar', 'Listing Text (Argentinian Spanish)'), ('listing_text_es-mx', 'Listing Text (Mexican Spanish)'), ('listing_text_es-ni', 'Listing Text (Nicaraguan Spanish)'), ('listing_text_es-ve', 'Listing Text (Venezuelan Spanish)'), ('listing_text_et', 'Listing Text (Estonian)'), ('listing_text_eu', 'Listing Text (Basque)'), ('listing_text_fa', 'Listing Text (Persian)'), ('listing_text_fi', 'Listing Text (Finnish)'), ('listing_text_fr', 'Listing Text (French)'), ('listing_text_fy', 'Listing Text (Frisian)'), ('listing_text_ga', 'Listing Text (Irish)'), ('listing_text_gl', 'Listing Text (Galician)'), ('listing_text_he', 'Listing Text (Hebrew)'), ('listing_text_hi', 'Listing Text (Hindi)'), ('listing_text_hr', 'Listing Text (Croatian)'), ('listing_text_hu', 'Listing Text (Hungarian)'), ('listing_text_ia', 'Listing Text (Interlingua)'), ('listing_text_id', 'Listing Text (Indonesian)'), ('listing_text_io', 'Listing Text (Ido)'), ('listing_text_is', 'Listing Text (Icelandic)'), ('listing_text_it', 'Listing Text (Italian)'), ('listing_text_ja', 'Listing Text (Japanese)'), ('listing_text_ka', 'Listing Text (Georgian)'), ('listing_text_kk', 'Listing Text (Kazakh)'), ('listing_text_km', 'Listing Text (Khmer)'), ('listing_text_kn', 'Listing Text (Kannada)'), ('listing_text_ko', 'Listing Text (Korean)'), ('listing_text_lb', 'Listing Text (Luxembourgish)'), ('listing_text_lt', 'Listing Text (Lithuanian)'), ('listing_text_lv', 'Listing Text (Latvian)'), ('listing_text_mk', 'Listing Text (Macedonian)'), ('listing_text_ml', 'Listing Text (Malayalam)'), ('listing_text_mn', 'Listing Text (Mongolian)'), ('listing_text_mr', 'Listing Text (Marathi)'), ('listing_text_my', 'Listing Text (Burmese)'), ('listing_text_nb', 'Listing Text (Norwegian Bokmal)'), ('listing_text_ne', 'Listing Text (Nepali)'), ('listing_text_nl', 'Listing Text (Dutch)'), ('listing_text_nn', 'Listing Text (Norwegian Nynorsk)'), ('listing_text_os', 'Listing Text (Ossetic)'), ('listing_text_pa', 'Listing Text (Punjabi)'), ('listing_text_pl', 'Listing Text (Polish)'), ('listing_text_pt', 'Listing Text (Portuguese)'), ('listing_text_pt-br', 'Listing Text (Brazilian Portuguese)'), ('listing_text_ro', 'Listing Text (Romanian)'), ('listing_text_ru', 'Listing Text (Russian)'), ('listing_text_sk', 'Listing Text (Slovak)'), ('listing_text_sl', 'Listing Text (Slovenian)'), ('listing_text_sq', 'Listing Text (Albanian)'), ('listing_text_sr', 'Listing Text (Serbian)'), ('listing_text_sr-latn', 'Listing Text (Serbian Latin)'), ('listing_text_sv', 'Listing Text (Swedish)'), ('listing_text_sw', 'Listing Text (Swahili)'), ('listing_text_ta', 'Listing Text (Tamil)'), ('listing_text_te', 'Listing Text (Telugu)'), ('listing_text_th', 'Listing Text (Thai)'), ('listing_text_tr', 'Listing Text (Turkish)'), ('listing_text_tt', 'Listing Text (Tatar)'), ('listing_text_udm', 'Listing Text (Udmurt)'), ('listing_text_uk', 'Listing Text (Ukrainian)'), ('listing_text_ur', 'Listing Text (Urdu)'), ('listing_text_vi', 'Listing Text (Vietnamese)'), ('listing_text_zh-cn', 'Listing Text (Simplified Chinese)'), ('listing_text_zh-hans', 'Listing Text (Simplified Chinese)'), ('listing_text_zh-hant', 'Listing Text (Traditional Chinese)'), ('listing_text_zh-tw', 'Listing Text (Traditional Chinese)')], verbose_name='content type', max_length=20)),
],
),
migrations.CreateModel(
name='BenefitLevel',
fields=[
('id', models.AutoField(serialize=False, auto_created=True, primary_key=True, verbose_name='ID')),
('max_words', models.PositiveIntegerField(blank=True, verbose_name='Max words', null=True)),
('other_limits', models.CharField(blank=True, verbose_name='Other limits', max_length=200)),
('benefit', models.ForeignKey(to='symposion_sponsorship.Benefit', related_name='benefit_levels', verbose_name='Benefit')),
],
options={
'verbose_name_plural': 'Benefit levels',
'ordering': ['level'],
'verbose_name': 'Benefit level',
},
),
migrations.CreateModel(
name='Sponsor',
fields=[
('id', models.AutoField(serialize=False, auto_created=True, primary_key=True, verbose_name='ID')),
('name', models.CharField(verbose_name='Sponsor Name', max_length=100)),
('display_url', models.URLField(blank=True, verbose_name='display URL')),
('external_url', models.URLField(verbose_name='External URL')),
('annotation', models.TextField(blank=True, verbose_name='Annotation')),
('contact_name', models.CharField(verbose_name='Contact Name', max_length=100)),
('contact_email', models.EmailField(verbose_name='Contact Email', max_length=254)),
('added', models.DateTimeField(default=datetime.datetime.now, verbose_name='added')),
('active', models.BooleanField(default=False, verbose_name='active')),
('web_logo_benefit', models.NullBooleanField(verbose_name='Web logo benefit', help_text='Web logo benefit is complete')),
('print_logo_benefit', models.NullBooleanField(verbose_name='Print logo benefit', help_text='Print logo benefit is complete')),
('print_description_benefit', models.NullBooleanField(verbose_name='Print description benefit', help_text='Print description benefit is complete')),
('company_description_benefit', models.NullBooleanField(verbose_name='Company description benefit', help_text='Company description benefit is complete')),
('applicant', models.ForeignKey(to=settings.AUTH_USER_MODEL, null=True, related_name='sponsorships', verbose_name='Applicant')),
],
options={
'verbose_name_plural': 'Sponsors',
'ordering': ['name'],
'verbose_name': 'Sponsor',
},
),
migrations.CreateModel(
name='SponsorBenefit',
fields=[
('id', models.AutoField(serialize=False, auto_created=True, primary_key=True, verbose_name='ID')),
('active', models.BooleanField(default=True, verbose_name='Active')),
('max_words', models.PositiveIntegerField(blank=True, verbose_name='Max words', null=True)),
('other_limits', models.CharField(blank=True, verbose_name='Other limits', max_length=200)),
('text', models.TextField(blank=True, verbose_name='Text')),
('upload', models.FileField(blank=True, verbose_name='File', upload_to='sponsor_files')),
('is_complete', models.NullBooleanField(verbose_name='Complete?', help_text='True - benefit complete; False - benefit incomplete; Null - n/a')),
('benefit', models.ForeignKey(to='symposion_sponsorship.Benefit', related_name='sponsor_benefits', verbose_name='Benefit')),
('sponsor', models.ForeignKey(to='symposion_sponsorship.Sponsor', related_name='sponsor_benefits', verbose_name='Sponsor')),
],
options={
'verbose_name_plural': 'Sponsor benefits',
'ordering': ['-active'],
'verbose_name': 'Sponsor benefit',
},
),
migrations.CreateModel(
name='SponsorLevel',
fields=[
('id', models.AutoField(serialize=False, auto_created=True, primary_key=True, verbose_name='ID')),
('name', models.CharField(verbose_name='Name', max_length=100)),
('order', models.IntegerField(default=0, verbose_name='Order')),
('cost', models.PositiveIntegerField(verbose_name='Cost')),
('description', models.TextField(blank=True, verbose_name='Description', help_text='This is private.')),
('conference', models.ForeignKey(to='symposion_conference.Conference', verbose_name='Conference')),
],
options={
'verbose_name_plural': 'Sponsor levels',
'ordering': ['conference', 'order'],
'verbose_name': 'Sponsor level',
},
),
migrations.AddField(
model_name='sponsor',
name='level',
field=models.ForeignKey(to='symposion_sponsorship.SponsorLevel', verbose_name='level'),
),
migrations.AddField(
model_name='sponsor',
name='sponsor_logo',
field=models.ForeignKey(blank=True, to='symposion_sponsorship.SponsorBenefit', null=True, related_name='+', verbose_name='Sponsor logo', editable=False),
),
migrations.AddField(
model_name='benefitlevel',
name='level',
field=models.ForeignKey(to='symposion_sponsorship.SponsorLevel', related_name='benefit_levels', verbose_name='Level'),
),
]

View file

@ -4,7 +4,7 @@ from django.views.generic import TemplateView
urlpatterns = patterns( urlpatterns = patterns(
"symposion.sponsorship.views", "symposion.sponsorship.views",
url(r"^$", TemplateView.as_view(template_name="sponsorship/list.html"), name="sponsor_list"), url(r"^$", TemplateView.as_view(template_name="symposion/sponsorship/list.html"), name="sponsor_list"),
url(r"^apply/$", "sponsor_apply", name="sponsor_apply"), url(r"^apply/$", "sponsor_apply", name="sponsor_apply"),
url(r"^add/$", "sponsor_add", name="sponsor_add"), url(r"^add/$", "sponsor_add", name="sponsor_add"),
url(r"^ziplogos/$", "sponsor_zip_logo_files", name="sponsor_zip_logos"), url(r"^ziplogos/$", "sponsor_zip_logo_files", name="sponsor_zip_logos"),

View file

@ -9,12 +9,13 @@ from zipfile import ZipFile, ZipInfo
from django.conf import settings from django.conf import settings
from django.contrib import messages from django.contrib import messages
from django.contrib.admin.views.decorators import staff_member_required from django.contrib.admin.views.decorators import staff_member_required
from django.contrib.auth.decorators import login_required
from django.http import Http404, HttpResponse from django.http import Http404, HttpResponse
from django.shortcuts import render_to_response, redirect, get_object_or_404 from django.shortcuts import render_to_response, redirect, get_object_or_404
from django.template import RequestContext from django.template import RequestContext
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from account.decorators import login_required
from symposion.sponsorship.forms import SponsorApplicationForm, \ from symposion.sponsorship.forms import SponsorApplicationForm, \
SponsorDetailsForm, SponsorBenefitsFormSet SponsorDetailsForm, SponsorBenefitsFormSet
from symposion.sponsorship.models import Benefit, Sponsor, SponsorBenefit, \ from symposion.sponsorship.models import Benefit, Sponsor, SponsorBenefit, \
@ -43,7 +44,7 @@ def sponsor_apply(request):
else: else:
form = SponsorApplicationForm(user=request.user) form = SponsorApplicationForm(user=request.user)
return render_to_response("sponsorship/apply.html", { return render_to_response("symposion/sponsorship/apply.html", {
"form": form, "form": form,
}, context_instance=RequestContext(request)) }, context_instance=RequestContext(request))
@ -63,7 +64,7 @@ def sponsor_add(request):
else: else:
form = SponsorApplicationForm(user=request.user) form = SponsorApplicationForm(user=request.user)
return render_to_response("sponsorship/add.html", { return render_to_response("symposion/sponsorship/add.html", {
"form": form, "form": form,
}, context_instance=RequestContext(request)) }, context_instance=RequestContext(request))
@ -96,7 +97,7 @@ def sponsor_detail(request, pk):
form = SponsorDetailsForm(instance=sponsor) form = SponsorDetailsForm(instance=sponsor)
formset = SponsorBenefitsFormSet(**formset_kwargs) formset = SponsorBenefitsFormSet(**formset_kwargs)
return render_to_response("sponsorship/detail.html", { return render_to_response("symposion/sponsorship/detail.html", {
"sponsor": sponsor, "sponsor": sponsor,
"form": form, "form": form,
"formset": formset, "formset": formset,

View file

@ -0,0 +1,56 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import datetime
from django.conf import settings
class Migration(migrations.Migration):
dependencies = [
('auth', '0006_require_contenttypes_0002'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Membership',
fields=[
('id', models.AutoField(serialize=False, auto_created=True, primary_key=True, verbose_name='ID')),
('state', models.CharField(max_length=20, choices=[('applied', 'applied'), ('invited', 'invited'), ('declined', 'declined'), ('rejected', 'rejected'), ('member', 'member'), ('manager', 'manager')], verbose_name='State')),
('message', models.TextField(blank=True, verbose_name='Message')),
],
options={
'verbose_name_plural': 'Memberships',
'verbose_name': 'Membership',
},
),
migrations.CreateModel(
name='Team',
fields=[
('id', models.AutoField(serialize=False, auto_created=True, primary_key=True, verbose_name='ID')),
('slug', models.SlugField(unique=True, verbose_name='Slug')),
('name', models.CharField(max_length=100, verbose_name='Name')),
('description', models.TextField(blank=True, verbose_name='Description')),
('access', models.CharField(max_length=20, choices=[('open', 'open'), ('application', 'by application'), ('invitation', 'by invitation')], verbose_name='Access')),
('created', models.DateTimeField(editable=False, default=datetime.datetime.now, verbose_name='Created')),
('manager_permissions', models.ManyToManyField(related_name='manager_teams', blank=True, to='auth.Permission', verbose_name='Manager permissions')),
('permissions', models.ManyToManyField(related_name='member_teams', blank=True, to='auth.Permission', verbose_name='Permissions')),
],
options={
'verbose_name_plural': 'Teams',
'verbose_name': 'Team',
},
),
migrations.AddField(
model_name='membership',
name='team',
field=models.ForeignKey(verbose_name='Team', to='teams.Team', related_name='memberships'),
),
migrations.AddField(
model_name='membership',
name='user',
field=models.ForeignKey(verbose_name='User', to=settings.AUTH_USER_MODEL, related_name='memberships'),
),
]

View file

View file

@ -2,12 +2,12 @@ from __future__ import unicode_literals
from django.http import Http404, HttpResponseNotAllowed from django.http import Http404, HttpResponseNotAllowed
from django.shortcuts import render, redirect, get_object_or_404 from django.shortcuts import render, redirect, get_object_or_404
from django.contrib.auth.decorators import login_required
from django.contrib import messages from django.contrib import messages
from symposion.utils.mail import send_email
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from account.decorators import login_required
from symposion.utils.mail import send_email
from symposion.teams.forms import TeamInvitationForm from symposion.teams.forms import TeamInvitationForm
from symposion.teams.models import Team, Membership from symposion.teams.models import Team, Membership
@ -75,7 +75,7 @@ def team_detail(request, slug):
else: else:
form = None form = None
return render(request, "teams/team_detail.html", { return render(request, "symposion/teams/team_detail.html", {
"team": team, "team": team,
"state": state, "state": state,
"invite_form": form, "invite_form": form,

View file

@ -17,10 +17,10 @@ def send_email(to, kind, **kwargs):
ctx.update(kwargs.get("context", {})) ctx.update(kwargs.get("context", {}))
subject = "[%s] %s" % ( subject = "[%s] %s" % (
current_site.name, current_site.name,
render_to_string("emails/%s/subject.txt" % kind, ctx).strip() render_to_string("symposion/emails/%s/subject.txt" % kind, ctx).strip()
) )
message_html = render_to_string("emails/%s/message.html" % kind, ctx) message_html = render_to_string("symposion/emails/%s/message.html" % kind, ctx)
message_plaintext = strip_tags(message_html) message_plaintext = strip_tags(message_html)
from_email = settings.DEFAULT_FROM_EMAIL from_email = settings.DEFAULT_FROM_EMAIL

View file

@ -1,53 +1,9 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import hashlib
import random
from django.shortcuts import render, redirect from django.shortcuts import render, redirect
from django.contrib.auth.models import User
from django.contrib.auth.decorators import login_required
import account.views from account.decorators import login_required
import symposion.forms
class SignupView(account.views.SignupView):
form_class = symposion.forms.SignupForm
form_kwargs = {
"prefix": "signup",
}
def create_user(self, form, commit=True):
user_kwargs = {
"first_name": form.cleaned_data["first_name"],
"last_name": form.cleaned_data["last_name"]
}
return super(SignupView, self).create_user(form, commit=commit,
**user_kwargs)
def generate_username(self, form):
def random_username():
h = hashlib.sha1(form.cleaned_data["email"]).hexdigest()[:25]
# don't ask
n = random.randint(1, (10 ** (5 - 1)) - 1)
return "%s%d" % (h, n)
while True:
try:
username = random_username()
User.objects.get(username=username)
except User.DoesNotExist:
break
return username
class LoginView(account.views.LoginView):
form_class = account.forms.LoginEmailForm
form_kwargs = {
"prefix": "login",
}
@login_required @login_required