Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
ddd6f4a
[FIX] vcp_management: bad path for icon
legalsylvain Feb 25, 2026
362e267
[IMP] vcp_management: allow to delete a repository, if it has reposit…
legalsylvain Feb 25, 2026
5bd0d26
[IMP] vcp_*: allow to fetch only 'sources' repositories, ignoring forks
legalsylvain Feb 25, 2026
331b89e
[IMP] vcp_*: allow to fetch only 'active' repositories, ignoring arch…
legalsylvain Feb 25, 2026
6e8a995
[IMP] vcp_management: allow to delete a branch, if it has repository …
legalsylvain Feb 25, 2026
b79d987
[IMP] vcp_*: add last_commit_date (in github, it related to 'PushedAt…
legalsylvain Feb 25, 2026
fcc76e2
[IMP] vcp_management: Add new smart button to go from a platform to r…
legalsylvain Feb 25, 2026
f3efe12
[FIX] vcp_github: adapt github mock to latest changes
legalsylvain Feb 25, 2026
fb1fef9
[REF] vcp_management: put vcp.platform.key model in dedicated file, a…
legalsylvain Feb 25, 2026
27b0f88
[IMP] vcp_management: use new image.mixin, simplifying code base
legalsylvain Feb 26, 2026
5f551f7
[IMP] vcp_odoo: use new image.mixin, simplifying code base
legalsylvain Feb 26, 2026
044a7a5
[FIX] vcp_odoo: delete a repository branch should unlink related odoo…
legalsylvain Feb 26, 2026
dc1df95
[IMP] vcp_odoo: Display number of versions in which the module is ava…
legalsylvain Feb 26, 2026
c07c249
[IMP] vcp_management: add branch count on repo level
legalsylvain Feb 26, 2026
dfc1ca8
[IMP] vcp_odoo: display all the versionsw where the module is available
legalsylvain Feb 26, 2026
7a24dd6
[ADD] vcp_github: demo data
legalsylvain Feb 26, 2026
6e21526
[IMP] vcp_*: Add branch pattern, to fetch only branches whose names a…
legalsylvain Feb 26, 2026
75f38b0
[REF] vcp_odoo: create a python file per model, following OCA convention
legalsylvain Feb 26, 2026
fca4785
[REF] vcp_management: use new menu items nested system
legalsylvain Feb 26, 2026
b256b9e
[IMP] vcp_management: Add vcp.branch menuitem, and improve display
legalsylvain Feb 26, 2026
6cbc808
[IMP+FIX] vcp_comment: Add vcp.branch menuitem, and improve display. …
legalsylvain Feb 27, 2026
32e0be3
[IMP] vcp_management: Add vcp.review menuitem, and improve display
legalsylvain Feb 27, 2026
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
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# generated from manifests external_dependencies
GitPython
github3.py
markdown
pathspec
6 changes: 4 additions & 2 deletions vcp_github/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@
"vcp_management",
],
"external_dependencies": {
"python": ["github3.py"],
"python": ["github3.py", "markdown"],
},
"data": [
"data/data.xml",
],
"demo": [],
"demo": [
"demo/demo_vcp_platform.xml",
],
}
8 changes: 8 additions & 0 deletions vcp_github/demo/demo_vcp_platform.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="vcp_platform_oca_github" model="vcp.platform">
<field name="name">OCA</field>
<field name="host_id" ref="vcp_github_host" />
<field name="fetch_repository_branch_pattern">^\d+\.\d+$</field>
</record>
</odoo>
27 changes: 21 additions & 6 deletions vcp_github/models/vcp_platform.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from datetime import datetime

import github3
import markdown
import requests
from pytz import UTC

Expand Down Expand Up @@ -40,6 +41,10 @@ def _update_information_github(self):
self.image_1920 = base64.b64encode(response.content)
repos = org.repositories()
for repo in repos:
if repo.fork and not self.fetch_repository_fork:
continue
if repo.archived and not self.fetch_repository_archived:
continue
self._update_github_repository(repo)
self.last_update = fields.Datetime.now()

Expand All @@ -50,20 +55,30 @@ def _parse_github_date(self, date):
datetime.fromisoformat(date.replace("Z", "+00:00"))
).replace(tzinfo=None)

def _parse_github_markdown(self, text):
return markdown.markdown(text)

def _update_github_repository(self, repo):
vals = {
"created_at": self._parse_github_date(repo.created_at),
"last_commit_date": self._parse_github_date(repo.pushed_at),
"stargazers_count": repo.stargazers_count,
"fork_count": repo.forks_count,
"is_fork": repo.fork,
"active": not repo.archived,
"watchers_count": repo.watchers_count,
"description": repo.description,
}
repository = self.env["vcp.repository"].search(
[
("name", "=", repo.name),
("platform_id", "=", self.id),
],
limit=1,
repository = (
self.env["vcp.repository"]
.with_context(active_test=False)
.search(
[
("name", "=", repo.name),
("platform_id", "=", self.id),
],
limit=1,
)
)
if not repository:
repository = (
Expand Down
12 changes: 10 additions & 2 deletions vcp_github/models/vcp_repository.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Copyright 2026 Dixmit
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
import logging
import re
from datetime import datetime, timedelta

import github3
Expand All @@ -25,6 +26,13 @@ def _update_branches_github(self):
existing_branches = {b.branch_id.name: b for b in self.branch_ids}
found_branches = self.env["vcp.repository.branch"]
for branch in repo.branches():
branch_pattern = (
self.fetch_branch_pattern
or self.platform_id.fetch_repository_branch_pattern
or False
)
if branch_pattern and not re.match(branch_pattern, branch.name):
continue
if branch.name in existing_branches:
existing_branches[branch.name].sudo().write(
{"last_commit": branch.commit.sha}
Expand Down Expand Up @@ -105,7 +113,7 @@ def _parse_github_pr(self, pr, client):
"id": str(c["id"]),
"user_id": c.get("user")
and self.platform_id.host_id._get_user(c["user"].get("login")),
"body": c["body"],
"body": self.platform_id._parse_github_markdown(c["body"]),
"created_at": self.platform_id._parse_github_date(c["created_at"]),
"updated_at": self.platform_id._parse_github_date(c["updated_at"]),
}
Expand All @@ -116,7 +124,7 @@ def _parse_github_pr(self, pr, client):
"id": str(r["id"]),
"user_id": r.get("user")
and self.platform_id.host_id._get_user(r["user"].get("login")),
"body": r["body"],
"body": self.platform_id._parse_github_markdown(r["body"]),
"submitted_at": self.platform_id._parse_github_date(
r.get("submitted_at")
),
Expand Down
7 changes: 7 additions & 0 deletions vcp_github/tests/test_github.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,16 @@ def test_update_organization_with_repository(self):
mock_org.avatar_url = False
mock_repo1 = MagicMock()
mock_repo1.name = "server-tools"
mock_repo1.archived = False
mock_repo1.fork = False
mock_repo1.created_at = "2020-01-01T00:00:00Z"
mock_repo1.pushed_at = "2020-02-01T00:00:00Z"
mock_repo2 = MagicMock()
mock_repo2.name = "server-brand"
mock_repo2.archived = False
mock_repo2.fork = False
mock_repo2.created_at = "2021-01-01T10:00:00Z"
mock_repo2.pushed_at = "2021-02-01T00:00:00Z"
mock_org.repositories.return_value = [mock_repo1, mock_repo2]
mock_client.organization.return_value = mock_org
mock_github3.login.return_value = mock_client
Expand Down Expand Up @@ -102,6 +108,7 @@ def test_update_repository(self):
"created_at": "2023-01-01T00:00:00Z",
"updated_at": "2023-01-03T00:00:00Z",
"closed_at": "2023-01-02T00:00:00Z",
"pushed_at": "2024-01-02T00:00:00Z",
"commits": 3,
"comments": 2,
"review_comments": 1,
Expand Down
2 changes: 1 addition & 1 deletion vcp_management/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
"security/ir.model.access.csv",
"data/ir_cron.xml",
"templates/templates.xml",
"views/menu.xml",
"views/vcp_comment.xml",
"views/vcp_review.xml",
"views/vcp_request.xml",
Expand All @@ -26,6 +25,7 @@
"views/vcp_host.xml",
"views/vcp_rule.xml",
"views/vcp_rule_information.xml",
"views/menu.xml",
],
"demo": [],
"external_dependencies": {
Expand Down
1 change: 1 addition & 0 deletions vcp_management/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from . import vcp_platform
from . import vcp_platform_key
from . import vcp_branch
from . import vcp_rule
from . import vcp_rule_information
Expand Down
1 change: 1 addition & 0 deletions vcp_management/models/vcp_branch.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ class VcpBranch(models.Model):
comodel_name="vcp.platform",
string="Platform",
required=True,
readonly=True,
)
_sql_constraints = [
("name_uniq", "unique(name, platform_id)", "Branch name must be unique.")
Expand Down
48 changes: 20 additions & 28 deletions vcp_management/models/vcp_platform.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class VcpPlatform(models.Model):
"""

_name = "vcp.platform"
_inherit = ["image.mixin"]
_description = "VCP Platform"

name = fields.Char(required=True)
Expand All @@ -28,21 +29,10 @@ class VcpPlatform(models.Model):
last_update = fields.Datetime(readonly=True)
active = fields.Boolean(default=True)
update_interval_days = fields.Integer(default=3)
image_1920 = fields.Image()
branch_ids = fields.One2many(
"vcp.branch",
inverse_name="platform_id",
)
image_128 = fields.Image(
max_width=128,
max_height=128,
store=True,
related="image_1920",
string="Image 128",
)
image_64 = fields.Image(
max_width=64, max_height=64, store=True, related="image_1920", string="Image 64"
)
host_id = fields.Many2one(
"vcp.host",
required=True,
Expand All @@ -57,8 +47,22 @@ class VcpPlatform(models.Model):
"vcp.repository",
inverse_name="platform_id",
)
repository_count = fields.Integer(compute="_compute_repository_count", store=True)
default_update_repository_information = fields.Boolean()
information_update = fields.Boolean()
fetch_repository_fork = fields.Boolean(
help="If checked, all repositories will be fetched (sources and forks)."
" Otherwise, only sources repositories will be fetched"
)
fetch_repository_archived = fields.Boolean(
help="If checked, all repositories will be fetched (actives and archived)."
" Otherwise, only active repositories will be fetched"
)
fetch_repository_branch_pattern = fields.Char(
help="Regular Expression. If set, only branches whose names are matching"
" the pattern will be fetched, when fetching branches of the repositories"
" of the platform."
)
local_path = fields.Char(compute="_compute_local_path")
rule_ids = fields.Many2many(
"vcp.rule",
Expand All @@ -80,6 +84,11 @@ def _compute_local_path(self):
for record in self:
record.local_path = f"{source_path}/{record.id}"

@api.depends("repository_ids")
def _compute_repository_count(self):
for record in self:
record.repository_count = len(record.repository_ids)

def update_information(self):
self.ensure_one()
getattr(self, f"_update_information_{self.kind}")()
Expand Down Expand Up @@ -352,20 +361,3 @@ def _improve_vcp_data(self, data, kind, **kwargs):
values["name"] = repository.name
values["url"] = repository._get_repository_url()
return data


class VcpPlatformKey(models.Model):
_name = "vcp.platform.key"
_description = "VCP Platform API Key" # TODO

platform_id = fields.Many2one(
comodel_name="vcp.platform",
string="Platform",
required=True,
ondelete="cascade",
)
name = fields.Char(required=True)

_sql_constraints = [
("name_uniq", "unique(name, platform_id)", "API Key must be unique.")
]
24 changes: 24 additions & 0 deletions vcp_management/models/vcp_platform_key.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Copyright 2026 Dixmit
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
import logging

from odoo import fields, models

_logger = logging.getLogger(__name__)


class VcpPlatformKey(models.Model):
_name = "vcp.platform.key"
_description = "VCP Platform API Key" # TODO

platform_id = fields.Many2one(
comodel_name="vcp.platform",
string="Platform",
required=True,
ondelete="cascade",
)
name = fields.Char(required=True)

_sql_constraints = [
("name_uniq", "unique(name, platform_id)", "API Key must be unique.")
]
17 changes: 16 additions & 1 deletion vcp_management/models/vcp_repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,23 @@ class VcpRepository(models.Model):
required=True,
)
created_at = fields.Datetime(readonly=True)
last_commit_date = fields.Datetime(readonly=True)
fetch_branch_pattern = fields.Char(
help="Regular Expression. If set, only branches whose names are matching"
" the pattern will be fetched. You can define that value at platform level."
)
stargazers_count = fields.Integer(readonly=True)
is_fork = fields.Boolean(
readonly=True,
help="Specify if the repo is a Source or a Fork repository",
)
fork_count = fields.Integer(readonly=True)
watchers_count = fields.Integer(readonly=True)
from_date = fields.Datetime(readonly=True, required=True)
request_ids = fields.One2many("vcp.request", inverse_name="repository_id")
request_count = fields.Integer(compute="_compute_request_count")
test_field = fields.Char() # TODO remove after testing
active = fields.Boolean(default=True)
active = fields.Boolean(default=True, readonly=True)
information_update = fields.Boolean(
compute="_compute_information_update",
store=True,
Expand All @@ -46,6 +55,7 @@ class VcpRepository(models.Model):
"vcp.repository.branch",
inverse_name="repository_id",
)
branch_count = fields.Integer(compute="_compute_branch_count", store=True)

def _get_rules(self):
rules = self.rule_ids
Expand Down Expand Up @@ -74,6 +84,11 @@ def _compute_request_count(self):
for record in self:
record.request_count = len(record.request_ids)

@api.depends("branch_ids")
def _compute_branch_count(self):
for record in self:
record.branch_count = len(record.branch_ids)

def update_branches(self):
self.ensure_one()
now = fields.Datetime.now()
Expand Down
9 changes: 9 additions & 0 deletions vcp_management/models/vcp_repository_branch.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@ class VcpRepositoryBranch(models.Model):
"vcp.branch",
string="Branch",
required=True,
ondelete="cascade",
)
repository_id = fields.Many2one(
"vcp.repository",
required=True,
ondelete="cascade",
)
platform_id = fields.Many2one(
related="repository_id.platform_id",
Expand Down Expand Up @@ -96,3 +98,10 @@ def _download_code(self):
depth=1,
)
return result

def _compute_display_name(self):
if not self._context.get("display_only_branch_name"):
return super()._compute_display_name()

for record in self:
record.display_name = record.branch_id.name
Loading