From 22a0fe3ca95fdeea7cda74562dc2fb712f377369 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89lie=20Bouttier?= Date: Mon, 22 Aug 2016 17:23:01 +0200 Subject: [PATCH] Convert event from integer field to foreign key This commit also fix conference creation for initial Site --- accounts/apps.py | 1 + accounts/signals.py | 3 +- conversations/tests.py | 5 +- ponyconf/settings.py | 4 +- proposals/__init__.py | 1 + proposals/admin.py | 20 ++++- proposals/apps.py | 2 + .../migrations/0010_auto_20160822_1010.py | 87 +++++++++++++++++++ proposals/models.py | 19 +++- proposals/signals.py | 25 +++++- .../templates/proposals/talk_detail.html | 2 +- 11 files changed, 155 insertions(+), 14 deletions(-) create mode 100644 proposals/migrations/0010_auto_20160822_1010.py diff --git a/accounts/apps.py b/accounts/apps.py index 4e6ca7c..cf90676 100644 --- a/accounts/apps.py +++ b/accounts/apps.py @@ -7,3 +7,4 @@ class AccountsConfig(AppConfig): def ready(self): import accounts.signals # noqa + post_migrate.connect(accounts.signals.create_default_options, sender=self) diff --git a/accounts/signals.py b/accounts/signals.py index dcaa00d..dd50329 100644 --- a/accounts/signals.py +++ b/accounts/signals.py @@ -2,7 +2,7 @@ from django.contrib import messages from django.contrib.auth.models import User from django.contrib.auth.signals import user_logged_in, user_logged_out from django.contrib.sites.shortcuts import get_current_site -from django.db.models.signals import post_migrate, post_save +from django.db.models.signals import post_save from django.dispatch import receiver from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_noop @@ -10,7 +10,6 @@ from django.utils.translation import ugettext_noop from .models import Connector, Participation, Profile, Transport -@receiver(post_migrate) def create_default_options(sender, **kwargs): Transport.objects.get_or_create(name=ugettext_noop('Train')) Transport.objects.get_or_create(name=ugettext_noop('Plane')) diff --git a/conversations/tests.py b/conversations/tests.py index 20c4992..cfe0e80 100644 --- a/conversations/tests.py +++ b/conversations/tests.py @@ -6,7 +6,7 @@ from django.core import mail from django.conf import settings from accounts.models import Participation -from proposals.models import Topic, Talk +from proposals.models import Topic, Talk, Event from .models import ConversationAboutTalk, ConversationWithParticipant, Message @@ -20,7 +20,8 @@ class ConversationTests(TestCase): conversation, _ = ConversationWithParticipant.objects.get_or_create(participation=pa) Message.objects.create(content='allo', conversation=conversation, author=b) Message.objects.create(content='aluil', conversation=conversation, author=a) - Talk.objects.get_or_create(site=Site.objects.first(), proposer=a, title='a talk', description='yay') + site = Site.objects.first() + Talk.objects.get_or_create(site=site, proposer=a, title='a talk', description='yay', event=Event.objects.get(site=site, name='other')) def test_models(self): talk, participant, message = (model.objects.first() for model in diff --git a/ponyconf/settings.py b/ponyconf/settings.py index 32efb1d..e5af6ac 100644 --- a/ponyconf/settings.py +++ b/ponyconf/settings.py @@ -33,6 +33,9 @@ ALLOWED_HOSTS = [] # Application definition INSTALLED_APPS = [ + # the post_migrate creating the first site should be call at first + 'django.contrib.sites', + # our apps 'accounts', 'ponyconf', @@ -53,7 +56,6 @@ INSTALLED_APPS = [ 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', - 'django.contrib.sites', ] MIDDLEWARE_CLASSES = [ diff --git a/proposals/__init__.py b/proposals/__init__.py index e69de29..b83842a 100644 --- a/proposals/__init__.py +++ b/proposals/__init__.py @@ -0,0 +1 @@ +default_app_config = 'proposals.apps.ProposalsConfig' diff --git a/proposals/admin.py b/proposals/admin.py index bb3588c..c120197 100644 --- a/proposals/admin.py +++ b/proposals/admin.py @@ -1,7 +1,23 @@ from django.contrib import admin -from proposals.models import Conference, Talk, Topic +from proposals.models import Conference, Talk, Topic, Event + + +class TalkAdmin(admin.ModelAdmin): + # Disable add button in django admin has it is too dangerous + # (it is easy to obtain incoherent data due to site framework) + def has_add_permission(self, request): + return False + # Filter for 'on site' tocpis and event + def get_form(self, request, obj=None, **kwargs): + form = super(TalkAdmin, self).get_form(request, obj, **kwargs) + # in fact, obj should never be none as 'add' button is disabled + if obj: + form.base_fields['topics'].queryset = Topic.objects.filter(site=obj.site) + form.base_fields['event'].queryset = Event.objects.filter(site=obj.site) + return form admin.site.register(Conference) admin.site.register(Topic) -admin.site.register(Talk) +admin.site.register(Talk, TalkAdmin) +admin.site.register(Event) diff --git a/proposals/apps.py b/proposals/apps.py index 9a14a60..6245495 100644 --- a/proposals/apps.py +++ b/proposals/apps.py @@ -1,4 +1,5 @@ from django.apps import AppConfig +from django.db.models.signals import post_migrate class ProposalsConfig(AppConfig): @@ -6,3 +7,4 @@ class ProposalsConfig(AppConfig): def ready(self): import proposals.signals # noqa + post_migrate.connect(proposals.signals.call_first_site_post_save, sender=self) diff --git a/proposals/migrations/0010_auto_20160822_1010.py b/proposals/migrations/0010_auto_20160822_1010.py new file mode 100644 index 0000000..223f575 --- /dev/null +++ b/proposals/migrations/0010_auto_20160822_1010.py @@ -0,0 +1,87 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10 on 2016-08-22 10:10 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +def migrate_event(apps, schema_editor): + db_alias = schema_editor.connection.alias + # Create default Event instance, as Site post_save is not triggered + Event = apps.get_model('proposals', 'Event') + Site = apps.get_model('sites', 'Site') + for site in Site.objects.using(db_alias).all(): + Event.objects.using(db_alias).bulk_create([ + Event(site=site, name='conference (short)'), + Event(site=site, name='conference (long)'), + Event(site=site, name='workshop'), + Event(site=site, name='stand'), + Event(site=site, name='other'), + ]) + # Migrate event_old field to event field + Talk = apps.get_model('proposals', 'Talk') + mapping = { + 0: 'conference (short)', + 1: 'conference (long)', + 2: 'workshop', + 3: 'stand', + 4: 'other', + } + for talk in Talk.objects.using(db_alias).all(): + talk.event = Event.objects.using(db_alias).get(site=talk.site, name=mapping[talk.event_old]) + talk.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ('sites', '0002_alter_domain_unique'), + ('proposals', '0009_auto_20160822_0921'), + ] + + operations = [ + # Creation Event model + migrations.CreateModel( + name='Event', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=64)), + ('site', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='sites.Site')), + ], + ), + migrations.AlterUniqueTogether( + name='event', + unique_together=set([('site', 'name')]), + ), + # Move field event to event_old + migrations.RenameField( + model_name='talk', + old_name='event', + new_name='event_old', + ), + # Add field event as ForeignKey to Event model (with NULL allowed for now) + migrations.AddField( + model_name='talk', + name='event', + field=models.ForeignKey(to='proposals.Event', null=True), + ), + # We slightly need to modify the ordering to work with the M2M + migrations.AlterModelOptions( + name='talk', + options={'ordering': ('event__id',)}, + ), + # Migrate the data from event_old field to event field + migrations.RunPython(migrate_event), + # Remove the event_old field as data have been migrated + migrations.RemoveField( + model_name='talk', + name='event_old', + ), + # As data are now migrated, switch to null=False for event field + migrations.AlterField( + model_name='talk', + name='event', + field=models.ForeignKey(to='proposals.Event') + ), + ] diff --git a/proposals/models.py b/proposals/models.py index 0ff11aa..8999d37 100644 --- a/proposals/models.py +++ b/proposals/models.py @@ -6,6 +6,7 @@ from django.core.urlresolvers import reverse from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import ugettext from autoslug import AutoSlugField @@ -44,9 +45,19 @@ class Topic(PonyConfModel): return reverse('list-talks-by-topic', kwargs={'topic': self.slug}) -class Talk(PonyConfModel): +class Event(models.Model): - EVENTS = IntEnum('Event', 'conference_short conference_long workshop stand other') + site = models.ForeignKey(Site, on_delete=models.CASCADE) + name = models.CharField(max_length=64) + + class Meta: + unique_together = ('site', 'name') + + def __str__(self): + return ugettext(self.name) + + +class Talk(PonyConfModel): site = models.ForeignKey(Site, on_delete=models.CASCADE) @@ -58,7 +69,7 @@ class Talk(PonyConfModel): description = models.TextField(blank=True, verbose_name=_('Description')) topics = models.ManyToManyField(Topic, blank=True, verbose_name=_('Topics')) notes = models.TextField(blank=True, verbose_name=_('Notes')) - event = models.IntegerField(choices=enum_to_choices(EVENTS), default=EVENTS.conference_short.value, verbose_name=_('Format')) + event = models.ForeignKey(Event) accepted = models.NullBooleanField(default=None) def __str__(self): @@ -83,7 +94,7 @@ class Talk(PonyConfModel): return query_sum(self.vote_set, 'vote') class Meta: - ordering = ('event',) + ordering = ('event__id',) class Vote(PonyConfModel): diff --git a/proposals/signals.py b/proposals/signals.py index 89cc46e..8abd801 100644 --- a/proposals/signals.py +++ b/proposals/signals.py @@ -1,10 +1,13 @@ -from django.db.models.signals import m2m_changed, post_migrate, post_save +from django.db.models.signals import m2m_changed, post_save from django.dispatch import Signal, receiver from django.contrib.sites.models import Site +from django.utils.translation import ugettext_noop +from django.conf import settings from accounts.models import Participation -from .models import Conference, Talk, Topic +from .models import Conference, Talk, Topic, Event + talk_added = Signal(providing_args=["sender", "instance", "author"]) talk_edited = Signal(providing_args=["sender", "instance", "author"]) @@ -15,6 +18,24 @@ def create_conference(sender, instance, **kwargs): Conference.objects.get_or_create(site=instance) +@receiver(post_save, sender=Site, dispatch_uid="Create default events type for Site") +def create_events(sender, instance, **kwargs): + if not Event.objects.filter(site=instance).exists(): + Event.objects.bulk_create([ + Event(site=instance, name=ugettext_noop('conference (short)')), + Event(site=instance, name=ugettext_noop('conference (long)')), + Event(site=instance, name=ugettext_noop('workshop')), + Event(site=instance, name=ugettext_noop('stand')), + Event(site=instance, name=ugettext_noop('other')), + ]) + + +def call_first_site_post_save(apps, **kwargs): + site = Site.objects.filter(id=getattr(settings, 'SITE_ID', 1)) + if site.exists(): + site.first().save() + + @receiver(m2m_changed, sender=Talk.speakers.through, dispatch_uid="Create Participation for speakers") def create_participation_for_speakers(sender, instance, action, reverse, model, pk_set, using, **kwargs): if action != "pre_add": diff --git a/proposals/templates/proposals/talk_detail.html b/proposals/templates/proposals/talk_detail.html index 614e39e..d60d6bf 100644 --- a/proposals/templates/proposals/talk_detail.html +++ b/proposals/templates/proposals/talk_detail.html @@ -14,7 +14,7 @@

{% trans "Format:" %}

-

{{ talk.get_event_display }}

+

{{ talk.event }}

{% trans "Abstract:" %}