diff --git a/event_track_generate/README.rst b/event_track_generate/README.rst new file mode 100644 index 000000000..3dee666f0 --- /dev/null +++ b/event_track_generate/README.rst @@ -0,0 +1,49 @@ +.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg + :alt: License: AGPL-3 + +Generate Event Tracks +===================== + +This module was written to extend the functionality of event tracks to support +generating many tracks at once. + +Usage +===== + +To use this module, you need to: + +* Open an event. +* In the *More* top menu, select *Generate event tracks*. + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed feedback +`here `_. + + +Credits +======= + +Contributors +------------ + +* `Grupo ESOC `_: + * `Jairo Llopis `_. + +Maintainer +---------- + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +This module is maintained by the OCA. + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +To contribute to this module, please visit http://odoo-community.org. diff --git a/event_track_generate/__init__.py b/event_track_generate/__init__.py new file mode 100644 index 000000000..27a9e10b0 --- /dev/null +++ b/event_track_generate/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +# © 2015 Grupo ESOC Ingeniería de Servicios, S.L.U. + +from . import wizards diff --git a/event_track_generate/__openerp__.py b/event_track_generate/__openerp__.py new file mode 100644 index 000000000..967a5b96b --- /dev/null +++ b/event_track_generate/__openerp__.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +# © 2015 Grupo ESOC Ingeniería de Servicios, S.L.U. + +{ + "name": "Generate Event Tracks", + "version": "8.0.2.0.0", + "category": "Events", + "author": "Odoo Community Association (OCA)", + "license": "AGPL-3", + "website": "http://www.grupoesoc.es", + "summary": "Generate a bunch of event tracks at once", + "depends": [ + "website_event_track", + ], + "data": [ + "wizards/wizard_generator_view.xml", + ], +} diff --git a/event_track_generate/i18n/es.po b/event_track_generate/i18n/es.po new file mode 100644 index 000000000..dfdf51b02 --- /dev/null +++ b/event_track_generate/i18n/es.po @@ -0,0 +1,246 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * event_track_generate +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 8.0-20150514\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-01-13 12:06+0100\n" +"PO-Revision-Date: 2016-01-13 12:06+0100\n" +"Last-Translator: Jairo Llopis \n" +"Language-Team: \n" +"Language: es_ES\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: Poedit 1.8.5\n" + +#. module: event_track_generate +#: field:event.track.generator,adjust_end_time:0 +msgid "Adjust end time" +msgstr "Ajustar hora de finalización" + +#. module: event_track_generate +#: field:event.track.generator,adjust_start_time:0 +msgid "Adjust start time" +msgstr "Ajustar hora de comienzo" + +#. module: event_track_generate +#: view:event.track.generator:event_track_generate.generator_view_form +msgid "Cancel" +msgstr "Cancelar" + +#. module: event_track_generate +#: help:event.track.generator,fridays:0 +msgid "Create tracks on Fridays." +msgstr "Crear sesiones los viernes." + +#. module: event_track_generate +#: help:event.track.generator,mondays:0 +msgid "Create tracks on Mondays." +msgstr "Crear sesiones los lunes." + +#. module: event_track_generate +#: help:event.track.generator,saturdays:0 +msgid "Create tracks on Saturdays." +msgstr "Crear sesiones los sábados." + +#. module: event_track_generate +#: help:event.track.generator,sundays:0 +msgid "Create tracks on Sundays." +msgstr "Crear sesiones los domingos." + +#. module: event_track_generate +#: help:event.track.generator,thursdays:0 +msgid "Create tracks on Thursdays." +msgstr "Crear sesiones los jueves." + +#. module: event_track_generate +#: help:event.track.generator,tuesdays:0 +msgid "Create tracks on Tuesdays." +msgstr "Crear sesiones los martes." + +#. module: event_track_generate +#: help:event.track.generator,wednesdays:0 +msgid "Create tracks on Wednesdays." +msgstr "Crear sesiones los miércoles." + +#. module: event_track_generate +#: field:event.track.generator,create_uid:0 +msgid "Created by" +msgstr "Creado por" + +#. module: event_track_generate +#: field:event.track.generator,create_date:0 +msgid "Created on" +msgstr "Creado el" + +#. module: event_track_generate +#: view:event.track.generator:event_track_generate.generator_view_form +msgid "Dates and event data" +msgstr "Fechas y datos del evento" + +#. module: event_track_generate +#: field:event.track.generator,delete_existing_tracks:0 +msgid "Delete existing tracks" +msgstr "Borrar sesiones existentes" + +#. module: event_track_generate +#: field:event.track.generator,duration:0 +msgid "Duration" +msgstr "Duración" + +#. module: event_track_generate +#: help:event.track.generator,duration:0 +msgid "Each track will have this duration." +msgstr "Cada sesión tendrá esta duración." + +#. module: event_track_generate +#: help:event.track.generator,start_time:0 +msgid "Each track will start at this time (in the event's timezone)." +msgstr "Cada sesión empezará a esta hora (en el huso horario del evento)." + +#. module: event_track_generate +#: field:event.track.generator,event_id:0 +msgid "Event" +msgstr "Evento" + +#. module: event_track_generate +#: field:event.track.generator,fridays:0 +msgid "Fridays" +msgstr "Viernes" + +#. module: event_track_generate +#: model:ir.actions.act_window,name:event_track_generate.generator_action +msgid "Generate event tracks" +msgstr "Generar sesiones de eventos" + +#. module: event_track_generate +#: view:event.track.generator:event_track_generate.generator_view_form +msgid "Generate tracks" +msgstr "Generar sesiones" + +#. module: event_track_generate +#: field:event.track.generator,id:0 +msgid "ID" +msgstr "ID" + +#. module: event_track_generate +#: field:event.track.generator,write_uid:0 +msgid "Last Updated by" +msgstr "Última actualización por" + +#. module: event_track_generate +#: field:event.track.generator,write_date:0 +msgid "Last Updated on" +msgstr "Última actualización el" + +#. module: event_track_generate +#: field:event.track.generator,location_id:0 +msgid "Location" +msgstr "Localización" + +#. module: event_track_generate +#: help:event.track.generator,adjust_end_time:0 +msgid "Make event's end time match the end of the last track." +msgstr "" +"Hacer que la hora de finalización del evento coincida con la de la última " +"sesión." + +#. module: event_track_generate +#: help:event.track.generator,adjust_start_time:0 +msgid "Make event's start time match the start of the first track." +msgstr "" +"Hacer que la hora de comienzo del evento coincida con la de la primera " +"sesión." + +#. module: event_track_generate +#: field:event.track.generator,mondays:0 +msgid "Mondays" +msgstr "Lunes" + +#. module: event_track_generate +#: view:event.track.generator:event_track_generate.generator_view_form +msgid "Other options" +msgstr "Otras opciones" + +#. module: event_track_generate +#: field:event.track.generator,publish_tracks_in_website:0 +msgid "Publish tracks in website" +msgstr "Publicar sesiones en el sitio web" + +#. module: event_track_generate +#: field:event.track.generator,saturdays:0 +msgid "Saturdays" +msgstr "Sábados" + +#. module: event_track_generate +#: view:event.track.generator:event_track_generate.generator_view_form +msgid "Schedule" +msgstr "Horario" + +#. module: event_track_generate +#: field:event.track.generator,speaker_ids:0 +msgid "Speakers" +msgstr "Ponentes" + +#. module: event_track_generate +#: field:event.track.generator,start_time:0 +msgid "Start time" +msgstr "Hora de comienzo" + +#. module: event_track_generate +#: field:event.track.generator,sundays:0 +msgid "Sundays" +msgstr "Domingos" + +#. module: event_track_generate +#: field:event.track.generator,tag_ids:0 +msgid "Tags" +msgstr "Etiquetas" + +#. module: event_track_generate +#: field:event.track.generator,thursdays:0 +msgid "Thursdays" +msgstr "Jueves" + +#. module: event_track_generate +#: help:event.track.generator,name:0 +msgid "Title that will be assigned to all created tracks." +msgstr "Título que se asignará a todas las sesiones creadas." + +#. module: event_track_generate +#: field:event.track.generator,name:0 +msgid "Track title" +msgstr "Título de la sesión" + +#. module: event_track_generate +#: field:event.track.generator,tuesdays:0 +msgid "Tuesdays" +msgstr "Martes" + +#. module: event_track_generate +#: field:event.track.generator,wednesdays:0 +msgid "Wednesdays" +msgstr "Miércoles" + +#. module: event_track_generate +#: view:event.track.generator:event_track_generate.generator_view_form +msgid "Weekdays" +msgstr "Días de la semana" + +#. module: event_track_generate +#: code:addons/event_track_generate/wizards/wizard_generator.py:83 +#, python-format +msgid "You must select at least one weekday." +msgstr "Debe seleccionar al menos un día de la semana." + +#. module: event_track_generate +#: view:event.track.generator:event_track_generate.generator_view_form +msgid "or" +msgstr "o" + +#~ msgid "Error(s) with the event track generator." +#~ msgstr "Error(es) con el generador de sesiones de eventos." diff --git a/event_track_generate/static/description/icon.png b/event_track_generate/static/description/icon.png new file mode 100644 index 000000000..656a4fcb4 Binary files /dev/null and b/event_track_generate/static/description/icon.png differ diff --git a/event_track_generate/static/description/icon.svg b/event_track_generate/static/description/icon.svg new file mode 100644 index 000000000..17804ca66 --- /dev/null +++ b/event_track_generate/static/description/icon.svg @@ -0,0 +1,220 @@ + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/event_track_generate/tests/__init__.py b/event_track_generate/tests/__init__.py new file mode 100644 index 000000000..7201b150f --- /dev/null +++ b/event_track_generate/tests/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +# © 2015 Grupo ESOC Ingeniería de Servicios, S.L.U. + +from . import test_generator diff --git a/event_track_generate/tests/test_generator.py b/event_track_generate/tests/test_generator.py new file mode 100644 index 000000000..aa51b8734 --- /dev/null +++ b/event_track_generate/tests/test_generator.py @@ -0,0 +1,130 @@ +# -*- coding: utf-8 -*- +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +# © 2015 Grupo ESOC Ingeniería de Servicios, S.L.U. + +from datetime import datetime +from itertools import product +from pytz import timezone +from openerp import fields +from openerp.tests.common import TransactionCase +from openerp.exceptions import ValidationError + + +class GeneratorCase(TransactionCase): + def setUp(self): + super(GeneratorCase, self).setUp() + + # Create an event + begin = datetime(2015, 8, 10, 7) + self.event = self.env["event.event"].create({ + "name": "Test event %s" % __name__, + "date_begin": begin, # Monday + "date_end": datetime(2015, 8, 23, 22), # Sunday + "date_tz": "Europe/Madrid"}) + self.tzdiff = timezone(self.event.date_tz).utcoffset(begin) + + # Add some tracks to the event + for day in range(10, 14): + self.event.track_ids |= self.env["event.track"].create({ + "name": u"Traçk name", + "event_id": self.event.id, + "date": datetime(2015, 8, day, 10, 15) - self.tzdiff, + "duration": 2.75}) + + # Create a generator + self.generator = self.env["event.track.generator"].create({ + "event_id": self.event.id, + "name": u"Some trâck name", + "start_time": 10.25, + "duration": 2.75, + "adjust_start_time": False, + "adjust_end_time": False}) + + def test_adjust_start_time(self): + """Event start time is adjusted correctly.""" + self.generator.adjust_start_time = True + self.generator.mondays = True + self.generator.action_generate() + self.assertEqual( + fields.Datetime.from_string(self.event.date_begin), + datetime(2015, 8, 10, 10, 15) - self.tzdiff) + + def test_adjust_end_time(self): + """Event end time is adjusted correctly.""" + self.generator.adjust_end_time = True + self.generator.sundays = True + self.generator.action_generate() + self.assertEqual( + fields.Datetime.from_string(self.event.date_end), + datetime(2015, 8, 23, 13) - self.tzdiff) + + def test_end_time(self): + """Test if end time is measured right.""" + self.assertEqual( + self.generator.end_time, + self.generator.start_time + self.generator.duration) + + def test_with_weekdays_and_delete_existing_tracks(self): + """Test generating all possible weekdays combination. + + Test also if the deletion of existing tracks work. + """ + # Delete tracks each time to check if correct weekdays are created + self.generator.delete_existing_tracks = True + + # Get all possible weekday combinations + for days in product([True, False], repeat=7): + + # For no weekdays, there's the test :meth:`~.test_without_weekdays` + if True in days: + (self.generator.mondays, + self.generator.tuesdays, + self.generator.wednesdays, + self.generator.thursdays, + self.generator.fridays, + self.generator.saturdays, + self.generator.sundays) = days + + # Generate tracks + self.generator.action_generate() + + # This will be used to know if they were generated fine + generated_weekdays = [0] * 7 + + for track in self.event.track_ids.exists(): + start_dt = fields.Datetime.from_string(track.date) + + # Count how many days were generated with this weekday + generated_weekdays[start_dt.weekday()] += 1 + + # There must be 2 or 0 days of each + self.assertEqual( + set(generated_weekdays), + {0, 2} if False in days else {2}) + + # Check that only the requested weekdays were generated + self.assertEqual( + tuple(bool(w) for w in generated_weekdays), + days) + + def test_without_deleting_existing_tracks(self): + """Test generating tracks without deleting existing ones.""" + self.generator.delete_existing_tracks = False + current = len(self.event.track_ids.exists()) + + # Must generate 1 monday + self.generator.mondays = True + self.generator.action_generate() + current += 1 + self.assertEqual(current, len(self.event.track_ids.exists())) + + # Must generate 2 sundays + self.generator.sundays = True + self.generator.action_generate() + current += 2 + self.assertEqual(current, len(self.event.track_ids.exists())) + + def test_without_weekdays(self): + """Test trying to generate tracks without setting weekdays.""" + with self.assertRaises(ValidationError): + self.generator.action_generate() diff --git a/event_track_generate/wizards/__init__.py b/event_track_generate/wizards/__init__.py new file mode 100644 index 000000000..e4ec304e1 --- /dev/null +++ b/event_track_generate/wizards/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +# © 2015 Grupo ESOC Ingeniería de Servicios, S.L.U. + +from . import wizard_generator diff --git a/event_track_generate/wizards/wizard_generator.py b/event_track_generate/wizards/wizard_generator.py new file mode 100644 index 000000000..952b79e06 --- /dev/null +++ b/event_track_generate/wizards/wizard_generator.py @@ -0,0 +1,193 @@ +# -*- coding: utf-8 -*- +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +# © 2015 Grupo ESOC Ingeniería de Servicios, S.L.U. + +from datetime import datetime, timedelta +from pytz import timezone +from openerp import _, api, fields, models +from openerp.exceptions import ValidationError + + +class Generator(models.TransientModel): + _name = "event.track.generator" + + event_id = fields.Many2one( + "event.event", + string="Event", + default=lambda self: self.env.context["active_id"], + required=True) + event_date_begin = fields.Datetime( + related="event_id.date_begin", + help="Change it in the event form. " + "No tracks before this date will be generated.") + event_date_end = fields.Datetime( + related="event_id.date_end", + help="Change it in the event form. " + "No tracks after this date will be generated.") + event_date_tz = fields.Selection( + related="event_id.date_tz", + help="Change it in the event form. Timezone of the generated tracks.") + name = fields.Char( + "Track title", + required=True, + help="Title that will be assigned to all created tracks.") + location_id = fields.Many2one("event.track.location", "Location") + speaker_ids = fields.Many2many( + "res.partner", + string="Speakers", + relation="event_track_generate_generator_speaker_ids") + tag_ids = fields.Many2many( + "event.track.tag", + string="Tags", + relation="event_track_generate_generator_tag_ids") + mondays = fields.Boolean(help="Create tracks on Mondays.") + tuesdays = fields.Boolean(help="Create tracks on Tuesdays.") + wednesdays = fields.Boolean(help="Create tracks on Wednesdays.") + thursdays = fields.Boolean(help="Create tracks on Thursdays.") + fridays = fields.Boolean(help="Create tracks on Fridays.") + saturdays = fields.Boolean(help="Create tracks on Saturdays.") + sundays = fields.Boolean(help="Create tracks on Sundays.") + start_time = fields.Float( + required=True, + help="Each track will start at this time (in the event's timezone).") + duration = fields.Float( + required=True, + help="Each track will have this duration.") + end_time = fields.Float( + compute="_compute_end_time", + help="Each track will end at this time.") + delete_existing_tracks = fields.Boolean() + publish_tracks_in_website = fields.Boolean() + adjust_start_time = fields.Boolean( + default=True, + help="Make event's start time match the start of the first track.") + adjust_end_time = fields.Boolean( + default=True, + help="Make event's end time match the end of the last track.") + + @api.multi + @api.depends("start_time", "duration") + def _compute_end_time(self): + self.end_time = self.start_time + self.duration + + @api.multi + def action_generate(self): + """Generate event tracks according to received data. + + This is the main method of this class, triggered by the UI. + """ + # You need at least one weekday + weekdays = self.weekdays() + if not any(weekdays): + raise ValidationError(_("You must select at least one weekday.")) + + # Delete existing + if self.delete_existing_tracks: + self.event_id.track_ids.unlink() + + # Create new + self.generate_tracks() + + # Adjust event's dates + self.adjust_dates() + + @api.multi + def adjust_dates(self): + """Adjust event dates if asked to do so.""" + # Cheek if the user wanted to adjust dates + if self.event_id.track_ids.exists() and (self.adjust_start_time or + self.adjust_end_time): + sorted_ = self.event_id.track_ids.sorted(lambda r: r.date) + + # Start date + if self.adjust_start_time: + self.event_id.date_begin = sorted_[0].date + + # End date + if self.adjust_end_time: + self.event_id.date_end = fields.Datetime.to_string( + fields.Datetime.from_string(sorted_[-1].date) + + timedelta(hours=sorted_[-1].duration)) + + @api.multi + def create_track(self, **values): + """Create a new track record with the provided values.""" + data = { + "name": self.name, + "event_id": self.event_id.id, + "duration": self.duration, + "location_id": self.location_id.id, + "speaker_ids": [(6, False, self.speaker_ids.ids)], + "tag_ids": [(6, False, self.tag_ids.ids)], + "user_id": self.event_id.user_id.id, + "website_published": self.publish_tracks_in_website} + data.update(values) + return self.env["event.track"].create(data) + + @api.multi + def datetime_fields(self): + """Fields converted to Python's Datetime-based objects.""" + result = { + "event_start": fields.Datetime.from_string(self.event_date_begin), + "event_end": fields.Datetime.from_string(self.event_date_end), + "duration_delta": timedelta(hours=self.duration), + "day_delta": timedelta(days=1), + "start_time": + datetime.min + timedelta(hours=self.start_time), + } + + # Needed to manually fix timezone offset, for start_time + result["tzdiff"] = (timezone(self.event_id.date_tz or + self.env.context["tz"] or + self.env.user.tz or + "UTC") + .utcoffset(result["event_start"])) + return result + + @api.multi + def existing_tracks(self, date): + """Return existing tracks that match some criteria.""" + return self.env["event.track"].search( + (("event_id", "=", self.event_id.id), + ("date", "=", date), + ("duration", "=", self.duration))) + + @api.multi + def generate_tracks(self): + """Know which tracks must be generated and do it.""" + counter = 0 + dt = self.datetime_fields() + weekdays = self.weekdays() + + # Check that tracks fit between event start and end dates + current = dt["event_start"] + while current <= dt["event_end"]: + # Get start date and time with fixed timezone offset + current_start = datetime.combine( + current.date(), + dt["start_time"].time()) - dt["tzdiff"] + if (current_start >= dt["event_start"] and + weekdays[current.weekday()]): + current_end = current_start + dt["duration_delta"] + if current_end <= dt["event_end"]: + # Need string for the ORM + current_start = fields.Datetime.to_string(current_start) + + # Check that no track exists with this data + if not self.existing_tracks(current_start): + self.create_track(date=current_start) + counter += 1 + + # Next day + current += dt["day_delta"] + + @api.multi + def weekdays(self): + """Sorted weekdays user selection.""" + return (self.mondays, + self.tuesdays, + self.wednesdays, + self.thursdays, + self.fridays, + self.saturdays, + self.sundays) diff --git a/event_track_generate/wizards/wizard_generator_view.xml b/event_track_generate/wizards/wizard_generator_view.xml new file mode 100644 index 000000000..a8ac41649 --- /dev/null +++ b/event_track_generate/wizards/wizard_generator_view.xml @@ -0,0 +1,67 @@ + + + + + + + + + + Event track generator + event.track.generator + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+