diff --git a/social_media_calendar/README.rst b/social_media_calendar/README.rst new file mode 100644 index 0000000000..f938e22449 --- /dev/null +++ b/social_media_calendar/README.rst @@ -0,0 +1,196 @@ +===================== +Social Media Calendar +===================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:9ef0613fac59ec66dcecfb14f4bff101e89925782f08418a3cf3b64bdf731cd0 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fsocial-lightgray.png?logo=github + :target: https://github.com/OCA/social/tree/17.0/social_media_calendar + :alt: OCA/social +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/social-17-0/social-17-0-social_media_calendar + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/social&target_branch=17.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module provides the functionality to display posts in the calendar +view. + +It is installed automatically together with *Social Media Base*, because +the calendar is meant to be part of the standard experience of the +posts: it adds the calendar view to the Posts menu without any +configuration. Uninstalling it is permanent as long as *Social Media +Base* is not installed again: neither an update of the apps list nor an +upgrade of *Social Media Base* brings the calendar back, but installing +*Social Media Base* again does. + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +Calendar view +------------- + +1. Go to Social Media > Posts +2. Click on the Calendar icon to open the calendar view +3. Click on a Post and a popover with its details opens. Its *Edit* + button opens the post form in a dialog, where it can be modified and + saved. + +Each post is placed on the date it was published; if it has not been +published yet, on its schedule date; and if it is not scheduled, on its +creation date. That is why a scheduled post moves to another day of the +calendar once it is really published. + +Posts cannot be rescheduled by dragging them across the calendar: the +date shown is computed from the dates of the post. To change it, open +the post and modify its schedule date. + +Deletion is the only action disabled in this view: posts are deleted +from the list or the form. + +Colors by publication status +---------------------------- + +The calendar view will show the posts in different colors depending on +their publication status: + +- Draft + + |POST_DRAFT| + +- Planned + + |POST_PLANNED| + +- Publishing + + |POST_PUBLISHING| + +- Partially Published, the post that reached some of its accounts and + failed on the others + +- Published + + |POST_PUBLISHED| + +- Cancelled + + |POST_CANCELLED| + +- Calendar view by color + + |POSTS_CALENDAR| + +Creating a post from the calendar +--------------------------------- + +Users allowed to create posts can click on an empty day: the post form +opens with *Schedule* selected and 07:00 of that day, in the time zone +of the user, proposed as the schedule date. The accounts and the message +are still required, so the form is the full one, not a quick creation. +Once saved, the post is planned and the scheduler publishes it on that +date. + +Whenever that proposal is already behind the clock, a date one hour from +now is proposed instead: this happens on a day already past, and also on +the current day once 07:00 has gone by. A schedule date behind the clock +cannot be saved at all: *Social Media Base* refuses it with a validation +error. + +Uninstalling the module +----------------------- + +The calendar view is attached to the posts action of *Social Media Base* +through records that belong to *Social Media Calendar*, so Odoo removes +them on uninstall on its own and the Posts menu keeps working with its +kanban, list and form views. The module stays uninstalled afterwards; +only installing *Social Media Base* again brings it back, because it is +auto-installed. + +.. |POST_DRAFT| image:: https://raw.githubusercontent.com/OCA/social/17.0/social_media_calendar/static/img/readme/POST_DRAFT.png +.. |POST_PLANNED| image:: https://raw.githubusercontent.com/OCA/social/17.0/social_media_calendar/static/img/readme/POST_PLANNED.png +.. |POST_PUBLISHING| image:: https://raw.githubusercontent.com/OCA/social/17.0/social_media_calendar/static/img/readme/POST_PUBLISHING.png +.. |POST_PUBLISHED| image:: https://raw.githubusercontent.com/OCA/social/17.0/social_media_calendar/static/img/readme/POST_PUBLISHED.png +.. |POST_CANCELLED| image:: https://raw.githubusercontent.com/OCA/social/17.0/social_media_calendar/static/img/readme/POST_CANCELLED.png +.. |POSTS_CALENDAR| image:: https://raw.githubusercontent.com/OCA/social/17.0/social_media_calendar/static/img/readme/POSTS_CALENDAR.png + +Known issues / Roadmap +====================== + +This module, like the rest of the *Social Media* family, is not +compatible with the ``social`` module of Odoo Enterprise: both define +the same model names (``social.post``, ``social.account``, +``social.media``). The incompatibility is declared in the manifest of +*Social Media Base* with ``"excludes": ["social"]``, so Odoo refuses the +installation with an error if the ``social`` module of Enterprise is +already installed in the database, and the other way round. + +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 to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +------- + +* BinhexTeam + +Contributors +------------ + +- `Binhex `__: + + - Edilio Escalona Almira e.escalona@binhex.cloud + +Maintainers +----------- + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +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. + +.. |maintainer-edescalona| image:: https://github.com/edescalona.png?size=40px + :target: https://github.com/edescalona + :alt: edescalona + +Current `maintainer `__: + +|maintainer-edescalona| + +This module is part of the `OCA/social `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/social_media_calendar/__init__.py b/social_media_calendar/__init__.py new file mode 100644 index 0000000000..0077c8866c --- /dev/null +++ b/social_media_calendar/__init__.py @@ -0,0 +1,4 @@ +# Copyright 2025 Binhex +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from . import models diff --git a/social_media_calendar/__manifest__.py b/social_media_calendar/__manifest__.py new file mode 100644 index 0000000000..b227e457e3 --- /dev/null +++ b/social_media_calendar/__manifest__.py @@ -0,0 +1,24 @@ +# Copyright 2025 Binhex +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +{ + "name": "Social Media Calendar", + "summary": "Module for social media calendar integration.", + "version": "17.0.1.0.0", + "category": "Social Network", + "development_status": "Beta", + "license": "AGPL-3", + "author": "BinhexTeam,Odoo Community Association (OCA)", + "website": "https://github.com/OCA/social", + "maintainers": ["edescalona"], + "depends": ["social_media_base"], + "data": [ + "views/social_post_views.xml", + ], + "assets": { + "web.assets_backend": [ + "social_media_calendar/static/src/js/views/**/*.js", + ], + }, + "auto_install": True, + "installable": True, +} diff --git a/social_media_calendar/i18n/es.po b/social_media_calendar/i18n/es.po new file mode 100644 index 0000000000..a4f81ed570 --- /dev/null +++ b/social_media_calendar/i18n/es.po @@ -0,0 +1,44 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * social_media_calendar +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2026-08-10 16:21+0000\n" +"PO-Revision-Date: 2026-08-10 16:21+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"Language: es\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: social_media_calendar +#: model:ir.model.fields,field_description:social_media_calendar.field_social_post__color +msgid "Color" +msgstr "Color" + +#. module: social_media_calendar +#: model:ir.model.fields,field_description:social_media_calendar.field_social_post__date_calendar +msgid "Date Calendar" +msgstr "Fecha en el calendario" + +#. module: social_media_calendar +#. odoo-javascript +#: code:addons/social_media_calendar/static/src/js/views/calendar/social_calendar_controller.esm.js:0 +#, python-format +msgid "New Post" +msgstr "Nueva publicación" + +#. module: social_media_calendar +#: model:ir.model,name:social_media_calendar.model_social_post +msgid "Social Post" +msgstr "Publicación social" + +#. module: social_media_calendar +#: model_terms:ir.ui.view,arch_db:social_media_calendar.social_post_view_calendar +msgid "Social Posts" +msgstr "Publicaciones sociales" diff --git a/social_media_calendar/i18n/es_ES.po b/social_media_calendar/i18n/es_ES.po new file mode 100644 index 0000000000..21a030e473 --- /dev/null +++ b/social_media_calendar/i18n/es_ES.po @@ -0,0 +1,44 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * social_media_calendar +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2026-08-10 16:21+0000\n" +"PO-Revision-Date: 2026-08-10 16:21+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"Language: es_ES\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: social_media_calendar +#: model:ir.model.fields,field_description:social_media_calendar.field_social_post__color +msgid "Color" +msgstr "Color" + +#. module: social_media_calendar +#: model:ir.model.fields,field_description:social_media_calendar.field_social_post__date_calendar +msgid "Date Calendar" +msgstr "Fecha en el calendario" + +#. module: social_media_calendar +#. odoo-javascript +#: code:addons/social_media_calendar/static/src/js/views/calendar/social_calendar_controller.esm.js:0 +#, python-format +msgid "New Post" +msgstr "Nueva publicación" + +#. module: social_media_calendar +#: model:ir.model,name:social_media_calendar.model_social_post +msgid "Social Post" +msgstr "Publicación social" + +#. module: social_media_calendar +#: model_terms:ir.ui.view,arch_db:social_media_calendar.social_post_view_calendar +msgid "Social Posts" +msgstr "Publicaciones sociales" diff --git a/social_media_calendar/i18n/social_media_calendar.pot b/social_media_calendar/i18n/social_media_calendar.pot new file mode 100644 index 0000000000..eecd7cd10b --- /dev/null +++ b/social_media_calendar/i18n/social_media_calendar.pot @@ -0,0 +1,43 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * social_media_calendar +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2026-08-10 16:21+0000\n" +"PO-Revision-Date: 2026-08-10 16:21+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: social_media_calendar +#: model:ir.model.fields,field_description:social_media_calendar.field_social_post__color +msgid "Color" +msgstr "" + +#. module: social_media_calendar +#: model:ir.model.fields,field_description:social_media_calendar.field_social_post__date_calendar +msgid "Date Calendar" +msgstr "" + +#. module: social_media_calendar +#. odoo-javascript +#: code:addons/social_media_calendar/static/src/js/views/calendar/social_calendar_controller.esm.js:0 +#, python-format +msgid "New Post" +msgstr "" + +#. module: social_media_calendar +#: model:ir.model,name:social_media_calendar.model_social_post +msgid "Social Post" +msgstr "" + +#. module: social_media_calendar +#: model_terms:ir.ui.view,arch_db:social_media_calendar.social_post_view_calendar +msgid "Social Posts" +msgstr "" diff --git a/social_media_calendar/models/__init__.py b/social_media_calendar/models/__init__.py new file mode 100644 index 0000000000..bdd84592e3 --- /dev/null +++ b/social_media_calendar/models/__init__.py @@ -0,0 +1,4 @@ +# Copyright 2025 Binhex +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from . import social_post diff --git a/social_media_calendar/models/social_post.py b/social_media_calendar/models/social_post.py new file mode 100644 index 0000000000..796c58eceb --- /dev/null +++ b/social_media_calendar/models/social_post.py @@ -0,0 +1,66 @@ +# Copyright 2025 Binhex +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import api, fields, models + +CALENDAR_COLOR_BY_STATE = { + "planned": 2, + "publishing": 6, + "partially_published": 3, + "published": 10, + "cancelled": 0, +} +CALENDAR_COLOR_DEFAULT = 4 + + +class SocialPost(models.Model): + """Places the posts on a calendar view by their relevant date.""" + + _inherit = "social.post" + + date_calendar = fields.Datetime(compute="_compute_date_calendar", store=True) + color = fields.Integer(compute="_compute_color") + + @api.model + def default_get(self, fields_list): + """Propose the day clicked on the calendar as the schedule date. + + The calendar places the events by ``date_calendar``, which is only a + read-only summary of the dates of the post, so the date the user + clicked cannot be written there: it is turned into the date the post + is really sent, and the post is switched to ``schedule`` because that + is the only mode where a date can be chosen. + + A day already past is dropped on purpose: *Social Media Base* refuses + a schedule date behind the clock with a validation error, so keeping + it would make the form unsavable. Leaving ``send_post_date`` out of + the defaults lets ``_compute_send_post_date`` propose its usual one + hour from now. + """ + res = super().default_get(fields_list) + date_calendar = fields.Datetime.to_datetime( + self.env.context.get("default_date_calendar") + ) + if date_calendar: + res["send_post"] = "schedule" + if date_calendar > fields.Datetime.now(): + res["send_post_date"] = date_calendar + return res + + @api.depends("state") + def _compute_color(self): + """Map the post state to its calendar color.""" + for post in self: + post.color = CALENDAR_COLOR_BY_STATE.get(post.state, CALENDAR_COLOR_DEFAULT) + + @api.depends("create_date", "send_post_date", "published_date") + def _compute_date_calendar(self): + """Show the post on the date it was published, or the planned one. + + It is a Datetime so the calendar renders it in the time zone of the + user, as the rest of the dates of the post. + """ + for post in self: + post.date_calendar = ( + post.published_date or post.send_post_date or post.create_date + ) diff --git a/social_media_calendar/pyproject.toml b/social_media_calendar/pyproject.toml new file mode 100644 index 0000000000..4231d0cccb --- /dev/null +++ b/social_media_calendar/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/social_media_calendar/readme/CONTRIBUTORS.md b/social_media_calendar/readme/CONTRIBUTORS.md new file mode 100644 index 0000000000..4c895949c9 --- /dev/null +++ b/social_media_calendar/readme/CONTRIBUTORS.md @@ -0,0 +1,2 @@ +- [Binhex](https://www.binhex.cloud): + - Edilio Escalona Almira diff --git a/social_media_calendar/readme/DESCRIPTION.md b/social_media_calendar/readme/DESCRIPTION.md new file mode 100644 index 0000000000..d12317af5c --- /dev/null +++ b/social_media_calendar/readme/DESCRIPTION.md @@ -0,0 +1,8 @@ +This module provides the functionality to display posts in the calendar view. + +It is installed automatically together with *Social Media Base*, because the +calendar is meant to be part of the standard experience of the posts: it adds +the calendar view to the Posts menu without any configuration. Uninstalling it +is permanent as long as *Social Media Base* is not installed again: neither an +update of the apps list nor an upgrade of *Social Media Base* brings the +calendar back, but installing *Social Media Base* again does. diff --git a/social_media_calendar/readme/ROADMAP.md b/social_media_calendar/readme/ROADMAP.md new file mode 100644 index 0000000000..d53593bc33 --- /dev/null +++ b/social_media_calendar/readme/ROADMAP.md @@ -0,0 +1,6 @@ +This module, like the rest of the *Social Media* family, is not compatible +with the `social` module of Odoo Enterprise: both define the same model names +(`social.post`, `social.account`, `social.media`). The incompatibility is +declared in the manifest of *Social Media Base* with `"excludes": ["social"]`, +so Odoo refuses the installation with an error if the `social` module of +Enterprise is already installed in the database, and the other way round. diff --git a/social_media_calendar/readme/USAGE.md b/social_media_calendar/readme/USAGE.md new file mode 100644 index 0000000000..e2c589aa53 --- /dev/null +++ b/social_media_calendar/readme/USAGE.md @@ -0,0 +1,69 @@ +Calendar view +------------------- + +1. Go to Social Media > Posts +2. Click on the Calendar icon to open the calendar view +3. Click on a Post and a popover with its details opens. Its *Edit* button + opens the post form in a dialog, where it can be modified and saved. + +Each post is placed on the date it was published; if it has not been published +yet, on its schedule date; and if it is not scheduled, on its creation date. +That is why a scheduled post moves to another day of the calendar once it is +really published. + +Posts cannot be rescheduled by dragging them across the calendar: the date +shown is computed from the dates of the post. To change it, open the post and +modify its schedule date. + +Deletion is the only action disabled in this view: posts are deleted from the +list or the form. + +Colors by publication status +--------------------------- + +The calendar view will show the posts in different colors depending on their publication status: + +- Draft + + ![POST_DRAFT](../static/img/readme/POST_DRAFT.png) +- Planned + + ![POST_PLANNED](../static/img/readme/POST_PLANNED.png) +- Publishing + + ![POST_PUBLISHING](../static/img/readme/POST_PUBLISHING.png) +- Partially Published, the post that reached some of its accounts and failed + on the others +- Published + + ![POST_PUBLISHED](../static/img/readme/POST_PUBLISHED.png) +- Cancelled + + ![POST_CANCELLED](../static/img/readme/POST_CANCELLED.png) + +- Calendar view by color + + ![POSTS_CALENDAR](../static/img/readme/POSTS_CALENDAR.png) + +Creating a post from the calendar +--------------------------------- + +Users allowed to create posts can click on an empty day: the post form opens +with *Schedule* selected and 07:00 of that day, in the time zone of the user, +proposed as the schedule date. The accounts and the message are still +required, so the form is the full one, not a quick creation. Once saved, the +post is planned and the scheduler publishes it on that date. + +Whenever that proposal is already behind the clock, a date one hour from now +is proposed instead: this happens on a day already past, and also on the +current day once 07:00 has gone by. A schedule date behind the clock cannot be +saved at all: *Social Media Base* refuses it with a validation error. + +Uninstalling the module +----------------------- + +The calendar view is attached to the posts action of *Social Media Base* +through records that belong to *Social Media Calendar*, so Odoo removes them +on uninstall on its own and the Posts menu keeps working with its kanban, +list and form views. The module stays uninstalled afterwards; only installing +*Social Media Base* again brings it back, because it is auto-installed. diff --git a/social_media_calendar/static/description/icon.png b/social_media_calendar/static/description/icon.png new file mode 100644 index 0000000000..847815a5bb Binary files /dev/null and b/social_media_calendar/static/description/icon.png differ diff --git a/social_media_calendar/static/description/icon.svg b/social_media_calendar/static/description/icon.svg new file mode 100644 index 0000000000..02e2e3462c --- /dev/null +++ b/social_media_calendar/static/description/icon.svg @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/social_media_calendar/static/description/index.html b/social_media_calendar/static/description/index.html new file mode 100644 index 0000000000..576e3cc309 --- /dev/null +++ b/social_media_calendar/static/description/index.html @@ -0,0 +1,527 @@ + + + + + +Social Media Calendar + + + +
+

Social Media Calendar

+ + +

Beta License: AGPL-3 OCA/social Translate me on Weblate Try me on Runboat

+

This module provides the functionality to display posts in the calendar +view.

+

It is installed automatically together with Social Media Base, because +the calendar is meant to be part of the standard experience of the +posts: it adds the calendar view to the Posts menu without any +configuration. Uninstalling it is permanent as long as Social Media +Base is not installed again: neither an update of the apps list nor an +upgrade of Social Media Base brings the calendar back, but installing +Social Media Base again does.

+

Table of contents

+ +
+

Usage

+
+

Calendar view

+
    +
  1. Go to Social Media > Posts
  2. +
  3. Click on the Calendar icon to open the calendar view
  4. +
  5. Click on a Post and a popover with its details opens. Its Edit +button opens the post form in a dialog, where it can be modified and +saved.
  6. +
+

Each post is placed on the date it was published; if it has not been +published yet, on its schedule date; and if it is not scheduled, on its +creation date. That is why a scheduled post moves to another day of the +calendar once it is really published.

+

Posts cannot be rescheduled by dragging them across the calendar: the +date shown is computed from the dates of the post. To change it, open +the post and modify its schedule date.

+

Deletion is the only action disabled in this view: posts are deleted +from the list or the form.

+
+
+

Colors by publication status

+

The calendar view will show the posts in different colors depending on +their publication status:

+
    +
  • Draft

    +

    POST_DRAFT

    +
  • +
  • Planned

    +

    POST_PLANNED

    +
  • +
  • Publishing

    +

    POST_PUBLISHING

    +
  • +
  • Partially Published, the post that reached some of its accounts and +failed on the others

    +
  • +
  • Published

    +

    POST_PUBLISHED

    +
  • +
  • Cancelled

    +

    POST_CANCELLED

    +
  • +
  • Calendar view by color

    +

    POSTS_CALENDAR

    +
  • +
+
+
+

Creating a post from the calendar

+

Users allowed to create posts can click on an empty day: the post form +opens with Schedule selected and 07:00 of that day, in the time zone +of the user, proposed as the schedule date. The accounts and the message +are still required, so the form is the full one, not a quick creation. +Once saved, the post is planned and the scheduler publishes it on that +date.

+

Whenever that proposal is already behind the clock, a date one hour from +now is proposed instead: this happens on a day already past, and also on +the current day once 07:00 has gone by. A schedule date behind the clock +cannot be saved at all: Social Media Base refuses it with a validation +error.

+
+
+

Uninstalling the module

+

The calendar view is attached to the posts action of Social Media Base +through records that belong to Social Media Calendar, so Odoo removes +them on uninstall on its own and the Posts menu keeps working with its +kanban, list and form views. The module stays uninstalled afterwards; +only installing Social Media Base again brings it back, because it is +auto-installed.

+
+
+
+

Known issues / Roadmap

+

This module, like the rest of the Social Media family, is not +compatible with the social module of Odoo Enterprise: both define +the same model names (social.post, social.account, +social.media). The incompatibility is declared in the manifest of +Social Media Base with "excludes": ["social"], so Odoo refuses the +installation with an error if the social module of Enterprise is +already installed in the database, and the other way round.

+
+
+

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 to smash it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • BinhexTeam
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+ +Odoo Community Association + +

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.

+

Current maintainer:

+

edescalona

+

This module is part of the OCA/social project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/social_media_calendar/static/img/readme/POSTS_CALENDAR.png b/social_media_calendar/static/img/readme/POSTS_CALENDAR.png new file mode 100644 index 0000000000..8825a8a862 Binary files /dev/null and b/social_media_calendar/static/img/readme/POSTS_CALENDAR.png differ diff --git a/social_media_calendar/static/img/readme/POST_CANCELLED.png b/social_media_calendar/static/img/readme/POST_CANCELLED.png new file mode 100644 index 0000000000..dc2b53fcf2 Binary files /dev/null and b/social_media_calendar/static/img/readme/POST_CANCELLED.png differ diff --git a/social_media_calendar/static/img/readme/POST_DRAFT.png b/social_media_calendar/static/img/readme/POST_DRAFT.png new file mode 100644 index 0000000000..cb6894e73c Binary files /dev/null and b/social_media_calendar/static/img/readme/POST_DRAFT.png differ diff --git a/social_media_calendar/static/img/readme/POST_PLANNED.png b/social_media_calendar/static/img/readme/POST_PLANNED.png new file mode 100644 index 0000000000..574631b06d Binary files /dev/null and b/social_media_calendar/static/img/readme/POST_PLANNED.png differ diff --git a/social_media_calendar/static/img/readme/POST_PUBLISHED.png b/social_media_calendar/static/img/readme/POST_PUBLISHED.png new file mode 100644 index 0000000000..5fbaa66e7e Binary files /dev/null and b/social_media_calendar/static/img/readme/POST_PUBLISHED.png differ diff --git a/social_media_calendar/static/img/readme/POST_PUBLISHING.png b/social_media_calendar/static/img/readme/POST_PUBLISHING.png new file mode 100644 index 0000000000..f47fb619d0 Binary files /dev/null and b/social_media_calendar/static/img/readme/POST_PUBLISHING.png differ diff --git a/social_media_calendar/static/src/js/views/calendar/social_calendar_controller.esm.js b/social_media_calendar/static/src/js/views/calendar/social_calendar_controller.esm.js new file mode 100644 index 0000000000..55e7902575 --- /dev/null +++ b/social_media_calendar/static/src/js/views/calendar/social_calendar_controller.esm.js @@ -0,0 +1,11 @@ +/** @odoo-module **/ + +import {CalendarController} from "@web/views/calendar/calendar_controller"; +import {_t} from "@web/core/l10n/translation"; + +export class SocialCalendarController extends CalendarController { + /** @override */ + get editRecordDefaultDisplayText() { + return _t("New Post"); + } +} diff --git a/social_media_calendar/static/src/js/views/calendar/social_calendar_view.esm.js b/social_media_calendar/static/src/js/views/calendar/social_calendar_view.esm.js new file mode 100644 index 0000000000..48ade7207c --- /dev/null +++ b/social_media_calendar/static/src/js/views/calendar/social_calendar_view.esm.js @@ -0,0 +1,12 @@ +/** @odoo-module **/ + +import {SocialCalendarController} from "./social_calendar_controller.esm"; +import {calendarView} from "@web/views/calendar/calendar_view"; +import {registry} from "@web/core/registry"; + +export const SocialCalendarView = { + ...calendarView, + Controller: SocialCalendarController, +}; + +registry.category("views").add("social_calendar", SocialCalendarView); diff --git a/social_media_calendar/tests/__init__.py b/social_media_calendar/tests/__init__.py new file mode 100644 index 0000000000..48c04fe2cc --- /dev/null +++ b/social_media_calendar/tests/__init__.py @@ -0,0 +1,4 @@ +# Copyright 2025 Binhex +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from . import test_social_calendar diff --git a/social_media_calendar/tests/test_social_calendar.py b/social_media_calendar/tests/test_social_calendar.py new file mode 100644 index 0000000000..759b4b2c1e --- /dev/null +++ b/social_media_calendar/tests/test_social_calendar.py @@ -0,0 +1,134 @@ +# Copyright 2025 Binhex +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from datetime import timedelta + +from lxml import etree + +from odoo import fields +from odoo.fields import Command + +from odoo.addons.social_media_base.tests.test_social_common import ( + TestSocialMediaBaseCommon, +) + + +class TestSocialMediaCalendar(TestSocialMediaBaseCommon): + def test_compute_color(self): + mapping = { + "planned": 2, + "publishing": 6, + "partially_published": 3, + "published": 10, + "cancelled": 0, + "draft": 4, + } + + for state, expected_color in mapping.items(): + post = self.SocialPost.create({"message": f"Post {state}", "state": state}) + self.assertEqual( + post.color, + expected_color, + f"State {state!r} should map to color {expected_color}", + ) + + def test_compute_date_calendar_fallbacks(self): + post = self.SocialPost.create({"message": "Calendar post"}) + self.assertEqual(post.date_calendar, post.create_date) + post.write({"send_post": "schedule"}) + self.assertEqual(post.date_calendar, post.send_post_date) + post.write({"published_date": "2030-06-20 10:00:00"}) + self.assertEqual(str(post.date_calendar), "2030-06-20 10:00:00") + + def test_date_calendar_keeps_the_time_of_the_user(self): + self.env.user.tz = "Europe/Madrid" + post = self.SocialPost.create({"message": "Late post"}) + post.write({"published_date": "2026-07-20 22:30:00"}) + self.assertEqual( + fields.Datetime.context_timestamp(post, post.date_calendar).strftime( + "%Y-%m-%d" + ), + "2026-07-21", + msg="The calendar must place the post on the day the user sees, " + "not on the UTC one.", + ) + + def test_default_get_proposes_the_day_clicked_on_the_calendar(self): + clicked_date = fields.Datetime.now() + timedelta(days=3) + defaults = self.SocialPost.with_context( + default_date_calendar=clicked_date + ).default_get(["send_post", "send_post_date"]) + self.assertEqual(defaults["send_post"], "schedule") + self.assertEqual(defaults["send_post_date"], clicked_date) + + def test_a_post_created_from_the_calendar_is_planned_on_that_day(self): + clicked_date = fields.Datetime.now() + timedelta(days=3) + SocialPost = self.SocialPost.with_context(default_date_calendar=clicked_date) + post = SocialPost.create( + { + "message": "Post from the calendar", + "account_ids": [Command.set([self.social_account_id.id])], + **SocialPost.default_get(["send_post", "send_post_date"]), + } + ) + self.assertEqual(post.state, "planned") + self.assertEqual(post.date_calendar, clicked_date) + + def test_default_get_ignores_a_day_already_past(self): + """A past date would be published by the cron on its next run.""" + clicked_date = fields.Datetime.now() - timedelta(days=1) + SocialPost = self.SocialPost.with_context(default_date_calendar=clicked_date) + defaults = SocialPost.default_get(["send_post", "send_post_date"]) + self.assertEqual(defaults["send_post"], "schedule") + self.assertNotIn("send_post_date", defaults) + post = SocialPost.create( + { + "message": "Post from a past day", + "account_ids": [Command.set([self.social_account_id.id])], + **defaults, + } + ) + self.assertGreater(post.send_post_date, fields.Datetime.now()) + + def test_default_get_without_a_click_on_the_calendar(self): + defaults = self.SocialPost.default_get(["send_post", "send_post_date"]) + self.assertEqual(defaults["send_post"], "now") + self.assertNotIn("send_post_date", defaults) + + def test_calendar_view_lets_the_user_create_a_post(self): + """Creating opens the form: the post needs its accounts and message.""" + view = self.env.ref("social_media_calendar.social_post_view_calendar") + arch = etree.fromstring(view.arch) + self.assertEqual(arch.get("create"), "1") + self.assertEqual(arch.get("quick_create"), "0") + + def test_calendar_popover_renders_the_attachments_of_the_post(self): + """A bare x2many renders nothing on the popover: it needs a widget.""" + view = self.env.ref("social_media_calendar.social_post_view_calendar") + arch = etree.fromstring(view.arch) + for field_name in ("image_ids", "video_ids"): + node = arch.find(f".//field[@name='{field_name}']") + self.assertIsNotNone(node, f"{field_name} is missing from the calendar") + self.assertEqual(node.get("widget"), "many2many_tags") + + def test_post_action_includes_calendar_view(self): + action = self.env.ref("social_media_base.social_post_action") + self.assertEqual( + [mode for __, mode in action.views], + ["kanban", "calendar", "tree", "form"], + msg="The calendar must be offered right after the kanban, which " + "stays the default view of the action.", + ) + + def test_post_action_keeps_the_calendar_on_an_update_of_the_base(self): + """The calendar must not depend on the ``view_mode`` of the action. + + That field belongs to ``social_media_base`` and is rewritten every + time that module is updated on its own. + """ + action = self.env.ref("social_media_base.social_post_action") + action.view_mode = "kanban,tree,form" + self.assertIn( + "calendar", + [mode for __, mode in action.views], + ) diff --git a/social_media_calendar/views/social_post_views.xml b/social_media_calendar/views/social_post_views.xml new file mode 100644 index 0000000000..aac7ebee1e --- /dev/null +++ b/social_media_calendar/views/social_post_views.xml @@ -0,0 +1,65 @@ + + + + + social.post.view.calendar + social.post + + + + + + + + + + + + + + 1 + kanban + + + + + + 2 + calendar + + + + + diff --git a/test-requirements.txt b/test-requirements.txt index 4ad8e0ecea..21aba6d498 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1 +1,2 @@ odoo-test-helper +odoo-addon-social_media_base @ git+https://github.com/OCA/social.git@refs/pull/1889/head#subdirectory=social_media_base