diff --git a/accounts/models.py b/accounts/models.py index 79b5f1c..473ed25 100644 --- a/accounts/models.py +++ b/accounts/models.py @@ -9,15 +9,13 @@ from django.utils.translation import ugettext from ponyconf.utils import PonyConfModel, enum_to_choices -from .utils import generate_user_uid +#from .utils import generate_user_uid class Profile(PonyConfModel): user = models.OneToOneField(User) 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')) linkedin = models.CharField(max_length=100, blank=True, default='', verbose_name=_('LinkedIn')) @@ -29,125 +27,5 @@ class Profile(PonyConfModel): def __str__(self): return self.user.get_full_name() or self.user.username - def get_absolute_url(self): - 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) + #def get_absolute_url(self): + # return reverse('profile') diff --git a/accounts/urls.py b/accounts/urls.py index 4f722ea..246265f 100644 --- a/accounts/urls.py +++ b/accounts/urls.py @@ -2,16 +2,17 @@ from django.conf import settings from django.conf.urls import include, url from django.contrib.auth import views as auth_views -from . import views +#from . import views urlpatterns = [ - url(r'^profile/$', views.profile, name='profile'), - url(r'^login/$', auth_views.login, {'extra_context': {'buttons': [views.RESET_PASSWORD_BUTTON]}}, name='login'), + #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, name='login'), 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/(?P[\w.@+-]+)$', views.participant_details, name='show-participant'), - url(r'^participant/(?P[\w.@+-]+)/edit/$', views.participant_edit, name='edit-participant'), - url(r'^avatar/', include('avatar.urls')), + #url(r'^participant/$', views.participation_list, name='list-participants'), + #url(r'^participant/(?P[\w.@+-]+)$', views.participant_details, name='show-participant'), + #url(r'^participant/(?P[\w.@+-]+)/edit/$', views.participant_edit, name='edit-participant'), + #url(r'^avatar/', include('avatar.urls')), url(r'', include('django.contrib.auth.urls')), - url(r'', include('registration.backends.default.urls')), + #url(r'', include('registration.backends.default.urls')), ] diff --git a/accounts/utils.py b/accounts/utils.py deleted file mode 100644 index 8854676..0000000 --- a/accounts/utils.py +++ /dev/null @@ -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() diff --git a/cfp/__init__.py b/cfp/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cfp/apps.py b/cfp/apps.py new file mode 100644 index 0000000..6933c94 --- /dev/null +++ b/cfp/apps.py @@ -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) diff --git a/cfp/migrations/0001_initial.py b/cfp/migrations/0001_initial.py new file mode 100644 index 0000000..1988bf2 --- /dev/null +++ b/cfp/migrations/0001_initial.py @@ -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')]), + ), + ] diff --git a/cfp/migrations/__init__.py b/cfp/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cfp/models.py b/cfp/models.py new file mode 100644 index 0000000..1233756 --- /dev/null +++ b/cfp/models.py @@ -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() diff --git a/cfp/urls.py b/cfp/urls.py new file mode 100644 index 0000000..c2c6c06 --- /dev/null +++ b/cfp/urls.py @@ -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[-\w]+)$', views.talk_edit, name='edit-talk'), + #url(r'^talk/vote/(?P[-\w]+)/(?P[-0-2]+)$', views.vote, name='vote'), + #url(r'^talk/details/(?P[-\w]+)$', views.TalkDetail.as_view(), name='show-talk'), + #url(r'^talk/accept/(?P[-\w]+)/$', views.talk_decide, {'accepted': True}, name='accept-talk'), + #url(r'^talk/decline/(?P[-\w]+)/$', views.talk_decide, {'accepted': False}, name='decline-talk'), + #url(r'^talk/assign-to-track/(?P[-\w]+)/(?P[-\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[-\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[-\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[-\w]+)$', views.talk_register, name='register-for-a-talk'), +] diff --git a/cfp/utils.py b/cfp/utils.py new file mode 100644 index 0000000..6db8b88 --- /dev/null +++ b/cfp/utils.py @@ -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) diff --git a/ponyconf/settings.py b/ponyconf/settings.py index f2fa409..a9a9d3e 100644 --- a/ponyconf/settings.py +++ b/ponyconf/settings.py @@ -37,19 +37,20 @@ INSTALLED_APPS = [ 'django.contrib.sites', # our apps - 'accounts', + #'accounts', 'ponyconf', - 'proposals', - 'conversations', - 'planning', - 'volunteers', + 'cfp', + #'proposals', + #'conversations', + #'planning', + #'volunteers', # external apps - 'djangobower', + #'djangobower', 'bootstrap3', - 'registration', - 'django_select2', - 'avatar', + #'registration', + #'django_select2', + #'avatar', # build-in apps 'django.contrib.admin', diff --git a/ponyconf/urls.py b/ponyconf/urls.py index 243a5d8..d23c1dd 100644 --- a/ponyconf/urls.py +++ b/ponyconf/urls.py @@ -21,9 +21,10 @@ from django.conf import settings urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^accounts/', include('accounts.urls')), - url(r'', include('proposals.urls')), - url(r'', include('planning.urls')), - url(r'^volunteers/', include('volunteers.urls')), - url(r'^conversations/', include('conversations.urls')), - url(r'^select2/', include('django_select2.urls')), + url(r'^', include('cfp.urls')), + #url(r'', include('proposals.urls')), + #url(r'', include('planning.urls')), + #url(r'^volunteers/', include('volunteers.urls')), + #url(r'^conversations/', include('conversations.urls')), + #url(r'^select2/', include('django_select2.urls')), ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)