Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions website_membership_group/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,22 @@
"website_membership",
],
"data": [
"data/website_snippet_filter_data.xml",
"views/membership_group_view.xml",
"templates/website.xml",
"views/snippets/snippet_options.xml",
"views/snippets/dynamic_templates.xml",
"views/snippets/members_snippet.xml",
],
"assets": {
"web.assets_frontend": [
"website_membership_group/static/src/scss/membership_group.scss",
"website_membership_group/static/src/js/membership_group_frontend.esm.js",
"website_membership_group/static/src/js/membership_snippet.esm.js",
],
"website.assets_wysiwyg": [
"website_membership_group/static/src/js/membership_group_options.esm.js",
"website_membership_group/static/src/js/membership_snippet_options.esm.js",
],
},
}
14 changes: 14 additions & 0 deletions website_membership_group/controllers/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,17 @@ def display_membership_group_page(self, membership_group):
vals = self._membership_group_page_render_vals(membership_group_sudo)

return request.render("website_membership_group.membership_group_page", vals)

@http.route("/membership/snippet/groups", type="json", auth="public", website=True)
def snippet_groups(self):
groups = (
request.env["membership.group"]
.sudo()
.search(
[
("is_published", "=", True),
],
order="name",
)
)
return [{"id": g.id, "name": g.name} for g in groups]
22 changes: 22 additions & 0 deletions website_membership_group/data/website_snippet_filter_data.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<!-- Filter for Dynamic Snippet -->
<record id="dynamic_snippet_membership_members_filter" model="ir.filters">
<field name="name">Membership Members</field>
<field name="model_id">res.partner</field>
<field name="user_id" eval="False"/>
<field name="domain">[('website_published', '=', True), ('membership_group_member_ids.state', '=', 'current')]</field>
<field name="sort">["name asc"]</field>
<field name="action_id" ref="website.action_website"/>
</record>

<!-- Dynamic Filter -->
<record id="dynamic_filter_membership_members" model="website.snippet.filter">
<field name="name">Membership Members</field>
<field name="filter_id" ref="website_membership_group.dynamic_snippet_membership_members_filter"/>
<field name="field_names">name,image_128,website_description</field>
<field name="limit" eval="16"/>
</record>
</data>
</odoo>
1 change: 1 addition & 0 deletions website_membership_group/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
from . import membership_group
from . import website_snippet_filter
71 changes: 71 additions & 0 deletions website_membership_group/models/website_snippet_filter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
from odoo import fields, models


class WebsiteSnippetFilter(models.Model):
_inherit = "website.snippet.filter"

def _filter_records_to_values(self, records, is_sample=False):
meta_data = self._get_filter_meta_data()
values = []
model = self.env[self.model_name]
Website = self.env["website"]
for record in records:
data = {}
for field_name, field_widget in meta_data.items():
field = model._fields.get(field_name)
if field and field.type in ("binary", "image"):
if is_sample:
raw = record[field_name]
if field_name in record and raw and raw is not True and raw is not False:
data[field_name] = raw.decode("utf8")
else:
data[field_name] = "/web/image"
else:
data[field_name] = Website.image_url(record, field_name)
elif field_widget == "monetary":
model_currency = None
if field and field.type == "monetary":
model_currency = record[field.get_currency_field(record)]
elif "currency_id" in model._fields:
model_currency = record["currency_id"]
if model_currency:
website_currency = self._get_website_currency()
data[field_name] = model_currency._convert(
record[field_name],
website_currency,
Website.get_current_website().company_id,
fields.Date.today(),
)
else:
data[field_name] = record[field_name]
else:
data[field_name] = record[field_name]
data["call_to_action_url"] = False
if not is_sample:
data["call_to_action_url"] = (
"website_url" in record and record["website_url"]
)
data["_record"] = record
values.append(data)
return values

def _render(self, template_key, limit, search_domain=None, with_sample=False, **custom_template_data):
if with_sample and search_domain:
records = self._prepare_values(limit=1, search_domain=search_domain)
if not records:
with_sample = False

fragments = super()._render(
template_key, limit,
search_domain=search_domain,
with_sample=with_sample,
**custom_template_data,
)
if not fragments:
return [
'<div class="text-center text-muted py-4 w-100">'
'<p class="mb-0">No members found.</p>'
'<small>Make sure members are published and have an active membership in the selected group.</small>'
'</div>'
]
return fragments
40 changes: 40 additions & 0 deletions website_membership_group/static/src/js/membership_snippet.esm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/** @odoo-module **/

import publicWidget from "@web/legacy/js/public/public_widget";
import DynamicSnippet from "@website/snippets/s_dynamic_snippet/000";

const DynamicSnippetMembers = DynamicSnippet.extend({
selector: ".s_dynamic_snippet_members",
disabledInEditableMode: false,

//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------

/**
* @override
* @private
*/
_getSearchDomain: function () {
const searchDomain = this._super.apply(this, arguments);
const filterByGroupId = parseInt(this.$el.get(0).dataset.filterByGroupId);
if (filterByGroupId >= 0) {
searchDomain.push(
["membership_group_member_ids.group_id", "=", filterByGroupId],
["membership_group_member_ids.state", "=", "current"]
);
}
return searchDomain;
},
/**
* @override
* @private
*/
_getMainPageUrl() {
return "/members";
},
});

publicWidget.registry.dynamic_snippet_members = DynamicSnippetMembers;

export default DynamicSnippetMembers;
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/** @odoo-module **/

import options from "@web_editor/js/editor/snippets.options";
import dynamicSnippetOptions from "@website/snippets/s_dynamic_snippet/options";
import { rpc } from "@web/core/network/rpc";

const dynamicSnippetMembersOptions = dynamicSnippetOptions.extend({
/**
* @override
*/
init: function () {
this._super.apply(this, arguments);
this.modelNameFilter = "res.partner";
this.groups = {};
},

//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------

/**
* @override
* @private
*/
_computeWidgetVisibility: function (widgetName, params) {
return this._super.apply(this, arguments);
},
/**
* Fetches published membership groups.
* @private
* @returns {Promise}
*/
_fetchGroups: function () {
return rpc("/membership/snippet/groups");
},
/**
* @override
* @private
*/
_renderCustomXML: async function (uiFragment) {
await this._super.apply(this, arguments);
await this._renderGroupSelector(uiFragment);
},
/**
* Renders the group option selector content into the provided uiFragment.
* @private
* @param {HTMLElement} uiFragment
*/
_renderGroupSelector: async function (uiFragment) {
if (!Object.keys(this.groups).length) {
const groupsList = await this._fetchGroups();
this.groups = {};
for (let index in groupsList) {
this.groups[groupsList[index].id] = groupsList[index];
}
}
const groupSelectorEl = uiFragment.querySelector('[data-name="group_opt"]');
return this._renderSelectUserValueWidgetButtons(groupSelectorEl, this.groups);
},
/**
* Sets default options values.
* @override
* @private
*/
_setOptionsDefaultValues: function () {
this._setOptionValue("filterByGroupId", -1);
this._super.apply(this, arguments);
},
});

options.registry.dynamic_snippet_members = dynamicSnippetMembersOptions;

export default dynamicSnippetMembersOptions;
1 change: 1 addition & 0 deletions website_membership_group/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import test_membership_snippet
103 changes: 103 additions & 0 deletions website_membership_group/tests/test_membership_snippet.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
from odoo.tests import HttpCase, tagged


@tagged("post_install", "-at_install")
class TestMembershipSnippet(HttpCase):
"""Tests for the membership members dynamic snippet."""

def test_snippet_filter_exists(self):
"""The dynamic snippet filter record exists."""
filter_record = self.env.ref(
"website_membership_group.dynamic_filter_membership_members",
raise_if_not_found=False,
)
self.assertTrue(filter_record)
self.assertEqual(filter_record.model_name, "res.partner")
self.assertEqual(filter_record.limit, 16)

def test_snippet_groups_endpoint(self):
"""The /membership/snippet/groups endpoint returns published groups."""
self.env["membership.group"].create(
{
"name": "Test Group",
"is_published": True,
}
)
result = self.url_open(
"/membership/snippet/groups",
data='{"jsonrpc": "2.0", "method": "call", "params": {}, "id": 1}',
headers={"Content-Type": "application/json"},
)
self.assertEqual(result.status_code, 200)
data = result.json()
self.assertIn("result", data)
group_names = [g["name"] for g in data["result"]]
self.assertIn("Test Group", group_names)

def test_dynamic_filter_renders_members(self):
"""The dynamic filter renders members for a published group."""
group = self.env["membership.group"].create(
{
"name": "Dynamic Test Group",
"is_published": True,
}
)
partner = self.env["res.partner"].create(
{
"name": "Dynamic Test Member",
"website_published": True,
}
)
self.env["membership.group.member"].create(
{
"group_id": group.id,
"partner_id": partner.id,
"type": "committee",
"state": "current",
}
)

filter_record = self.env.ref(
"website_membership_group.dynamic_filter_membership_members"
)
fragments = filter_record._render(
"website_membership_group.dynamic_filter_template_res_partner_grid",
limit=16,
search_domain=[("membership_group_member_ids.group_id", "=", group.id)],
)
self.assertTrue(fragments)
self.assertIn("Dynamic Test Member", fragments[0])

def test_dynamic_filter_hides_unpublished_members(self):
"""Unpublished partners are not rendered."""
group = self.env["membership.group"].create(
{
"name": "Hidden Member Group",
"is_published": True,
}
)
partner = self.env["res.partner"].create(
{
"name": "Hidden Member",
"website_published": False,
}
)
self.env["membership.group.member"].create(
{
"group_id": group.id,
"partner_id": partner.id,
"type": "committee",
"state": "current",
}
)

filter_record = self.env.ref(
"website_membership_group.dynamic_filter_membership_members"
)
fragments = filter_record._render(
"website_membership_group.dynamic_filter_template_res_partner_grid",
limit=16,
search_domain=[("membership_group_member_ids.group_id", "=", group.id)],
)
# Should be empty because partner is not website_published
self.assertFalse(any("Hidden Member" in f for f in fragments))
Loading
Loading