cfp app
This commit is contained in:
parent
b6d26f675d
commit
fcbff0e9dd
|
@ -9,15 +9,13 @@ from django.utils.translation import ugettext
|
||||||
|
|
||||||
from ponyconf.utils import PonyConfModel, enum_to_choices
|
from ponyconf.utils import PonyConfModel, enum_to_choices
|
||||||
|
|
||||||
from .utils import generate_user_uid
|
#from .utils import generate_user_uid
|
||||||
|
|
||||||
|
|
||||||
class Profile(PonyConfModel):
|
class Profile(PonyConfModel):
|
||||||
|
|
||||||
user = models.OneToOneField(User)
|
user = models.OneToOneField(User)
|
||||||
phone_number = models.CharField(max_length=16, blank=True, default='', verbose_name=_('Phone number'))
|
phone_number = models.CharField(max_length=16, blank=True, default='', verbose_name=_('Phone number'))
|
||||||
biography = models.TextField(blank=True, verbose_name=_('Biography'))
|
|
||||||
email_token = models.CharField(max_length=12, default=generate_user_uid, unique=True)
|
|
||||||
|
|
||||||
twitter = models.CharField(max_length=100, blank=True, default='', verbose_name=_('Twitter'))
|
twitter = models.CharField(max_length=100, blank=True, default='', verbose_name=_('Twitter'))
|
||||||
linkedin = models.CharField(max_length=100, blank=True, default='', verbose_name=_('LinkedIn'))
|
linkedin = models.CharField(max_length=100, blank=True, default='', verbose_name=_('LinkedIn'))
|
||||||
|
@ -29,125 +27,5 @@ class Profile(PonyConfModel):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.user.get_full_name() or self.user.username
|
return self.user.get_full_name() or self.user.username
|
||||||
|
|
||||||
def get_absolute_url(self):
|
#def get_absolute_url(self):
|
||||||
return reverse('profile')
|
# return reverse('profile')
|
||||||
|
|
||||||
|
|
||||||
class Option(models.Model):
|
|
||||||
name = models.CharField(max_length=64, unique=True)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
abstract = True
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return ugettext(self.name)
|
|
||||||
|
|
||||||
|
|
||||||
class Transport(Option):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class Connector(Option):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class Participation(PonyConfModel):
|
|
||||||
|
|
||||||
LICENCES = IntEnum('Video licence', 'CC-Zero CC-BY CC-BY-SA CC-BY-ND CC-BY-NC CC-BY-NC-SA CC-BY-NC-ND')
|
|
||||||
ACCOMMODATION_NO = 0
|
|
||||||
ACCOMMODATION_HOTEL = 1
|
|
||||||
ACCOMMODATION_HOMESTAY = 2
|
|
||||||
ACCOMMODATION_CHOICES = (
|
|
||||||
(ACCOMMODATION_NO, _('No')),
|
|
||||||
(ACCOMMODATION_HOTEL, _('Hotel')),
|
|
||||||
(ACCOMMODATION_HOMESTAY, _('Homestay')),
|
|
||||||
)
|
|
||||||
|
|
||||||
site = models.ForeignKey(Site, on_delete=models.CASCADE)
|
|
||||||
user = models.ForeignKey(User)
|
|
||||||
|
|
||||||
|
|
||||||
need_transport = models.NullBooleanField(verbose_name=_('Defray transportation?'), default=None)
|
|
||||||
arrival = models.DateTimeField(blank=True, null=True)
|
|
||||||
departure = models.DateTimeField(blank=True, null=True)
|
|
||||||
transport = models.ManyToManyField(Transport, verbose_name=_("I want to travel by"), blank=True)
|
|
||||||
transport_city_outward = models.CharField(blank=True, default='', max_length=256, verbose_name=_("Departure city"))
|
|
||||||
transport_city_return = models.CharField(blank=True, default='', max_length=256, verbose_name=_("Return city"), help_text=_("If different from departure city"))
|
|
||||||
transport_booked = models.BooleanField(default=False)
|
|
||||||
|
|
||||||
accommodation = models.IntegerField(choices=ACCOMMODATION_CHOICES, verbose_name=_('Need accommodation?'), null=True, blank=True)
|
|
||||||
accommodation_booked = models.BooleanField(default=False)
|
|
||||||
|
|
||||||
constraints = models.TextField(blank=True, verbose_name=_("Constraints"))
|
|
||||||
connector = models.ManyToManyField(Connector, verbose_name=_("I can output"), blank=True)
|
|
||||||
sound = models.BooleanField(_("I need sound"), default=False)
|
|
||||||
|
|
||||||
videotaped = models.BooleanField(_("I'm ok to be recorded on video"), default=True)
|
|
||||||
video_licence = models.IntegerField(choices=enum_to_choices(LICENCES), default=2, verbose_name=_("Video licence"))
|
|
||||||
|
|
||||||
notes = models.TextField(default='', blank=True, verbose_name=_("Notes"), help_text=_('This field is only visible by organizers.'))
|
|
||||||
orga = models.BooleanField(default=False)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
# A User can participe only once to a Conference (= Site)
|
|
||||||
unique_together = ('site', 'user')
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return str(self.user.profile)
|
|
||||||
|
|
||||||
def get_absolute_url(self):
|
|
||||||
return reverse('show-participant', kwargs={'username': self.user.username})
|
|
||||||
|
|
||||||
def is_orga(self):
|
|
||||||
return self.orga
|
|
||||||
|
|
||||||
def is_staff(self):
|
|
||||||
return self.is_orga() or self.topic_set.exists() or self.track_set.exists()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def topic_set(self):
|
|
||||||
return self.user.topic_set.filter(site=self.site)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def track_set(self):
|
|
||||||
return self.user.track_set.filter(site=self.site)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def talk_set(self):
|
|
||||||
return self.user.talk_set.filter(site=self.site)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def accepted_talk_set(self):
|
|
||||||
return self.talk_set.filter(accepted=True)
|
|
||||||
@property
|
|
||||||
def pending_talk_set(self):
|
|
||||||
return self.talk_set.filter(accepted=None)
|
|
||||||
@property
|
|
||||||
def refused_talk_set(self):
|
|
||||||
return self.talk_set.filter(accepted=False)
|
|
||||||
@property
|
|
||||||
def not_refused_talk_set(self): # accepted + pending
|
|
||||||
return self.talk_set.exclude(accepted=False)
|
|
||||||
|
|
||||||
# return True, False or None if availabilities have not been filled
|
|
||||||
def is_available(self, start, end=None):
|
|
||||||
if not self.availabilities.exists():
|
|
||||||
return None
|
|
||||||
for timeslot in self.availabilities.all():
|
|
||||||
if start < timeslot.start:
|
|
||||||
continue
|
|
||||||
if start > timeslot.end:
|
|
||||||
continue
|
|
||||||
if end:
|
|
||||||
assert(start < end)
|
|
||||||
if end > timeslot.end:
|
|
||||||
continue
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
class AvailabilityTimeslot(models.Model):
|
|
||||||
|
|
||||||
participation = models.ForeignKey(Participation, related_name='availabilities')
|
|
||||||
start = models.DateTimeField(blank=True)
|
|
||||||
end = models.DateTimeField(blank=True)
|
|
||||||
|
|
|
@ -2,16 +2,17 @@ from django.conf import settings
|
||||||
from django.conf.urls import include, url
|
from django.conf.urls import include, url
|
||||||
from django.contrib.auth import views as auth_views
|
from django.contrib.auth import views as auth_views
|
||||||
|
|
||||||
from . import views
|
#from . import views
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^profile/$', views.profile, name='profile'),
|
#url(r'^profile/$', views.profile, name='profile'),
|
||||||
url(r'^login/$', auth_views.login, {'extra_context': {'buttons': [views.RESET_PASSWORD_BUTTON]}}, name='login'),
|
#url(r'^login/$', auth_views.login, {'extra_context': {'buttons': [views.RESET_PASSWORD_BUTTON]}}, name='login'),
|
||||||
|
url(r'^login/$', auth_views.login, name='login'),
|
||||||
url(r'^logout/$', auth_views.logout, {'next_page': settings.LOGOUT_REDIRECT_URL}, name='logout'),
|
url(r'^logout/$', auth_views.logout, {'next_page': settings.LOGOUT_REDIRECT_URL}, name='logout'),
|
||||||
url(r'^participant/$', views.participation_list, name='list-participants'),
|
#url(r'^participant/$', views.participation_list, name='list-participants'),
|
||||||
url(r'^participant/(?P<username>[\w.@+-]+)$', views.participant_details, name='show-participant'),
|
#url(r'^participant/(?P<username>[\w.@+-]+)$', views.participant_details, name='show-participant'),
|
||||||
url(r'^participant/(?P<username>[\w.@+-]+)/edit/$', views.participant_edit, name='edit-participant'),
|
#url(r'^participant/(?P<username>[\w.@+-]+)/edit/$', views.participant_edit, name='edit-participant'),
|
||||||
url(r'^avatar/', include('avatar.urls')),
|
#url(r'^avatar/', include('avatar.urls')),
|
||||||
url(r'', include('django.contrib.auth.urls')),
|
url(r'', include('django.contrib.auth.urls')),
|
||||||
url(r'', include('registration.backends.default.urls')),
|
#url(r'', include('registration.backends.default.urls')),
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
from django.contrib.sites.shortcuts import get_current_site
|
|
||||||
from django.utils.crypto import get_random_string
|
|
||||||
|
|
||||||
|
|
||||||
def generate_user_uid():
|
|
||||||
return get_random_string(length=12, allowed_chars='abcdefghijklmnopqrstuvwxyz0123456789')
|
|
||||||
|
|
||||||
|
|
||||||
def is_orga(request, user):
|
|
||||||
return user.is_authenticated and user.participation_set.get(site=get_current_site(request)).is_orga()
|
|
||||||
|
|
||||||
|
|
||||||
def is_staff(request, user):
|
|
||||||
return user.is_authenticated and user.participation_set.get(site=get_current_site(request)).is_staff()
|
|
||||||
|
|
||||||
|
|
||||||
def can_edit_profile(request, profile):
|
|
||||||
editor = request.user.participation_set.get(site=get_current_site(request))
|
|
||||||
return editor.is_orga() or editor.topic_set.filter(talk__speakers=profile.user).exists()
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
from django.apps import AppConfig
|
||||||
|
from django.db.models.signals import post_migrate
|
||||||
|
|
||||||
|
|
||||||
|
class CFPConfig(AppConfig):
|
||||||
|
name = 'cfp'
|
||||||
|
|
||||||
|
def ready(self):
|
||||||
|
pass
|
||||||
|
#import cfp.signals # noqa
|
||||||
|
#post_migrate.connect(proposals.signals.call_first_site_post_save, sender=self)
|
|
@ -0,0 +1,144 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.5 on 2017-05-29 20:47
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import autoslug.fields
|
||||||
|
import colorful.fields
|
||||||
|
from django.conf import settings
|
||||||
|
import django.core.validators
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('sites', '0002_alter_domain_unique'),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Conference',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('home', models.TextField(blank=True, default='')),
|
||||||
|
('venue', models.TextField(blank=True, default='')),
|
||||||
|
('city', models.CharField(blank=True, default='', max_length=64)),
|
||||||
|
('site', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='sites.Site')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Participant',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('updated', models.DateTimeField(auto_now=True)),
|
||||||
|
('name', models.CharField(max_length=128)),
|
||||||
|
('email', models.EmailField(max_length=254)),
|
||||||
|
('phone_number', models.CharField(blank=True, default='', max_length=16, verbose_name='Phone number')),
|
||||||
|
('biography', models.TextField(blank=True, verbose_name='Biography')),
|
||||||
|
('notes', models.TextField(blank=True, default='', help_text='This field is only visible by organizers.', verbose_name='Notes')),
|
||||||
|
('vip', models.BooleanField(default=False)),
|
||||||
|
('site', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='sites.Site')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Talk',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('updated', models.DateTimeField(auto_now=True)),
|
||||||
|
('title', models.CharField(help_text='After submission, title can only be changed by the staff.', max_length=128, verbose_name='Title')),
|
||||||
|
('slug', autoslug.fields.AutoSlugField(editable=False, populate_from='title', unique=True)),
|
||||||
|
('description', models.TextField(blank=True, verbose_name='Description')),
|
||||||
|
('notes', models.TextField(blank=True, verbose_name='Message to organizers')),
|
||||||
|
('videotaped', models.BooleanField(default=True, verbose_name="I'm ok to be recorded on video")),
|
||||||
|
('video_licence', models.IntegerField(choices=[(1, 'CC-Zero'), (2, 'CC-BY'), (3, 'CC-BY-SA'), (4, 'CC-BY-ND'), (5, 'CC-BY-NC'), (6, 'CC-BY-NC-SA'), (7, 'CC-BY-NC-ND')], default=2, verbose_name='Video licence')),
|
||||||
|
('sound', models.BooleanField(default=False, verbose_name='I need sound')),
|
||||||
|
('accepted', models.NullBooleanField(default=None)),
|
||||||
|
('duration', models.PositiveIntegerField(default=0, verbose_name='Duration (min)')),
|
||||||
|
('plenary', models.BooleanField(default=False)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'ordering': ('category__id', 'title'),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='TalkCategory',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(max_length=64)),
|
||||||
|
('duration', models.PositiveIntegerField(default=0, verbose_name='Default duration (min)')),
|
||||||
|
('color', colorful.fields.RGBColorField(default='#ffffff', verbose_name='Color on program')),
|
||||||
|
('label', models.CharField(blank=True, default='', max_length=64, verbose_name='Label on program')),
|
||||||
|
('opening_date', models.DateTimeField(blank=True, default=None, null=True)),
|
||||||
|
('closing_date', models.DateTimeField(blank=True, default=None, null=True)),
|
||||||
|
('site', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='sites.Site')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'ordering': ('pk',),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Track',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('updated', models.DateTimeField(auto_now=True)),
|
||||||
|
('name', models.CharField(max_length=128, verbose_name='Name')),
|
||||||
|
('slug', autoslug.fields.AutoSlugField(editable=False, populate_from='name')),
|
||||||
|
('description', models.TextField(blank=True, verbose_name='Description')),
|
||||||
|
('site', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='sites.Site')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Vote',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('updated', models.DateTimeField(auto_now=True)),
|
||||||
|
('vote', models.IntegerField(default=0, validators=[django.core.validators.MinValueValidator(-2), django.core.validators.MaxValueValidator(2)])),
|
||||||
|
('talk', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cfp.Talk')),
|
||||||
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='talk',
|
||||||
|
name='category',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cfp.TalkCategory', verbose_name='Intervention kind'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='talk',
|
||||||
|
name='site',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='sites.Site'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='talk',
|
||||||
|
name='speakers',
|
||||||
|
field=models.ManyToManyField(to='cfp.Participant', verbose_name='Speakers'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='talk',
|
||||||
|
name='track',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='cfp.Track', verbose_name='Track'),
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='vote',
|
||||||
|
unique_together=set([('talk', 'user')]),
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='track',
|
||||||
|
unique_together=set([('site', 'name')]),
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='talkcategory',
|
||||||
|
unique_together=set([('site', 'name')]),
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='participant',
|
||||||
|
unique_together=set([('site', 'email')]),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,339 @@
|
||||||
|
from enum import IntEnum
|
||||||
|
from datetime import timedelta
|
||||||
|
from os.path import join, basename
|
||||||
|
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.contrib.sites.models import Site
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
|
from django.core.validators import MaxValueValidator, MinValueValidator
|
||||||
|
from django.db import models
|
||||||
|
from django.db.models import Q
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from django.utils.translation import ugettext
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
#from autoslug import AutoSlugField
|
||||||
|
#from colorful.fields import RGBColorField
|
||||||
|
|
||||||
|
#from accounts.models import Participation
|
||||||
|
from ponyconf.utils import PonyConfModel, enum_to_choices
|
||||||
|
|
||||||
|
#from .utils import generate_user_uid
|
||||||
|
|
||||||
|
from enum import IntEnum
|
||||||
|
from datetime import timedelta
|
||||||
|
from os.path import join, basename
|
||||||
|
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.contrib.sites.models import Site
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
|
from django.core.validators import MaxValueValidator, MinValueValidator
|
||||||
|
from django.db import models
|
||||||
|
from django.db.models import Q
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from django.utils.translation import ugettext
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
from autoslug import AutoSlugField
|
||||||
|
from colorful.fields import RGBColorField
|
||||||
|
|
||||||
|
#from accounts.models import Participation
|
||||||
|
#from ponyconf.utils import PonyConfModel, enum_to_choices
|
||||||
|
#from planning.models import Room
|
||||||
|
|
||||||
|
from .utils import query_sum
|
||||||
|
from .utils import generate_user_uid
|
||||||
|
|
||||||
|
from enum import IntEnum
|
||||||
|
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.contrib.sites.models import Site
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
|
from django.db import models
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from django.utils.translation import ugettext
|
||||||
|
|
||||||
|
#from ponyconf.utils import PonyConfModel, enum_to_choices
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class Participant(PonyConfModel):
|
||||||
|
|
||||||
|
#LICENCES = IntEnum('Video licence', 'CC-Zero CC-BY CC-BY-SA CC-BY-ND CC-BY-NC CC-BY-NC-SA CC-BY-NC-ND')
|
||||||
|
|
||||||
|
site = models.ForeignKey(Site, on_delete=models.CASCADE)
|
||||||
|
|
||||||
|
name = models.CharField(max_length=128)
|
||||||
|
email = models.EmailField()
|
||||||
|
|
||||||
|
phone_number = models.CharField(max_length=16, blank=True, default='', verbose_name=_('Phone number'))
|
||||||
|
biography = models.TextField(blank=True, verbose_name=_('Biography'))
|
||||||
|
#email_token = models.CharField(max_length=12, default=generate_user_uid, unique=True)
|
||||||
|
|
||||||
|
|
||||||
|
# TALK
|
||||||
|
#videotaped = models.BooleanField(_("I'm ok to be recorded on video"), default=True)
|
||||||
|
#video_licence = models.IntegerField(choices=enum_to_choices(LICENCES), default=2, verbose_name=_("Video licence"))
|
||||||
|
#sound = models.BooleanField(_("I need sound"), default=False)
|
||||||
|
|
||||||
|
notes = models.TextField(default='', blank=True, verbose_name=_("Notes"), help_text=_('This field is only visible by organizers.'))
|
||||||
|
vip = models.BooleanField(default=False)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
# A User can participe only once to a Conference (= Site)
|
||||||
|
unique_together = ('site', 'email')
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return str(self.name)
|
||||||
|
|
||||||
|
#def get_absolute_url(self):
|
||||||
|
# return reverse('show-participant', kwargs={'username': self.user.username})
|
||||||
|
|
||||||
|
#def is_orga(self):
|
||||||
|
# return self.orga
|
||||||
|
|
||||||
|
#def is_staff(self):
|
||||||
|
# return self.is_orga() or self.topic_set.exists() or self.track_set.exists()
|
||||||
|
|
||||||
|
#@property
|
||||||
|
#def topic_set(self):
|
||||||
|
# return self.user.topic_set.filter(site=self.site)
|
||||||
|
|
||||||
|
#@property
|
||||||
|
#def track_set(self):
|
||||||
|
# return self.user.track_set.filter(site=self.site)
|
||||||
|
|
||||||
|
#@property
|
||||||
|
#def talk_set(self):
|
||||||
|
# return self.user.talk_set.filter(site=self.site)
|
||||||
|
|
||||||
|
#@property
|
||||||
|
#def accepted_talk_set(self):
|
||||||
|
# return self.talk_set.filter(accepted=True)
|
||||||
|
#@property
|
||||||
|
#def pending_talk_set(self):
|
||||||
|
# return self.talk_set.filter(accepted=None)
|
||||||
|
#@property
|
||||||
|
#def refused_talk_set(self):
|
||||||
|
# return self.talk_set.filter(accepted=False)
|
||||||
|
#@property
|
||||||
|
#def not_refused_talk_set(self): # accepted + pending
|
||||||
|
# return self.talk_set.exclude(accepted=False)
|
||||||
|
|
||||||
|
|
||||||
|
class Conference(models.Model):
|
||||||
|
|
||||||
|
site = models.OneToOneField(Site, on_delete=models.CASCADE)
|
||||||
|
home = models.TextField(blank=True, default="")
|
||||||
|
venue = models.TextField(blank=True, default="")
|
||||||
|
city = models.CharField(max_length=64, blank=True, default="")
|
||||||
|
#subscriptions_open = models.BooleanField(default=False) # workshop subscription
|
||||||
|
|
||||||
|
#def cfp_is_open(self):
|
||||||
|
# events = Event.objects.filter(site=self.site)
|
||||||
|
# return any(map(lambda x: x.is_open(), events))
|
||||||
|
|
||||||
|
#@property
|
||||||
|
#def opened_events(self):
|
||||||
|
# now = timezone.now()
|
||||||
|
# return Event.objects.filter(site=self.site)\
|
||||||
|
# .filter(Q(opening_date__isnull=True) | Q(opening_date__lte=now))\
|
||||||
|
# .filter(Q(closing_date__isnull=True) | Q(closing_date__gte=now))
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return str(self.site)
|
||||||
|
|
||||||
|
|
||||||
|
class Track(PonyConfModel):
|
||||||
|
|
||||||
|
site = models.ForeignKey(Site, on_delete=models.CASCADE)
|
||||||
|
|
||||||
|
name = models.CharField(max_length=128, verbose_name=_('Name'))
|
||||||
|
slug = AutoSlugField(populate_from='name')
|
||||||
|
description = models.TextField(blank=True, verbose_name=_('Description'))
|
||||||
|
|
||||||
|
#managers = models.ManyToManyField(User, blank=True, verbose_name=_('Managers'))
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
unique_together = ('site', 'name')
|
||||||
|
|
||||||
|
#def estimated_duration(self):
|
||||||
|
# return sum([talk.estimated_duration for talk in self.talk_set.all()])
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
#def get_absolute_url(self):
|
||||||
|
# return reverse('list-talks') + '?track=%s' % self.slug
|
||||||
|
|
||||||
|
|
||||||
|
#class Topic(PonyConfModel):
|
||||||
|
#
|
||||||
|
# site = models.ForeignKey(Site, on_delete=models.CASCADE)
|
||||||
|
#
|
||||||
|
# name = models.CharField(max_length=128, verbose_name=_('Name'))
|
||||||
|
# slug = AutoSlugField(populate_from='name', unique=True)
|
||||||
|
# description = models.TextField(blank=True, verbose_name=_('Description'))
|
||||||
|
# track = models.ForeignKey(Track, blank=True, null=True, verbose_name=_('Destination track'))
|
||||||
|
#
|
||||||
|
# reviewers = models.ManyToManyField(User, blank=True, verbose_name=_('Reviewers'))
|
||||||
|
#
|
||||||
|
# class Meta:
|
||||||
|
# unique_together = ('site', 'name')
|
||||||
|
#
|
||||||
|
# def __str__(self):
|
||||||
|
# return self.name
|
||||||
|
#
|
||||||
|
# def get_absolute_url(self):
|
||||||
|
# return reverse('list-talks') + '?topic=%s' % self.slug
|
||||||
|
|
||||||
|
|
||||||
|
class TalkCategory(models.Model): # type of talk (conf 30min, 1h, stand, …)
|
||||||
|
|
||||||
|
site = models.ForeignKey(Site, on_delete=models.CASCADE)
|
||||||
|
name = models.CharField(max_length=64)
|
||||||
|
duration = models.PositiveIntegerField(default=0, verbose_name=_('Default duration (min)'))
|
||||||
|
color = RGBColorField(default='#ffffff', verbose_name=_("Color on program"))
|
||||||
|
label = models.CharField(max_length=64, verbose_name=_("Label on program"), blank=True, default="")
|
||||||
|
opening_date = models.DateTimeField(null=True, blank=True, default=None)
|
||||||
|
closing_date = models.DateTimeField(null=True, blank=True, default=None)
|
||||||
|
|
||||||
|
def is_open(self):
|
||||||
|
now = timezone.now()
|
||||||
|
if self.opening_date and now < self.opening_date:
|
||||||
|
return False
|
||||||
|
if self.closing_date and now > self.closing_date:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
unique_together = ('site', 'name')
|
||||||
|
ordering = ('pk',)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return ugettext(self.name)
|
||||||
|
|
||||||
|
#def get_absolute_url(self):
|
||||||
|
# return reverse('list-talks') + '?kind=%d' % self.pk
|
||||||
|
|
||||||
|
|
||||||
|
#class Attendee(PonyConfModel):
|
||||||
|
#
|
||||||
|
# user = models.ForeignKey(User, null=True)
|
||||||
|
# name = models.CharField(max_length=64, blank=True, default="")
|
||||||
|
# email = models.EmailField(blank=True, default="")
|
||||||
|
#
|
||||||
|
# def get_name(self):
|
||||||
|
# if self.user:
|
||||||
|
# return str(self.user.profile)
|
||||||
|
# else:
|
||||||
|
# return self.name
|
||||||
|
# get_name.short_description = _('Name')
|
||||||
|
#
|
||||||
|
# def get_email(self):
|
||||||
|
# if self.user:
|
||||||
|
# return self.user.email
|
||||||
|
# else:
|
||||||
|
# return self.email
|
||||||
|
# get_email.short_description = _('Email')
|
||||||
|
#
|
||||||
|
# def __str__(self):
|
||||||
|
# return self.get_name()
|
||||||
|
|
||||||
|
|
||||||
|
#def talk_materials_destination(talk, filename):
|
||||||
|
# return join(talk.site.name, talk.slug, filename)
|
||||||
|
|
||||||
|
|
||||||
|
class Talk(PonyConfModel):
|
||||||
|
|
||||||
|
LICENCES = IntEnum('Video licence', 'CC-Zero CC-BY CC-BY-SA CC-BY-ND CC-BY-NC CC-BY-NC-SA CC-BY-NC-ND')
|
||||||
|
|
||||||
|
site = models.ForeignKey(Site, on_delete=models.CASCADE)
|
||||||
|
|
||||||
|
#proposer = models.ForeignKey(User, related_name='+')
|
||||||
|
speakers = models.ManyToManyField(Participant, verbose_name=_('Speakers'))
|
||||||
|
title = models.CharField(max_length=128, verbose_name=_('Title'), help_text=_('After submission, title can only be changed by the staff.'))
|
||||||
|
slug = AutoSlugField(populate_from='title', unique=True)
|
||||||
|
#abstract = models.CharField(max_length=255, blank=True, verbose_name=_('Abstract'))
|
||||||
|
description = models.TextField(blank=True, verbose_name=_('Description'))
|
||||||
|
track = models.ForeignKey(Track, blank=True, null=True, verbose_name=_('Track'))
|
||||||
|
notes = models.TextField(blank=True, verbose_name=_('Message to organizers'))
|
||||||
|
category = models.ForeignKey(TalkCategory, verbose_name=_('Intervention kind'))
|
||||||
|
videotaped = models.BooleanField(_("I'm ok to be recorded on video"), default=True)
|
||||||
|
video_licence = models.IntegerField(choices=enum_to_choices(LICENCES), default=2, verbose_name=_("Video licence"))
|
||||||
|
sound = models.BooleanField(_("I need sound"), default=False)
|
||||||
|
accepted = models.NullBooleanField(default=None)
|
||||||
|
#start_date = models.DateTimeField(null=True, blank=True, default=None)
|
||||||
|
duration = models.PositiveIntegerField(default=0, verbose_name=_('Duration (min)'))
|
||||||
|
#room = models.ForeignKey(Room, blank=True, null=True, default=None)
|
||||||
|
plenary = models.BooleanField(default=False)
|
||||||
|
#materials = models.FileField(null=True, upload_to=talk_materials_destination, verbose_name=_('Materials'),
|
||||||
|
# help_text=_('You can use this field to share some materials related to your intervention.'))
|
||||||
|
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ('title',)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.title
|
||||||
|
|
||||||
|
def get_speakers_str(self):
|
||||||
|
speakers = [str(Participant.objects.get(site=self.site, user=speaker)) for speaker in self.speakers.all()]
|
||||||
|
if len(speakers) == 0:
|
||||||
|
return 'superman'
|
||||||
|
elif len(speakers) == 1:
|
||||||
|
return speakers[0]
|
||||||
|
else:
|
||||||
|
return ', '.join(speakers[:-1]) + ' & ' + str(speakers[-1])
|
||||||
|
|
||||||
|
@property
|
||||||
|
def estimated_duration(self):
|
||||||
|
return self.duration or self.category.duration
|
||||||
|
|
||||||
|
#def get_absolute_url(self):
|
||||||
|
# return reverse('show-talk', kwargs={'slug': self.slug})
|
||||||
|
|
||||||
|
def score(self):
|
||||||
|
if self.vote_set.exists():
|
||||||
|
return query_sum(self.vote_set, 'vote') / len(self.vote_set.all())
|
||||||
|
else:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
@property
|
||||||
|
def end_date(self):
|
||||||
|
if self.estimated_duration:
|
||||||
|
return self.start_date + timedelta(minutes=self.estimated_duration)
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
#@property
|
||||||
|
#def dtstart(self):
|
||||||
|
# return self.start_date.strftime('%Y%m%dT%H%M%SZ')
|
||||||
|
|
||||||
|
#@property
|
||||||
|
#def dtend(self):
|
||||||
|
# return self.end_date.strftime('%Y%m%dT%H%M%SZ')
|
||||||
|
|
||||||
|
#@property
|
||||||
|
#def materials_name(self):
|
||||||
|
# return basename(self.materials.name)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ('category__id', 'title',)
|
||||||
|
|
||||||
|
|
||||||
|
class Vote(PonyConfModel):
|
||||||
|
|
||||||
|
talk = models.ForeignKey(Talk)
|
||||||
|
user = models.ForeignKey(User)
|
||||||
|
vote = models.IntegerField(validators=[MinValueValidator(-2), MaxValueValidator(2)], default=0)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
unique_together = ('talk', 'user')
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "%+i by %s for %s" % (self.vote, self.user, self.talk)
|
||||||
|
|
||||||
|
#def get_absolute_url(self):
|
||||||
|
# return self.talk.get_absolute_url()
|
|
@ -0,0 +1,28 @@
|
||||||
|
from django.conf.urls import url
|
||||||
|
|
||||||
|
#from proposals import views
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
#url(r'^markdown/$', views.markdown_preview, name='markdown'),
|
||||||
|
#url(r'^$', views.home, name='home'),
|
||||||
|
#url(r'^staff/$', views.staff, name='staff'),
|
||||||
|
#url(r'^conference/$', views.conference, name='edit-conference'),
|
||||||
|
#url(r'^talk/propose/$', views.participate, name='participate-as-speaker'),
|
||||||
|
#url(r'^talk/$', views.talk_list, name='list-talks'),
|
||||||
|
#url(r'^talk/add/$', views.talk_edit, name='add-talk'),
|
||||||
|
#url(r'^talk/edit/(?P<talk>[-\w]+)$', views.talk_edit, name='edit-talk'),
|
||||||
|
#url(r'^talk/vote/(?P<talk>[-\w]+)/(?P<score>[-0-2]+)$', views.vote, name='vote'),
|
||||||
|
#url(r'^talk/details/(?P<slug>[-\w]+)$', views.TalkDetail.as_view(), name='show-talk'),
|
||||||
|
#url(r'^talk/accept/(?P<talk>[-\w]+)/$', views.talk_decide, {'accepted': True}, name='accept-talk'),
|
||||||
|
#url(r'^talk/decline/(?P<talk>[-\w]+)/$', views.talk_decide, {'accepted': False}, name='decline-talk'),
|
||||||
|
#url(r'^talk/assign-to-track/(?P<talk>[-\w]+)/(?P<track>[-\w]+)/$', views.talk_assign_to_track, name='assign-talk-to-track'),
|
||||||
|
#url(r'^topic/$', views.TopicList.as_view(), name='list-topics'),
|
||||||
|
#url(r'^topic/add/$', views.TopicCreate.as_view(), name='add-topic'),
|
||||||
|
#url(r'^topic/(?P<slug>[-\w]+)/edit/$', views.TopicUpdate.as_view(), name='edit-topic'),
|
||||||
|
#url(r'^track/$', views.TrackList.as_view(), name='list-tracks'),
|
||||||
|
#url(r'^track/add/$', views.TrackCreate.as_view(), name='add-track'),
|
||||||
|
#url(r'^track/(?P<slug>[-\w]+)/edit/$', views.TrackUpdate.as_view(), name='edit-track'),
|
||||||
|
#url(r'^speakers/$', views.speaker_list, name='list-speakers'),
|
||||||
|
#url(r'^register/$', views.talk_registrable_list, name='list-registrable-talks'),
|
||||||
|
#url(r'^register/(?P<talk>[-\w]+)$', views.talk_register, name='register-for-a-talk'),
|
||||||
|
]
|
|
@ -0,0 +1,31 @@
|
||||||
|
from django.utils.crypto import get_random_string
|
||||||
|
|
||||||
|
|
||||||
|
def generate_user_uid():
|
||||||
|
return get_random_string(length=12, allowed_chars='abcdefghijklmnopqrstuvwxyz0123456789')
|
||||||
|
|
||||||
|
from django.contrib.sites.shortcuts import get_current_site
|
||||||
|
from django.db.models import Q, Sum
|
||||||
|
from django.db.models.functions import Coalesce
|
||||||
|
from django.utils.safestring import mark_safe
|
||||||
|
|
||||||
|
#from accounts.models import Participation
|
||||||
|
|
||||||
|
from markdown import markdown
|
||||||
|
import bleach
|
||||||
|
|
||||||
|
|
||||||
|
def query_sum(queryset, field):
|
||||||
|
return queryset.aggregate(s=Coalesce(Sum(field), 0))['s']
|
||||||
|
|
||||||
|
|
||||||
|
def allowed_talks(talks, request):
|
||||||
|
if not Participation.objects.get(site=get_current_site(request), user=request.user).is_orga():
|
||||||
|
talks = talks.filter(Q(topics__reviewers=request.user) | Q(speakers=request.user) | Q(proposer=request.user))
|
||||||
|
return talks.distinct()
|
||||||
|
|
||||||
|
def markdown_to_html(md):
|
||||||
|
html = markdown(md)
|
||||||
|
allowed_tags = bleach.ALLOWED_TAGS + ['p', 'pre', 'span' ] + ['h%d' % i for i in range(1, 7) ]
|
||||||
|
html = bleach.clean(html, tags=allowed_tags)
|
||||||
|
return mark_safe(html)
|
|
@ -37,19 +37,20 @@ INSTALLED_APPS = [
|
||||||
'django.contrib.sites',
|
'django.contrib.sites',
|
||||||
|
|
||||||
# our apps
|
# our apps
|
||||||
'accounts',
|
#'accounts',
|
||||||
'ponyconf',
|
'ponyconf',
|
||||||
'proposals',
|
'cfp',
|
||||||
'conversations',
|
#'proposals',
|
||||||
'planning',
|
#'conversations',
|
||||||
'volunteers',
|
#'planning',
|
||||||
|
#'volunteers',
|
||||||
|
|
||||||
# external apps
|
# external apps
|
||||||
'djangobower',
|
#'djangobower',
|
||||||
'bootstrap3',
|
'bootstrap3',
|
||||||
'registration',
|
#'registration',
|
||||||
'django_select2',
|
#'django_select2',
|
||||||
'avatar',
|
#'avatar',
|
||||||
|
|
||||||
# build-in apps
|
# build-in apps
|
||||||
'django.contrib.admin',
|
'django.contrib.admin',
|
||||||
|
|
|
@ -21,9 +21,10 @@ from django.conf import settings
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^admin/', admin.site.urls),
|
url(r'^admin/', admin.site.urls),
|
||||||
url(r'^accounts/', include('accounts.urls')),
|
url(r'^accounts/', include('accounts.urls')),
|
||||||
url(r'', include('proposals.urls')),
|
url(r'^', include('cfp.urls')),
|
||||||
url(r'', include('planning.urls')),
|
#url(r'', include('proposals.urls')),
|
||||||
url(r'^volunteers/', include('volunteers.urls')),
|
#url(r'', include('planning.urls')),
|
||||||
url(r'^conversations/', include('conversations.urls')),
|
#url(r'^volunteers/', include('volunteers.urls')),
|
||||||
url(r'^select2/', include('django_select2.urls')),
|
#url(r'^conversations/', include('conversations.urls')),
|
||||||
|
#url(r'^select2/', include('django_select2.urls')),
|
||||||
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||||
|
|
Loading…
Reference in New Issue