feat: add promotional image support to notifications and expose thumbnails via API

This commit is contained in:
Suherdy Yacob 2026-06-13 22:10:32 +07:00
parent 8737c8a661
commit e7685c38c8
4 changed files with 50 additions and 11 deletions

View File

@ -26,29 +26,41 @@ class AppNotificationController(http.Controller):
def fetch_notifications(self, **kw):
"""
Endpoint for the Flutter app Background Task and In-App notification center.
Returns image_128 (base64 thumbnail) for list display.
The Flutter detail screen loads the full image via /web/image/ with session cookie.
"""
import base64 as b64mod
user = request.env.user
partner = user.partner_id
last_id = kw.get('last_id', 0)
# Give them global notifications OR notifications tagged explicitly to them
domain = [
('id', '>', last_id),
'|',
'|',
('partner_ids', 'in', [partner.id]),
('is_global', '=', True)
]
notifications = request.env['mapan.app.notification'].sudo().search_read(
domain,
['id', 'title', 'body', 'create_date'],
['id', 'title', 'body', 'create_date', 'image_128'],
order='create_date desc',
limit=50
)
# Normalize image_128 bytes → base64 string for JSON transport
for notif in notifications:
img = notif.get('image_128')
if img and isinstance(img, bytes):
notif['image_128'] = b64mod.b64encode(img).decode('utf-8')
elif not img:
notif['image_128'] = None
notif['has_image'] = bool(notif['image_128'])
return {
'status': 'success',
'status': 'success',
'data': notifications
}

View File

@ -1,4 +1,4 @@
from odoo import models, fields
from odoo import models, fields, api
class AppNotification(models.Model):
_name = 'mapan.app.notification'
@ -7,20 +7,37 @@ class AppNotification(models.Model):
title = fields.Char(string='Notification Title', required=True)
body = fields.Text(string='Notification Body', required=True)
# Optional promotional image — Odoo auto-generates image_128 thumbnail
image = fields.Image(
string='Notification Image',
max_width=1920,
max_height=1920,
help='Optional image shown in the notification detail screen.'
)
image_128 = fields.Image(
string='Thumbnail',
related='image',
max_width=128,
max_height=128,
store=True,
readonly=True,
)
# If no partner_ids are selected, it is broadcast to everyone logically
partner_ids = fields.Many2many(
'res.partner',
'res.partner',
string='Selected Customers',
help='Leave empty to broadcast to all app users.'
)
is_global = fields.Boolean(
string='Broadcast to All?',
compute='_compute_is_global',
store=True
)
@api.depends('partner_ids')
def _compute_is_global(self):
for rec in self:
rec.is_global = not bool(rec.partner_ids)

View File

@ -7,6 +7,12 @@ class PushNotificationWizard(models.TransientModel):
title = fields.Char(string='Notification Title', required=True)
body = fields.Text(string='Notification Body', required=True)
image = fields.Image(
string='Notification Image (optional)',
max_width=1920,
max_height=1920,
help='Upload a promotional image to display in the notification detail.'
)
recipient_type = fields.Selection([
('all', 'All Activated Members'),
('specific', 'Specific Members')
@ -26,6 +32,7 @@ class PushNotificationWizard(models.TransientModel):
self.env['mapan.app.notification'].create({
'title': self.title,
'body': self.body,
'image': self.image,
})
target_msg = 'all activated app members'
else:
@ -33,6 +40,7 @@ class PushNotificationWizard(models.TransientModel):
self.env['mapan.app.notification'].create({
'title': self.title,
'body': self.body,
'image': self.image,
'partner_ids': [(6, 0, self.partner_ids.ids)],
})
target_msg = f'{len(self.partner_ids)} selected member(s)'

View File

@ -8,6 +8,8 @@
<group>
<field name="title"/>
<field name="body"/>
<field name="image" widget="image" options="{'size': [0, 200]}"
help="Optional promotional image (JPG/PNG). Displayed in the notification detail screen."/>
<field name="recipient_type" widget="radio" options="{'horizontal': true}"/>
</group>
<group string="Recipients" invisible="recipient_type != 'specific'">