TIP: You can type at any time to perform a new search.
Social Media Base
social_media_base · OCA/social
🛠 Migration considerations
social_account_view_tree— View 'social_account_view_tree' is defined with a `<tree>` root tag: renamed to `<list>` in Odoo 18.0. migration-view-tree-tag · sourcesocial_post_account_view_tree— View 'social_post_account_view_tree' is defined with a `<tree>` root tag: renamed to `<list>` in Odoo 18.0. migration-view-tree-tag · sourcesocial_post_view_tree— View 'social_post_view_tree' is defined with a `<tree>` root tag: renamed to `<list>` in Odoo 18.0. migration-view-tree-tag · source
Found by automated static analysis: patterns worth a look before/after upgrading this module to a newer Odoo version.
- Repository
- OCA/social · module folder · Try on Runboat
- Module version
- 1.0.1
- Category
- Social Network
- Folder size
- 1.03 MB
- License
- AGPL-3
- Application
- No
- Auto-installable
- No
- Website
- https://github.com/OCA/social
- Last tracking update
- 2026-09-11 23:03:47
- Authors
- Odoo Community Association (OCA), Binhex
- Maintainers
- Odoo Community Association (OCA), Binhex
- Committers
- OCA-git-bot, oca-ci, Edilio Escalona Almira
- Odoo dependencies
- Python dependencies
- None
- System dependencies
- None
- Required by
- None
- Description
This module provides the fundamental foundation for social media management. It facilitates the integration of user accounts, posts and native graph and pivot analysis over a daily time series. Designed to be flexible and scalable, it allows developers and businesses to integrate and customize social features according to their needs. What it asks of a social media is always a fixed number of calls per account: publishing, deleting, and the daily series of the page. Reading back what an account already published — the publications themselves and their comments — costs one call per page, so it grows with the history of the account and lives in *Social Media Sync* instead. The figures of a publication are read back here, but only for the ones published in the last 30 days: what that window costs is decided by the days it looks back and not by the history of the account. Without *Social Media Sync* the dashboard still works: the figures of a card are added up from what is already stored, and the *Statistics* dialog of a publication shows both the clicks Odoo counted on its own tracked link and the figures the social media reported for it, read back over the last 30 days. The list, the search filters and the form of a publication draw those figures only with *Social Media Sync* installed, because they span the whole history of the account. This module does not connect to any social media by itself: it brings the models, the security, the scheduled actions and the common interface. To use it, a connector module has to be installed as well (for instance *Social Media Linkedin* or *Social Media X*), which is what registers the social media, implements the OAuth authorization and publishes for real. Main features: - Integration of multiple user accounts. - Basic methods that can be extended and adapted to suit the social media. - Basic business structure. - Dashboard of published posts with video and deletion indicators. - A *Campaigns* menu inside the application, so the Odoo marketing campaigns (`utm.campaign`) can be managed without installing another marketing application. A post carries the campaign it belongs to, and the campaign form opens a new post already attached to it. - Campaign badge on the posts kanban and on the dashboard cards. - The posts of a marketing campaign, shown on the campaign from the moment they are drafted, with their consolidated figures and a stat button on the campaign form, the same way *Email Marketing* and *SMS Marketing* plug into a campaign. - A UTM medium per social media and a UTM source per publication, so the links of a post are tracked separately for each account it is published on. - Links of a published message routed through the Odoo link tracker, so a click from the social media is counted in Odoo and attributed to the campaign and to the publication. - Because of the above the module depends on `utm` and on `link_tracker`, which are installed with it. They are core modules that bring the marketing campaigns, the mediums, the sources and the short links, and they change nothing in the social media data. - Statistics stored as one row per account and day, so a native graph and a pivot draw them with the search panel, the comparison and the export of Odoo. Only the social media reporting figures by day fill it. A cron rewrites the last days every two hours, because the social media revise figures already past, and the *Update* button of the dashboard rewrites the same window on demand. - Figures of the dashboard card added up from what is already stored, so opening the dashboard costs no call at all, however many publications the account has. - Account credentials (OAuth tokens) are only visible to administrator users. - What each social media refuses to publish, shown on the post while it is written instead of when it is sent. Every connector declares its own rules once — how long the message may be, how many images and videos, which formats and which sizes — and the post form shows two blocks: what a social media will publish differently from what is written, and what it will not publish at all. Saving is never blocked, because a post is written before it is finished; the publication is where an objection stops something, and it reads the very same rules, so the form and the publication can never disagree. A publication refused this way fails on its own line: the other accounts of the post go out as usual.
Code Analysis ⓘ
Views touched (20)
| XML ID | Name | Model | Type | Status |
|---|---|---|---|---|
social_account_statistics_view_graph |
social.account.statistics.graph | social.account.statistics | graph | New |
social_account_statistics_view_pivot |
social.account.statistics.pivot | social.account.statistics | pivot | New |
social_account_statistics_view_search |
social.account.statistics.search | social.account.statistics | search | New |
social_account_view_form |
social.account.form | social.account | form | New |
social_account_view_search |
social.account.view.search | social.account | search | New |
social_account_view_tree |
social.account.tree | social.account | tree | New |
social_media_post_preview |
social_media_post_preview | ir.ui.view | qweb | New |
social_media_view_kanban |
social.media.kanban | social.media | kanban | New |
social_post_account_view_form |
social.post.account.form | social.post.account | form | New |
social_post_account_view_form_statistics |
social.post.account.form.statistics | social.post.account | form | New |
social_post_account_view_kanban |
social.post.account.kanban | social.post.account | kanban | New |
social_post_account_view_search |
social.post.account.search | social.post.account | search | New |
social_post_account_view_tree |
social.post.account.tree | social.post.account | tree | New |
social_post_view_form |
social.post.form | social.post | form | New |
social_post_view_kanban |
social.post.kanban | social.post | kanban | New |
social_post_view_search |
social.post.view.search | social.post | search | New |
social_post_view_tree |
social.post.tree | social.post | tree | New |
utm_campaign_view_form |
utm.campaign.view.form.inherit.social.media.base | utm.campaign | form | Inherits utm.utm_campaign_view_form |
utm_campaign_view_kanban |
utm.campaign.view.kanban.inherit.social.media.base | utm.campaign | kanban | Inherits utm.utm_campaign_view_kanban |
wizard_social_account_view_form |
Associate Social Account | wizard.social.account | form | New |
HTTP endpoints (0)
No HTTP endpoints found for this module.
Models touched (15)
New fields (0)
No new fields.
Public methods (1)-
session_info(self)Deliver once the notification kept by the social media callbacks. See ``social.media.base.mixin._notify_user_session``.
New fields (1)
-
social_post_account_idMany2one → social.post.accounthelp='Publication the tracked link was published in.'index='btree_not_null'ondelete='set null'string='Social Media Publication' args: 'social.post.account'
No public methods.
New fields (1)
-
social_post_account_idMany2one → social.post.accounthelp='Publication the clicked link was published in.'index='btree_not_null'ondelete='set null'related='link_id.social_post_account_id'store=Truestring='Social Media Publication' args: 'social.post.account'
No public methods.
New fields (20)
-
access_tokenChargroups='base.group_system' -
activeBooleandefault=True -
can_manage_accountBooleancompute='_compute_can_manage_account'help='Whether the current user may update or archive this account: its responsible user and the social media administrators.' -
company_idMany2one → res.companydefault=<expr> args: 'res.company' -
engagementFloatcompute='_compute_engagement'default=0digits=(16, 4)help='Interactions of the account over its impressions, as a ratio.'store=True -
expire_access_token_dateDatestring='Expire Access Token' -
image_1920Imagedefault=_default_image -
last_update_accountDatetime -
media_idMany2one → social.mediaondelete='restrict' args: 'social.media' -
media_typeSelectionrelated='media_id.media_type' -
nameChar -
need_updateBooleandefault=Falsehelp='The credentials of the account expired and it has to be authorized again. It means that and nothing else; an account with publications left to import is marked with posts_need_import, in the synchronization module.' -
post_account_idsOne2many → social.post.accountargs: 'social.post.account', 'account_id' -
post_countIntegercompute='_compute_post_count' -
post_idsMany2many → social.postcolumn1='social_account_id'column2='social_post_id'help='Posts that target this account. Inverse of the accounts of a post, it is what keeps the counter up to date.'readonly=Truerelation='social_account_social_post_rel'string='Posts' args: 'social.post' -
refresh_access_tokenChargroups='base.group_system' -
remote_refCharcopy=Falsehelp='Identifier of this account on the social media. It is set by the connector module of each social media.'index=Truestring='Remote Reference' -
user_idMany2one → res.usersdefault=<expr>help='User this account belongs to. Only the responsible user and the social media administrators can see it.'index=Truerequired=Truestring='Responsible'tracking=True args: 'res.users' -
usernameChar -
utm_campaign_countIntegercompute='_compute_utm_campaign_count'
-
action_open_posts(self)Open the posts this account is one of the targets of. -
action_open_utm_campaigns(self)Open the marketing campaigns of the publications of this account. -
action_purge_account(self)Delete the accounts and their publication history from Odoo only. The records of the other applications that reference an account lose the link instead of being deleted. :return: the accounts list action, the current record no longer exists. :rtype: dict -
action_refresh_statistics(self)Ask the social media again for the figures of the last days. The graph view reads what the crons left, so this is what a user presses when he does not want to wait for the next pass. A social media that reports no figures by day answers nothing, and saying so is more useful than announcing an update that did not happen. The figures of the recent publications are read back in the same press, over the same window the daily cron reads: one set to explain and one ceiling of cost, wherever the user presses. The notification keeps speaking of the daily series, which is the only part of this a social media can be unable to answer. -
action_update_account(self) -
action_validate_access_token(self)Check the token against the social media, from the account form. The user asking whether the token works expects a real answer: the stored dates cannot tell a token that was revoked on the social media side. ``check_remote_token`` is what connectors use to tell this deliberate check from the guard that runs before every call. -
compute_dashboard_statistics(self)Recompute the figures the dashboard shows, without asking anybody. What the client calls when the kanban loads. It is pure aggregation over rows that are already stored, so opening the dashboard costs no call at all and can happen as often as the user wants. :return: whether anything was recomputed. :rtype: bool -
refresh_dashboard_statistics(self)Ask the social media for the figures again, from the dashboard. What the client calls when somebody presses *Update*. Unlike :meth:`compute_dashboard_statistics` this one does spend calls — one per account, against the endpoint that fills the daily series — and that is the point: the user is saying he does not want to wait for the two-hour cron. It is the same thing :meth:`action_refresh_statistics` already does from the account form, over every account instead of one. The figures of the recent publications are read back too, over the same window the daily cron reads: the button asks for the same set the cron does, so there is a single ceiling of cost and a single thing to explain. Refreshing what the social media reports first and aggregating afterwards is not optional: the figures of the card come from those rows and from the publications, so the other order would recompute it from what was already on screen. There is no throttle. Opening the dashboard costs no call at all, so the only calls there are to spare are the ones the user asked for by pressing the button, and a button that answers with the same figures looks broken — which is exactly what it is there to fix. :return: whether anything was refreshed. :rtype: bool -
validate_access_token(self)Hook for the connector modules to refresh an expired token. Called before every operation on the social media, so connectors keep it cheap and answer from the stored expiry dates. -
write(self, vals)
New fields (12)
-
account_idMany2one → social.accountindex=Trueondelete='cascade'required=True args: 'social.account' -
click_countIntegerdefault=0 -
comment_countIntegerdefault=0 -
company_idMany2oneindex=Truerelated='account_id.company_id'store=True -
dateDateindex=Truerequired=True -
engagementFloatdefault=0digits=(16, 4)group_operator='avg'help='Engagement rate as the social media reports it. It is a ratio, so periods are averaged instead of added up.' -
impression_countIntegerdefault=0 -
like_countIntegerdefault=0 -
media_idMany2oneindex=Truerelated='account_id.media_id'store=True -
media_typeSelectionrelated='account_id.media_id.media_type'store=True -
share_countIntegerdefault=0 -
user_idMany2oneindex=Truerelated='account_id.user_id'store=True
No public methods.
New fields (5)
-
descriptionText -
imageBinary -
media_typeSelectionreadonly=True args: [] -
nameChar -
utm_medium_idMany2one → utm.mediumhelp='Delivery method reported to the marketing campaigns for the links published on this social media. The connector module of each social media provides a default one.'ondelete='restrict'string='UTM Medium' args: 'utm.medium'
-
action_open_account(self)Open the wizard that associates an account of this social media. Pure hook: the base module knows no social media, so it returns nothing. Every connector module overrides it and returns the ``ir.actions.act_window`` of its own association wizard.
New fields (0)
No new fields.
Public methods (0)No public methods.
New fields (26)
-
account_idsMany2many → social.accountondelete='restrict'required=True args: 'social.account' -
activeBooleandefault=True -
any_failed_postBooleancompute='_compute_any_failed_post' -
campaign_idMany2one → utm.campaignindex='btree_not_null'ondelete='set null'string='Campaign' args: 'utm.campaign' -
content_lockedBooleancompute='_compute_content_locked'help='The post reached at least one social media, so what it says can no longer be changed.' -
count_post_clicksIntegercompute='_compute_post_statistics'default=0string='Clicks' -
count_post_commentsIntegercompute='_compute_post_statistics'default=0string='Comments' -
count_post_engagementFloatcompute='_compute_post_statistics'default=0string='Engagement' -
count_post_impressionIntegercompute='_compute_post_statistics'default=0string='Impression' -
count_post_interactionsIntegercompute='_compute_post_statistics'default=0string='Interactions' -
count_post_likesIntegercompute='_compute_post_statistics'default=0string='Likes' -
count_post_sharesIntegercompute='_compute_post_statistics'default=0string='Shares' -
hide_postBooleancompute='_compute_hide_post' -
image_idsMany2many → ir.attachmentcolumn1='post_id'column2='image_id'ondelete='restrict'relation='social_post_image_rel' args: 'ir.attachment' -
link_click_countIntegercompute='_compute_link_click_count'help="Clicks Odoo registered on the tracked links of the publications of this post. Different from 'Clicks', which is the figure the social media report."string='Tracked Clicks' -
messageTextrequired=Truetracking=True -
message_errorTextcompute='_compute_post_check_messages'help='What no social media of the post will be able to publish as the post stands. It does not block saving, so the post can be finished later, but the publication is refused until it is fixed.' -
message_infoTextcompute='_compute_post_check_messages'help='What a social media of the post is going to publish differently from what is written here. It does not stop anything: the post is published, changed by the social media.' -
post_account_idsOne2many → social.post.accountargs: 'social.post.account', 'post_id' -
post_previewHtmlcompute='_compute_post_preview'store=True -
published_dateDatetimetracking=True -
send_postSelectiondefault='now'required=Truetracking=True args: [('now', 'Now'), ('schedule', 'Schedule')] -
send_post_dateDatetimecompute='_compute_send_post_date'help="Date the post is sent to the social media. Switching to 'Schedule' proposes one hour from now, and it can be changed."readonly=Falsestore=Truestring='Schedule date' -
stateSelectiondefault='draft'tracking=True args: [('draft', 'Draft'), ('planned', 'Planned'), ('publishing', 'Publishing'), ('partially_published', 'Partially Published'), ('published', 'Published'), ('cancelled', 'Cancelled')] -
user_idMany2one → res.usersdefault=<expr>help='User this post belongs to. Only the responsible user and the social media administrators can see it.'index=Truerequired=Truestring='Responsible'tracking=True args: 'res.users' -
video_idsMany2many → ir.attachmentcolumn1='post_id'column2='video_id'ondelete='restrict'relation='social_post_video_rel' args: 'ir.attachment'
-
action_cancel(self) -
action_create_post_account(self)Publish the post on all its social media accounts. -
action_draft(self) -
create(self, vals_list)@api.model_create_multi -
default_get(self, fields_list)@api.model -
unlink(self)Delete the posts together with the publications they created. ``social.post.account.post_id`` is ``restrict`` on purpose, so that the history of what exists on the social media is never dropped by accident. The post is what knows whether that is the case: a publication that never reached the social media, or one already gone from it, is deleted with its post; a publication still online stops the deletion with an explanation. The medias go with it, and nothing here has to arrange that: :meth:`_anchor_media_attachments` attaches every media to the record that holds it, and ``unlink`` deletes the attachments of the records it deletes, so the images of the post leave with the post and what a publication downloaded leaves with the publication. The filestore frees the files on its next collection. -
write(self, vals)
New fields (26)
-
account_idMany2one → social.accountondelete='restrict'required=True args: 'social.account' -
account_remote_refCharrelated='account_id.remote_ref'string='Account Remote Reference' -
activeBooleandefault=True -
authorCharrelated='account_id.name'store=True -
campaign_idMany2one → utm.campaigncompute='_compute_campaign_id'help='Marketing campaign of the parent post. A publication imported from the social media has no parent post, so its campaign is written on the imported publication itself.'index='btree_not_null'ondelete='set null'readonly=Falsestore=Truestring='Campaign' args: 'utm.campaign' -
effective_dateDatetimecompute='_compute_effective_date'help='Publication date, or the date the post is scheduled for while it is not published yet.'store=Truestring='Date' -
engagementFloatdefault=0digits=(16, 4) -
failed_descriptionHtml -
has_videoBooleandefault=Falsehelp='Indicates that the published post has at least one video attached.' -
image_idsMany2many → ir.attachmentcolumn1='post_id'column2='image_id'ondelete='restrict'relation='social_post_account_image_rel' args: 'ir.attachment' -
is_scheduledBooleancompute='_compute_is_scheduled'help='The post of this publication is scheduled and not published yet.'store=Truestring='Scheduled' -
link_click_countIntegercompute='_compute_link_click_count'help="Clicks Odoo registered on the tracked links of this publication. Different from 'Clicks', which is the figure the social media reports for the publication itself."string='Tracked Clicks' -
media_idMany2one → social.mediarelated='account_id.media_id'required=True args: 'social.media' -
media_refsJsoncopy=Falsedefault=dicthelp='Reference each media of this publication has on the social media, keyed by the identifier of its attachment. A media without an entry here is one the social media does not know about.' -
media_typeSelectionrelated='media_id.media_type' -
medium_idMany2one → utm.mediumcompute='_compute_medium_id'help='Delivery method reported to the marketing campaign. It lives on the publication and not on the post because a post is spread over several social media.'index='btree_not_null'ondelete='set null'readonly=Falsestore=Truestring='Medium' args: 'utm.medium' -
messageTexthelp='Text this publication sends to the social media, which is not the text of its post: every publication carries its own UTM source, so each link is rewritten into a tracker of its own and the same post reads differently on every account. Publications imported from the social media have no post to read a message from either.'required=True -
post_account_urlChar -
post_idMany2one → social.postondelete='restrict' args: 'social.post' -
published_dateDatetime -
remote_refCharcopy=Falsehelp='Identifier of this publication on the social media. It is set by the connector module of each social media.'index=Truestring='Remote Reference' -
source_idMany2one → utm.sourcecopy=Falsehelp='Created when the publication is sent, so that every publication of the same post owns its own tracked links.'index='btree_not_null'ondelete='restrict'readonly=Truestring='Source' args: 'utm.source' -
stateSelectiondefault='ready'help="'Deleted' means the publication no longer exists on the social media although it is kept in Odoo for history." args: [('ready', 'Ready'), ('posted', 'Posted'), ('failed', 'Failed'), ('deleted', 'Deleted')] -
statistics_dateDatetimecopy=Falsehelp='When the figures of this publication were last read back from the social media. Empty means they were never read.'readonly=Truestring='Statistics Read On' -
user_idMany2oneindex=Truereadonly=Truerelated='account_id.user_id'store=Truestring='Responsible' -
video_idsMany2many → ir.attachmentcolumn1='post_id'column2='video_id'ondelete='restrict'relation='social_post_account_video_rel' args: 'ir.attachment'
-
action_delete_post_account(self) -
action_open_post_account_url(self)Ask the social media, then open this publication on it. The address is only known for a publication that made it to the social media, so the button showing it is hidden otherwise. The address alone is not proof that the publication is still online: it survives a deletion made on the social media until something asks. Asking costs one call, for the one publication the user is opening, which is what makes it worth making here. -
action_open_statistics(self)Open the figures of the publication in a dialog. Called from the menu of the card of the dashboard, which is where the figures brought by the synchronization were missing. Nothing is asked to the social media: what is shown is what the last update stored. :return: the action opening the statistics view. :rtype: dict -
check_post_exists(self)Ask the social media whether this publication is still online. Public entry point shared by the form button and by the dashboard, so both answer the same thing from the same code. :rtype: bool -
create(self, vals_list)@api.model_create_multi -
write(self, vals)
New fields (2)
-
image_urlsCharcompute='_compute_image_urls'store=True -
video_urlsCharcompute='_compute_video_urls'help='URL of each video of this record, in the order they were added.'store=True
No public methods.
New fields (6)
-
click_countIntegerdefault=0 -
comment_countIntegerdefault=0 -
impression_countIntegerdefault=0help='Total number of views, which may include multiple views by the same user.' -
interactions_countIntegercompute='_compute_interactions_count'default=0help='Interactions with the publication: clicks, likes, comments and shares.'store=True -
like_countIntegerdefault=0 -
share_countIntegerdefault=0
No public methods.
New fields (1)
-
web_urlCharcompute='_compute_web_url'help='Address of this record on the social media.'string='Web URL'
-
action_open_url(self)Open this record on the social media, in a new tab.
New fields (11)
-
social_click_countIntegercompute='_compute_social_statistics'groups=_SOCIAL_GROUPSstring='Social Media Clicks' -
social_comment_countIntegercompute='_compute_social_statistics'groups=_SOCIAL_GROUPSstring='Social Media Comments' -
social_engagementFloatcompute='_compute_social_statistics'groups=_SOCIAL_GROUPSstring='Social Media Engagement' -
social_impression_countIntegercompute='_compute_social_statistics'groups=_SOCIAL_GROUPSstring='Social Media Impressions' -
social_interactions_countIntegercompute='_compute_social_statistics'groups=_SOCIAL_GROUPSstring='Social Media Interactions' -
social_like_countIntegercompute='_compute_social_statistics'groups=_SOCIAL_GROUPSstring='Social Media Likes' -
social_link_click_countIntegercompute='_compute_social_link_click_count'groups=_SOCIAL_GROUPShelp='Clicks Odoo registered on the tracked links of the social media publications of this campaign. Part of the clicks of the campaign, not something to add to them.'string='Social Media Tracked Clicks' -
social_post_account_idsOne2many → social.post.accountgroups=_SOCIAL_GROUPSreadonly=Truestring='Social Media Publications' args: 'social.post.account', 'campaign_id' -
social_post_countIntegercompute='_compute_social_post_count'groups=_SOCIAL_GROUPSstring='Social Media Posts Count' -
social_post_idsOne2many → social.postgroups=_SOCIAL_GROUPSreadonly=Truestring='Social Media Posts' args: 'social.post', 'campaign_id' -
social_share_countIntegercompute='_compute_social_statistics'groups=_SOCIAL_GROUPSstring='Social Media Shares'
-
action_view_social_posts(self)Open the social media posts linked to this campaign.
New fields (0)
No new fields.
Public methods (0)No public methods.
New fields (7)
-
account_idMany2one → social.accountargs: 'social.account' -
csrf_state_tokenCharhelp='Anti-CSRF state token used during the OAuth flow.'readonly=True -
imageBinaryrelated='media_id.image' -
media_idMany2one → social.mediarequired=True args: 'social.media' -
media_typeSelectionrelated='media_id.media_type'string='Media Type' -
update_keysBooleandefault=Falsehelp='Only enable this field if your credentials have changed' -
update_tokenBooleandefault=Falsehelp='Only enable this field if the access token has expired and has to be requested again'
-
action_associate_social_account(self)Link the account with the social media. -
action_update_account(self)Refresh the account data according to the wizard selection.
- Status
- Open migration PR — not merged yet for this version
- CI status
- checks failing
- Open since
- 282 days ago
- Last activity
- 113 days ago
- Repository
- OCA/social
- Pull request
- [18.0][MIG] social_media_base: Migration to 18.0 (#1769)