mirror of
https://github.com/boostorg/website-v2.git
synced 2026-01-19 04:42:17 +00:00
Decouple release reports from releases (#1737)
This commit is contained in:
@@ -334,7 +334,7 @@ For this to work `SLACK_BOT_API` must be set in the `.env` file.
|
|||||||
|
|
||||||
| Options | Format | Description |
|
| Options | Format | Description |
|
||||||
|----------------|--------|----------------------------------------------------------------------------------------------------------------------|
|
|----------------|--------|----------------------------------------------------------------------------------------------------------------------|
|
||||||
| `--start_date` | date | If passed, retrieves data from the start date supplied, d-m-y, default 20-11-1998 (the start of the data in mailman) |
|
| `--start_date` | date | If passed, retrieves data from the start date supplied, d-m-y, default 1998-11-20 (the start of the data in mailman) |
|
||||||
| `--end_date` | date | If passed, If passed, retrieves data until the start date supplied, d-m-y, default today |
|
| `--end_date` | date | If passed, If passed, retrieves data until the start date supplied, d-m-y, default today |
|
||||||
|
|
||||||
## `link_contributors_to_users`
|
## `link_contributors_to_users`
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ from reports.generation import (
|
|||||||
get_new_subscribers_stats,
|
get_new_subscribers_stats,
|
||||||
)
|
)
|
||||||
from slack.models import Channel, SlackActivityBucket, SlackUser
|
from slack.models import Channel, SlackActivityBucket, SlackUser
|
||||||
from versions.models import Version
|
from versions.models import Version, ReportConfiguration
|
||||||
from .models import (
|
from .models import (
|
||||||
Commit,
|
Commit,
|
||||||
CommitAuthor,
|
CommitAuthor,
|
||||||
@@ -232,8 +232,8 @@ class CreateReportForm(CreateReportFullForm):
|
|||||||
|
|
||||||
html_template_name = "admin/release_report_detail.html"
|
html_template_name = "admin/release_report_detail.html"
|
||||||
|
|
||||||
version = ModelChoiceField(
|
report_configuration = ModelChoiceField(
|
||||||
queryset=Version.objects.minor_versions().order_by("-version_array")
|
queryset=ReportConfiguration.objects.order_by("-version")
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
@@ -255,14 +255,12 @@ class CreateReportForm(CreateReportFullForm):
|
|||||||
self.cleaned_data["library_8"],
|
self.cleaned_data["library_8"],
|
||||||
]
|
]
|
||||||
lib_string = ",".join(str(x.id) if x else "" for x in chosen_libraries)
|
lib_string = ",".join(str(x.id) if x else "" for x in chosen_libraries)
|
||||||
version = self.cleaned_data["version"]
|
report_configuration = self.cleaned_data["report_configuration"]
|
||||||
return f"release-report-{lib_string}-{version.name}"
|
return f"release-report-{lib_string}-{report_configuration.version}"
|
||||||
|
|
||||||
def _get_top_contributors_for_version(self):
|
def _get_top_contributors_for_version(self, version):
|
||||||
return (
|
return (
|
||||||
CommitAuthor.objects.filter(
|
CommitAuthor.objects.filter(commit__library_version__version=version)
|
||||||
commit__library_version__version=self.cleaned_data["version"]
|
|
||||||
)
|
|
||||||
.annotate(
|
.annotate(
|
||||||
commit_count=Count(
|
commit_count=Count(
|
||||||
"commit",
|
"commit",
|
||||||
@@ -277,30 +275,32 @@ class CreateReportForm(CreateReportFullForm):
|
|||||||
def _get_library_queryset_by_version(
|
def _get_library_queryset_by_version(
|
||||||
self, version: Version, annotate_commit_count=False
|
self, version: Version, annotate_commit_count=False
|
||||||
):
|
):
|
||||||
qs = self.library_queryset.filter(
|
qs = self.library_queryset.none()
|
||||||
library_version=LibraryVersion.objects.filter(
|
if version:
|
||||||
library=OuterRef("id"), version=version
|
qs = self.library_queryset.filter(
|
||||||
)[:1],
|
library_version=LibraryVersion.objects.filter(
|
||||||
)
|
library=OuterRef("id"), version=version
|
||||||
|
)[:1],
|
||||||
|
)
|
||||||
if annotate_commit_count:
|
if annotate_commit_count:
|
||||||
qs = qs.annotate(commit_count=Count("library_version__commit"))
|
qs = qs.annotate(commit_count=Count("library_version__commit"))
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
def _get_top_libraries_for_version(self):
|
def _get_top_libraries_for_version(self, version):
|
||||||
library_qs = self._get_library_queryset_by_version(
|
library_qs = self._get_library_queryset_by_version(
|
||||||
self.cleaned_data["version"], annotate_commit_count=True
|
version, annotate_commit_count=True
|
||||||
)
|
)
|
||||||
return library_qs.order_by("-commit_count")
|
return library_qs.order_by("-commit_count")
|
||||||
|
|
||||||
def _get_libraries_by_name(self):
|
def _get_libraries_by_name(self, version):
|
||||||
library_qs = self._get_library_queryset_by_version(
|
library_qs = self._get_library_queryset_by_version(
|
||||||
self.cleaned_data["version"], annotate_commit_count=True
|
version, annotate_commit_count=True
|
||||||
)
|
)
|
||||||
return library_qs.order_by("name")
|
return library_qs.order_by("name")
|
||||||
|
|
||||||
def _get_libraries_by_quality(self):
|
def _get_libraries_by_quality(self, version):
|
||||||
# returns "great", "good", and "standard" libraries in that order
|
# returns "great", "good", and "standard" libraries in that order
|
||||||
library_qs = self._get_library_queryset_by_version(self.cleaned_data["version"])
|
library_qs = self._get_library_queryset_by_version(version)
|
||||||
return list(
|
return list(
|
||||||
chain(
|
chain(
|
||||||
library_qs.filter(graphic__isnull=False),
|
library_qs.filter(graphic__isnull=False),
|
||||||
@@ -309,17 +309,16 @@ class CreateReportForm(CreateReportFullForm):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
def _get_library_version_counts(self, libraries, library_order):
|
def _get_library_version_counts(self, library_order, version):
|
||||||
library_qs = self._get_library_queryset_by_version(
|
library_qs = self._get_library_queryset_by_version(
|
||||||
self.cleaned_data["version"], annotate_commit_count=True
|
version, annotate_commit_count=True
|
||||||
)
|
)
|
||||||
return sorted(
|
return sorted(
|
||||||
list(library_qs.values("commit_count", "id")),
|
list(library_qs.values("commit_count", "id")),
|
||||||
key=lambda x: library_order.index(x["id"]),
|
key=lambda x: library_order.index(x["id"]),
|
||||||
)
|
)
|
||||||
|
|
||||||
def _global_new_contributors(self, library_version):
|
def _global_new_contributors(self, version):
|
||||||
version = self.cleaned_data["version"]
|
|
||||||
version_lt = list(
|
version_lt = list(
|
||||||
Version.objects.minor_versions()
|
Version.objects.minor_versions()
|
||||||
.filter(version_array__lt=version.cleaned_version_parts_int)
|
.filter(version_array__lt=version.cleaned_version_parts_int)
|
||||||
@@ -343,8 +342,7 @@ class CreateReportForm(CreateReportFullForm):
|
|||||||
|
|
||||||
return set(version_author_ids) - set(prior_version_author_ids)
|
return set(version_author_ids) - set(prior_version_author_ids)
|
||||||
|
|
||||||
def _count_new_contributors(self, libraries, library_order):
|
def _count_new_contributors(self, libraries, library_order, version):
|
||||||
version = self.cleaned_data["version"]
|
|
||||||
version_lt = list(
|
version_lt = list(
|
||||||
Version.objects.minor_versions()
|
Version.objects.minor_versions()
|
||||||
.filter(version_array__lt=version.cleaned_version_parts_int)
|
.filter(version_array__lt=version.cleaned_version_parts_int)
|
||||||
@@ -382,12 +380,12 @@ class CreateReportForm(CreateReportFullForm):
|
|||||||
key=lambda x: library_order.index(x["id"]),
|
key=lambda x: library_order.index(x["id"]),
|
||||||
)
|
)
|
||||||
|
|
||||||
def _count_issues(self, libraries, library_order, version):
|
def _count_issues(self, libraries, library_order, version, prior_version):
|
||||||
data = {
|
data = {
|
||||||
x["library_id"]: x
|
x["library_id"]: x
|
||||||
for x in Issue.objects.count_opened_closed_during_release(version).filter(
|
for x in Issue.objects.count_opened_closed_during_release(
|
||||||
library_id__in=[x.id for x in libraries]
|
version, prior_version
|
||||||
)
|
).filter(library_id__in=[x.id for x in libraries])
|
||||||
}
|
}
|
||||||
ret = []
|
ret = []
|
||||||
for lib_id in library_order:
|
for lib_id in library_order:
|
||||||
@@ -397,14 +395,14 @@ class CreateReportForm(CreateReportFullForm):
|
|||||||
ret.append({"opened": 0, "closed": 0, "library_id": lib_id})
|
ret.append({"opened": 0, "closed": 0, "library_id": lib_id})
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def _count_commit_contributors_totals(self, version):
|
def _count_commit_contributors_totals(self, version, prior_version):
|
||||||
"""Get a count of contributors for this release, and a count of
|
"""Get a count of contributors for this release, and a count of
|
||||||
new contributors.
|
new contributors.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
version_lt = list(
|
version_lt = list(
|
||||||
Version.objects.minor_versions()
|
Version.objects.minor_versions()
|
||||||
.filter(version_array__lt=version.cleaned_version_parts_int)
|
.filter(version_array__lte=prior_version.cleaned_version_parts_int)
|
||||||
.values_list("id", flat=True)
|
.values_list("id", flat=True)
|
||||||
)
|
)
|
||||||
version_lte = version_lt + [version.id]
|
version_lte = version_lt + [version.id]
|
||||||
@@ -439,13 +437,13 @@ class CreateReportForm(CreateReportFullForm):
|
|||||||
this_release_count = qs["this_release_count"]
|
this_release_count = qs["this_release_count"]
|
||||||
return this_release_count, new_count
|
return this_release_count, new_count
|
||||||
|
|
||||||
def _get_top_contributors_for_library_version(self, library_order):
|
def _get_top_contributors_for_library_version(self, library_order, version):
|
||||||
top_contributors_release = []
|
top_contributors_release = []
|
||||||
for library_id in library_order:
|
for library_id in library_order:
|
||||||
top_contributors_release.append(
|
top_contributors_release.append(
|
||||||
CommitAuthor.objects.filter(
|
CommitAuthor.objects.filter(
|
||||||
commit__library_version=LibraryVersion.objects.get(
|
commit__library_version=LibraryVersion.objects.get(
|
||||||
version=self.cleaned_data["version"], library_id=library_id
|
version=version, library_id=library_id
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.annotate(commit_count=Count("commit"))
|
.annotate(commit_count=Count("commit"))
|
||||||
@@ -453,10 +451,10 @@ class CreateReportForm(CreateReportFullForm):
|
|||||||
)
|
)
|
||||||
return top_contributors_release
|
return top_contributors_release
|
||||||
|
|
||||||
def _count_mailinglist_contributors(self, version):
|
def _count_mailinglist_contributors(self, version, prior_version):
|
||||||
version_lt = list(
|
version_lt = list(
|
||||||
Version.objects.minor_versions()
|
Version.objects.minor_versions()
|
||||||
.filter(version_array__lt=version.cleaned_version_parts_int)
|
.filter(version_array__lte=prior_version.cleaned_version_parts_int)
|
||||||
.values_list("id", flat=True)
|
.values_list("id", flat=True)
|
||||||
)
|
)
|
||||||
version_lte = version_lt + [version.id]
|
version_lte = version_lt + [version.id]
|
||||||
@@ -620,7 +618,9 @@ class CreateReportForm(CreateReportFullForm):
|
|||||||
):
|
):
|
||||||
"""Get slack stats for specific channels, or all channels."""
|
"""Get slack stats for specific channels, or all channels."""
|
||||||
start = prior_version.release_date
|
start = prior_version.release_date
|
||||||
end = version.release_date - timedelta(days=1)
|
end = date.today()
|
||||||
|
if version.release_date:
|
||||||
|
end = version.release_date - timedelta(days=1)
|
||||||
# count of all messages in the date range
|
# count of all messages in the date range
|
||||||
q = Q(day__range=[start, end])
|
q = Q(day__range=[start, end])
|
||||||
if channels:
|
if channels:
|
||||||
@@ -671,7 +671,15 @@ class CreateReportForm(CreateReportFullForm):
|
|||||||
return diffs
|
return diffs
|
||||||
|
|
||||||
def get_stats(self):
|
def get_stats(self):
|
||||||
version = self.cleaned_data["version"]
|
report_configuration = self.cleaned_data["report_configuration"]
|
||||||
|
version = Version.objects.filter(name=report_configuration.version).first()
|
||||||
|
|
||||||
|
prior_version = None
|
||||||
|
if not version:
|
||||||
|
# if the version is not set then the user has chosen a report configuration
|
||||||
|
# that's not matching a live version, so we use the most recent version
|
||||||
|
version = Version.objects.filter(name="master").first()
|
||||||
|
prior_version = Version.objects.most_recent()
|
||||||
|
|
||||||
downloads = {
|
downloads = {
|
||||||
k: list(v)
|
k: list(v)
|
||||||
@@ -680,12 +688,14 @@ class CreateReportForm(CreateReportFullForm):
|
|||||||
key=attrgetter("operating_system"),
|
key=attrgetter("operating_system"),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
prior_version = (
|
|
||||||
Version.objects.minor_versions()
|
if not prior_version:
|
||||||
.filter(version_array__lt=version.cleaned_version_parts_int)
|
prior_version = (
|
||||||
.order_by("-version_array")
|
Version.objects.minor_versions()
|
||||||
.first()
|
.filter(version_array__lt=version.cleaned_version_parts_int)
|
||||||
)
|
.order_by("-version_array")
|
||||||
|
.first()
|
||||||
|
)
|
||||||
|
|
||||||
commit_count = Commit.objects.filter(
|
commit_count = Commit.objects.filter(
|
||||||
library_version__version__name__lte=version.name,
|
library_version__version__name__lte=version.name,
|
||||||
@@ -696,8 +706,8 @@ class CreateReportForm(CreateReportFullForm):
|
|||||||
library_version__library__in=self.library_queryset,
|
library_version__library__in=self.library_queryset,
|
||||||
).count()
|
).count()
|
||||||
|
|
||||||
top_libraries_for_version = self._get_top_libraries_for_version()
|
top_libraries_for_version = self._get_top_libraries_for_version(version)
|
||||||
top_libraries_by_name = self._get_libraries_by_name()
|
top_libraries_by_name = self._get_libraries_by_name(version)
|
||||||
library_order = self._get_library_order(top_libraries_by_name)
|
library_order = self._get_library_order(top_libraries_by_name)
|
||||||
libraries = Library.objects.filter(id__in=library_order).order_by(
|
libraries = Library.objects.filter(id__in=library_order).order_by(
|
||||||
Case(
|
Case(
|
||||||
@@ -719,10 +729,10 @@ class CreateReportForm(CreateReportFullForm):
|
|||||||
for item in zip(
|
for item in zip(
|
||||||
libraries,
|
libraries,
|
||||||
self._get_library_full_counts(libraries, library_order),
|
self._get_library_full_counts(libraries, library_order),
|
||||||
self._get_library_version_counts(libraries, library_order),
|
self._get_library_version_counts(library_order, version),
|
||||||
self._get_top_contributors_for_library_version(library_order),
|
self._get_top_contributors_for_library_version(library_order, version),
|
||||||
self._count_new_contributors(libraries, library_order),
|
self._count_new_contributors(libraries, library_order, version),
|
||||||
self._count_issues(libraries, library_order, version),
|
self._count_issues(libraries, library_order, version, prior_version),
|
||||||
self._get_library_versions(library_order, version),
|
self._get_library_versions(library_order, version),
|
||||||
self._get_dependency_data(library_order, version),
|
self._get_dependency_data(library_order, version),
|
||||||
)
|
)
|
||||||
@@ -730,7 +740,7 @@ class CreateReportForm(CreateReportFullForm):
|
|||||||
library_data = [
|
library_data = [
|
||||||
x for x in library_data if x["version_count"]["commit_count"] > 0
|
x for x in library_data if x["version_count"]["commit_count"] > 0
|
||||||
]
|
]
|
||||||
top_contributors = self._get_top_contributors_for_version()
|
top_contributors = self._get_top_contributors_for_version(version)
|
||||||
# total messages sent during this release (version)
|
# total messages sent during this release (version)
|
||||||
total_mailinglist_count = EmailData.objects.filter(version=version).aggregate(
|
total_mailinglist_count = EmailData.objects.filter(version=version).aggregate(
|
||||||
total=Sum("count")
|
total=Sum("count")
|
||||||
@@ -743,11 +753,11 @@ class CreateReportForm(CreateReportFullForm):
|
|||||||
(
|
(
|
||||||
mailinglist_contributor_release_count,
|
mailinglist_contributor_release_count,
|
||||||
mailinglist_contributor_new_count,
|
mailinglist_contributor_new_count,
|
||||||
) = self._count_mailinglist_contributors(version)
|
) = self._count_mailinglist_contributors(version, prior_version)
|
||||||
(
|
(
|
||||||
commit_contributors_release_count,
|
commit_contributors_release_count,
|
||||||
commit_contributors_new_count,
|
commit_contributors_new_count,
|
||||||
) = self._count_commit_contributors_totals(version)
|
) = self._count_commit_contributors_totals(version, prior_version)
|
||||||
library_count = LibraryVersion.objects.filter(
|
library_count = LibraryVersion.objects.filter(
|
||||||
version=version,
|
version=version,
|
||||||
library__in=self.library_queryset,
|
library__in=self.library_queryset,
|
||||||
@@ -775,22 +785,35 @@ class CreateReportForm(CreateReportFullForm):
|
|||||||
slack_channels = batched(
|
slack_channels = batched(
|
||||||
Channel.objects.filter(name__istartswith="boost").order_by("name"), 10
|
Channel.objects.filter(name__istartswith="boost").order_by("name"), 10
|
||||||
)
|
)
|
||||||
committee_members = version.financial_committee_members.all()
|
committee_members = report_configuration.financial_committee_members.all()
|
||||||
mailinglist_post_stats = get_mailing_list_post_stats(
|
mailinglist_post_stats = get_mailing_list_post_stats(
|
||||||
prior_version.release_date, version.release_date
|
prior_version.release_date, version.release_date or date.today()
|
||||||
)
|
)
|
||||||
new_subscribers_stats = get_new_subscribers_stats(
|
new_subscribers_stats = get_new_subscribers_stats(
|
||||||
prior_version.release_date, version.release_date
|
prior_version.release_date, version.release_date or date.today()
|
||||||
)
|
)
|
||||||
library_index_library_data = []
|
library_index_library_data = []
|
||||||
for library in self._get_libraries_by_quality():
|
for library in self._get_libraries_by_quality(version):
|
||||||
library_index_library_data.append(
|
library_index_library_data.append(
|
||||||
(
|
(
|
||||||
library,
|
library,
|
||||||
library in [lib["library"] for lib in library_data],
|
library in [lib["library"] for lib in library_data],
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
wordcloud_base64, wordcloud_top_words = generate_wordcloud(version)
|
wordcloud_base64, wordcloud_top_words = generate_wordcloud(
|
||||||
|
version, prior_version
|
||||||
|
)
|
||||||
|
|
||||||
|
opened_issues_count = (
|
||||||
|
Issue.objects.filter(library__in=self.library_queryset)
|
||||||
|
.opened_during_release(version, prior_version)
|
||||||
|
.count()
|
||||||
|
)
|
||||||
|
closed_issues_count = (
|
||||||
|
Issue.objects.filter(library__in=self.library_queryset)
|
||||||
|
.closed_during_release(version, prior_version)
|
||||||
|
.count()
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"committee_members": committee_members,
|
"committee_members": committee_members,
|
||||||
@@ -799,17 +822,10 @@ class CreateReportForm(CreateReportFullForm):
|
|||||||
"wordcloud_base64": wordcloud_base64,
|
"wordcloud_base64": wordcloud_base64,
|
||||||
"wordcloud_frequencies": wordcloud_top_words,
|
"wordcloud_frequencies": wordcloud_top_words,
|
||||||
"version": version,
|
"version": version,
|
||||||
|
"report_configuration": report_configuration,
|
||||||
"prior_version": prior_version,
|
"prior_version": prior_version,
|
||||||
"opened_issues_count": Issue.objects.filter(
|
"opened_issues_count": opened_issues_count,
|
||||||
library__in=self.library_queryset
|
"closed_issues_count": closed_issues_count,
|
||||||
)
|
|
||||||
.opened_during_release(version)
|
|
||||||
.count(),
|
|
||||||
"closed_issues_count": Issue.objects.filter(
|
|
||||||
library__in=self.library_queryset
|
|
||||||
)
|
|
||||||
.closed_during_release(version)
|
|
||||||
.count(),
|
|
||||||
"mailinglist_counts": mailinglist_counts,
|
"mailinglist_counts": mailinglist_counts,
|
||||||
"mailinglist_total": total_mailinglist_count or 0,
|
"mailinglist_total": total_mailinglist_count or 0,
|
||||||
"mailinglist_contributor_release_count": mailinglist_contributor_release_count, # noqa: E501
|
"mailinglist_contributor_release_count": mailinglist_contributor_release_count, # noqa: E501
|
||||||
|
|||||||
@@ -111,11 +111,15 @@ def get_commit_data_for_repo_versions(key, min_version=""):
|
|||||||
if not is_clone_successful:
|
if not is_clone_successful:
|
||||||
logger.error(f"Clone failed for {library.key}. {message=} {error=}")
|
logger.error(f"Clone failed for {library.key}. {message=} {error=}")
|
||||||
return
|
return
|
||||||
versions = [""] + list(
|
versions = (
|
||||||
Version.objects.minor_versions()
|
[""]
|
||||||
.filter(library_version__library__key=library.key)
|
+ list(
|
||||||
.order_by("version_array")
|
Version.objects.minor_versions()
|
||||||
.values_list("name", flat=True)
|
.filter(library_version__library__key=library.key)
|
||||||
|
.order_by("version_array")
|
||||||
|
.values_list("name", flat=True)
|
||||||
|
)
|
||||||
|
+ ["master"]
|
||||||
)
|
)
|
||||||
for a, b in zip(versions, versions[1:]):
|
for a, b in zip(versions, versions[1:]):
|
||||||
if a < min_version and b < min_version:
|
if a < min_version and b < min_version:
|
||||||
@@ -489,17 +493,29 @@ class LibraryUpdater:
|
|||||||
)
|
)
|
||||||
CommitAuthorEmail.objects.create(email=commit.email, author=author)
|
CommitAuthorEmail.objects.create(email=commit.email, author=author)
|
||||||
authors[commit.email] = author
|
authors[commit.email] = author
|
||||||
return Commit(
|
|
||||||
author=author,
|
try:
|
||||||
library_version=library_versions[commit.version],
|
library_version = library_versions[commit.version]
|
||||||
sha=commit.sha,
|
return Commit(
|
||||||
message=commit.message,
|
author=author,
|
||||||
committed_at=commit.committed_at,
|
library_version=library_version,
|
||||||
is_merge=commit.is_merge,
|
sha=commit.sha,
|
||||||
)
|
message=commit.message,
|
||||||
|
committed_at=commit.committed_at,
|
||||||
|
is_merge=commit.is_merge,
|
||||||
|
)
|
||||||
|
|
||||||
|
except KeyError:
|
||||||
|
logger.error(f"KeyError {commit.version=}")
|
||||||
|
return None
|
||||||
|
|
||||||
def handle_version_diff_stat(diff: VersionDiffStat):
|
def handle_version_diff_stat(diff: VersionDiffStat):
|
||||||
lv = library_versions[diff.version]
|
try:
|
||||||
|
lv = library_versions[diff.version]
|
||||||
|
except KeyError:
|
||||||
|
# we iterate over all library versions, but for libraries that
|
||||||
|
# haven't had updates in a release one may not exist for master/develop
|
||||||
|
return None
|
||||||
lv.insertions = diff.insertions
|
lv.insertions = diff.insertions
|
||||||
lv.deletions = diff.deletions
|
lv.deletions = diff.deletions
|
||||||
lv.files_changed = diff.files_changed
|
lv.files_changed = diff.files_changed
|
||||||
@@ -509,10 +525,14 @@ class LibraryUpdater:
|
|||||||
for item in get_commit_data_for_repo_versions(library.key, min_version):
|
for item in get_commit_data_for_repo_versions(library.key, min_version):
|
||||||
match item:
|
match item:
|
||||||
case ParsedCommit():
|
case ParsedCommit():
|
||||||
commits_handled += 1
|
commit_item = handle_commit(item)
|
||||||
commits.append(handle_commit(item))
|
if commit_item:
|
||||||
|
commits.append(commit_item)
|
||||||
|
commits_handled += 1
|
||||||
case VersionDiffStat():
|
case VersionDiffStat():
|
||||||
library_version_updates.append(handle_version_diff_stat(item))
|
lv_update = handle_version_diff_stat(item)
|
||||||
|
if lv_update:
|
||||||
|
library_version_updates.append(lv_update)
|
||||||
case _:
|
case _:
|
||||||
assert_never()
|
assert_never()
|
||||||
|
|
||||||
|
|||||||
@@ -1,46 +1,36 @@
|
|||||||
|
from datetime import date
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import Q, Count
|
from django.db.models import Q, Count
|
||||||
|
|
||||||
from versions.models import Version
|
|
||||||
|
|
||||||
|
|
||||||
class IssueQuerySet(models.QuerySet):
|
class IssueQuerySet(models.QuerySet):
|
||||||
def closed_during_release(self, version):
|
def closed_during_release(self, version, prior_version):
|
||||||
"""Get the issues that were closed during a specific version.
|
"""Get the issues that were closed during a specific version.
|
||||||
|
|
||||||
Uses the release dates of the version and the prior version and queries for
|
Uses the release dates of the version and the prior version and queries for
|
||||||
issues closed in that timeframe.
|
issues closed in that timeframe.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
prior_release = (
|
release_date = version.release_date
|
||||||
Version.objects.minor_versions()
|
if version.name == "master":
|
||||||
.filter(release_date__lt=version.release_date)
|
release_date = date.today()
|
||||||
.order_by("-release_date")
|
|
||||||
.first()
|
|
||||||
)
|
|
||||||
if not prior_release:
|
|
||||||
return self.none()
|
|
||||||
return self.filter(
|
return self.filter(
|
||||||
closed__gte=prior_release.release_date, closed__lt=version.release_date
|
closed__gte=prior_version.release_date, closed__lt=release_date
|
||||||
)
|
)
|
||||||
|
|
||||||
def opened_during_release(self, version):
|
def opened_during_release(self, version, prior_version):
|
||||||
"""Get the issues that were created during a specific version release.
|
"""Get the issues that were created during a specific version release.
|
||||||
|
|
||||||
Uses the release dates of the version and the prior version and queries for
|
Uses the release dates of the version and the prior version and queries for
|
||||||
issues created in that timeframe.
|
issues created in that timeframe.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
prior_release = (
|
release_date = version.release_date
|
||||||
Version.objects.minor_versions()
|
if version.name == "master":
|
||||||
.filter(release_date__lt=version.release_date)
|
release_date = date.today()
|
||||||
.order_by("-release_date")
|
|
||||||
.first()
|
|
||||||
)
|
|
||||||
if not prior_release:
|
|
||||||
return self.none()
|
|
||||||
return self.filter(
|
return self.filter(
|
||||||
created__gte=prior_release.release_date, created__lt=version.release_date
|
created__gte=prior_version.release_date, created__lt=release_date
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -48,35 +38,32 @@ class IssueManager(models.Manager):
|
|||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return IssueQuerySet(self.model, using=self._db)
|
return IssueQuerySet(self.model, using=self._db)
|
||||||
|
|
||||||
def closed_during_release(self, version):
|
def closed_during_release(self, version, prior_version):
|
||||||
return self.get_queryset().closed_during_release(version)
|
return self.get_queryset().closed_during_release(version, prior_version)
|
||||||
|
|
||||||
def opened_during_release(self, version):
|
def opened_during_release(self, version, prior_version):
|
||||||
return self.get_queryset().opened_during_release(version)
|
return self.get_queryset().opened_during_release(version, prior_version)
|
||||||
|
|
||||||
def count_opened_closed_during_release(self, version):
|
def count_opened_closed_during_release(self, version, prior_version):
|
||||||
|
if version is None:
|
||||||
|
return self.get_queryset().none()
|
||||||
qs = self.get_queryset()
|
qs = self.get_queryset()
|
||||||
prior_release = (
|
release_date = version.release_date
|
||||||
Version.objects.minor_versions()
|
if version.name == "master":
|
||||||
.filter(release_date__lt=version.release_date)
|
release_date = date.today()
|
||||||
.order_by("-release_date")
|
|
||||||
.first()
|
|
||||||
)
|
|
||||||
if not prior_release:
|
|
||||||
return qs.none()
|
|
||||||
return qs.values("library_id").annotate(
|
return qs.values("library_id").annotate(
|
||||||
opened=Count(
|
opened=Count(
|
||||||
"id",
|
"id",
|
||||||
filter=Q(
|
filter=Q(
|
||||||
created__gte=prior_release.release_date,
|
created__gte=prior_version.release_date,
|
||||||
created__lt=version.release_date,
|
created__lt=release_date,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
closed=Count(
|
closed=Count(
|
||||||
"id",
|
"id",
|
||||||
filter=Q(
|
filter=Q(
|
||||||
closed__gte=prior_release.release_date,
|
closed__gte=prior_version.release_date,
|
||||||
closed__lt=version.release_date,
|
closed__lt=release_date,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
from datetime import date
|
||||||
from itertools import pairwise
|
from itertools import pairwise
|
||||||
import djclick as click
|
import djclick as click
|
||||||
import psycopg2
|
import psycopg2
|
||||||
@@ -57,9 +58,10 @@ def create_emaildata(conn: Connection):
|
|||||||
|
|
||||||
versions = Version.objects.minor_versions().order_by("version_array")
|
versions = Version.objects.minor_versions().order_by("version_array")
|
||||||
columns = ["email", "name", "count"]
|
columns = ["email", "name", "count"]
|
||||||
|
versions = list(versions) + [Version.objects.get(name="master")]
|
||||||
for a, b in pairwise(versions):
|
for a, b in pairwise(versions):
|
||||||
start = a.release_date
|
start = a.release_date
|
||||||
end = b.release_date
|
end = b.release_date or date.today()
|
||||||
if not (start and end):
|
if not (start and end):
|
||||||
raise ValueError("All x.x.0 versions must have a release date.")
|
raise ValueError("All x.x.0 versions must have a release date.")
|
||||||
with conn.cursor(name=f"emaildata_sync_{b.name}") as cursor:
|
with conn.cursor(name=f"emaildata_sync_{b.name}") as cursor:
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import base64
|
|||||||
import io
|
import io
|
||||||
import logging
|
import logging
|
||||||
import random
|
import random
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta, date
|
||||||
|
|
||||||
import psycopg2
|
import psycopg2
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
@@ -21,7 +21,9 @@ from versions.models import Version
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def generate_wordcloud(version: Version) -> tuple[str | None, list]:
|
def generate_wordcloud(
|
||||||
|
version: Version, prior_version: Version
|
||||||
|
) -> tuple[str | None, list]:
|
||||||
"""Generates a wordcloud png and returns it as a base64 string and word frequencies.
|
"""Generates a wordcloud png and returns it as a base64 string and word frequencies.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@@ -42,7 +44,7 @@ def generate_wordcloud(version: Version) -> tuple[str | None, list]:
|
|||||||
font_path=font_full_path,
|
font_path=font_full_path,
|
||||||
)
|
)
|
||||||
word_frequencies = {}
|
word_frequencies = {}
|
||||||
for content in get_mail_content(version):
|
for content in get_mail_content(version, prior_version):
|
||||||
for key, val in wc.process_text(content).items():
|
for key, val in wc.process_text(content).items():
|
||||||
if len(key) < 2:
|
if len(key) < 2:
|
||||||
continue
|
continue
|
||||||
@@ -104,13 +106,7 @@ def grey_color_func(*args, **kwargs):
|
|||||||
return "hsl(0, 0%%, %d%%)" % random.randint(10, 80)
|
return "hsl(0, 0%%, %d%%)" % random.randint(10, 80)
|
||||||
|
|
||||||
|
|
||||||
def get_mail_content(version: Version):
|
def get_mail_content(version: Version, prior_version: Version):
|
||||||
prior_version = (
|
|
||||||
Version.objects.minor_versions()
|
|
||||||
.filter(version_array__lt=version.cleaned_version_parts_int)
|
|
||||||
.order_by("-release_date")
|
|
||||||
.first()
|
|
||||||
)
|
|
||||||
if not prior_version or not settings.HYPERKITTY_DATABASE_NAME:
|
if not prior_version or not settings.HYPERKITTY_DATABASE_NAME:
|
||||||
return []
|
return []
|
||||||
conn = psycopg2.connect(settings.HYPERKITTY_DATABASE_URL)
|
conn = psycopg2.connect(settings.HYPERKITTY_DATABASE_URL)
|
||||||
@@ -120,7 +116,10 @@ def get_mail_content(version: Version):
|
|||||||
SELECT content FROM hyperkitty_email
|
SELECT content FROM hyperkitty_email
|
||||||
WHERE date >= %(start)s AND date < %(end)s;
|
WHERE date >= %(start)s AND date < %(end)s;
|
||||||
""",
|
""",
|
||||||
{"start": prior_version.release_date, "end": version.release_date},
|
{
|
||||||
|
"start": prior_version.release_date,
|
||||||
|
"end": version.release_date or date.today(),
|
||||||
|
},
|
||||||
)
|
)
|
||||||
for [content] in cursor:
|
for [content] in cursor:
|
||||||
yield content
|
yield content
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ body {
|
|||||||
</style>
|
</style>
|
||||||
{% endblock css %}
|
{% endblock css %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
{% now "F j, Y" as today %}
|
||||||
{% with bg_color='' %}
|
{% with bg_color='' %}
|
||||||
<div>
|
<div>
|
||||||
<div class="pdf-page grid grid-cols-2 gap-x-4 items-center justify-items-center {{ bg_color }}" style="background-image: url('{% static 'img/release_report/bg6.png' %}')">
|
<div class="pdf-page grid grid-cols-2 gap-x-4 items-center justify-items-center {{ bg_color }}" style="background-image: url('{% static 'img/release_report/bg6.png' %}')">
|
||||||
@@ -51,12 +52,13 @@ body {
|
|||||||
class="mt-[3px]"
|
class="mt-[3px]"
|
||||||
style="width:3.3rem; margin-right:.5rem;" src="{% static 'img/Boost_Symbol_Transparent.svg' %}"
|
style="width:3.3rem; margin-right:.5rem;" src="{% static 'img/Boost_Symbol_Transparent.svg' %}"
|
||||||
>
|
>
|
||||||
<span class="font-bold">Boost</span> <span class="text-[2.75rem] self-end mb-[2px]">{{ version.display_name }}</span>
|
<span class="font-bold">Boost</span> <span class="text-[2.75rem] self-end mb-[2px]">{{ report_configuration.display_name }}</span>
|
||||||
</h1>
|
</h1>
|
||||||
<div class="flex gap-x-12 link-icons my-4 text-2xl justify-between">
|
<div class="flex gap-x-12 link-icons my-4 text-2xl justify-between">
|
||||||
{% include "includes/_social_icon_links.html" %}
|
{% include "includes/_social_icon_links.html" %}
|
||||||
</div>
|
</div>
|
||||||
<div><span class="font-bold">{{ commit_count|intcomma }}</span> commit{{ commit_count|pluralize }} up through {{ version.display_name }}</div>
|
<div class="mb-2" class="font-bold">{% firstof version.release_date today %}</div>
|
||||||
|
<div><span class="font-bold">{{ commit_count|intcomma }}</span> commit{{ commit_count|pluralize }} up through {{ report_configuration.display_name }}</div>
|
||||||
<div><span class="font-bold">{{ lines_added|intcomma }}</span> line{{ lines_added|pluralize }} added, <span class="font-bold">{{ lines_removed|intcomma }}</span> line{{ lines_removed|pluralize }} removed</div>
|
<div><span class="font-bold">{{ lines_added|intcomma }}</span> line{{ lines_added|pluralize }} added, <span class="font-bold">{{ lines_removed|intcomma }}</span> line{{ lines_removed|pluralize }} removed</div>
|
||||||
<div><span class="font-bold">{{ version_commit_count|intcomma }}</span> new commit{{ version_commit_count|pluralize }} in all library repositories</div>
|
<div><span class="font-bold">{{ version_commit_count|intcomma }}</span> new commit{{ version_commit_count|pluralize }} in all library repositories</div>
|
||||||
<div><span class="font-bold">{{ commit_contributors_release_count }}</span> commit contributors, <span class="font-bold">{{ commit_contributors_new_count }}</span> new</div>
|
<div><span class="font-bold">{{ commit_contributors_release_count }}</span> commit contributors, <span class="font-bold">{{ commit_contributors_new_count }}</span> new</div>
|
||||||
@@ -102,10 +104,10 @@ body {
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col h-full justify-between">
|
<div class="flex flex-col h-full justify-between">
|
||||||
|
|
||||||
{% if version.release_report_cover_image and version.release_report_cover_image.url %}
|
{% if report_configuration.release_report_cover_image and report_configuration.release_report_cover_image.url %}
|
||||||
<img
|
<img
|
||||||
class="max-h-[60%]"
|
class="max-h-[60%]"
|
||||||
src="{{ version.release_report_cover_image.url|strip_query_string }}"
|
src="{{ report_configuration.release_report_cover_image.url|strip_query_string }}"
|
||||||
alt="release report cover image"
|
alt="release report cover image"
|
||||||
>
|
>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -151,7 +153,7 @@ body {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if version.sponsor_message %}
|
{% if report_configuration.sponsor_message %}
|
||||||
<div id="fiscal_committee_page" class="pdf-page !p-8 {{ bg_color }} bg-gray-200 relative">
|
<div id="fiscal_committee_page" class="pdf-page !p-8 {{ bg_color }} bg-gray-200 relative">
|
||||||
<h2 class="mt-0">From the Fiscal Sponsorship Committee</h2>
|
<h2 class="mt-0">From the Fiscal Sponsorship Committee</h2>
|
||||||
<div class="flex flex-col w-full h-[85%] sponsor-message relative">
|
<div class="flex flex-col w-full h-[85%] sponsor-message relative">
|
||||||
@@ -169,7 +171,7 @@ body {
|
|||||||
d="M 28.068153,0 H 471.93339 c 17.18711,0 28.0676,10.63409 28.0674,26.12903 l -8e-4,56.64968 c 0,16.63468 -9.6004,26.12907 -28.0674,26.12907 -144.5008,3.63566 -356.11545,0.75984 -445.23187,0 -15.3824,0 -26.70095,-10.79719 -26.70074,-26.12907 l 7.8e-4,-56.64968 c 1.8e-4,-13.27403 12.64918,-26.12903 28.06737,-26.12903 z"
|
d="M 28.068153,0 H 471.93339 c 17.18711,0 28.0676,10.63409 28.0674,26.12903 l -8e-4,56.64968 c 0,16.63468 -9.6004,26.12907 -28.0674,26.12907 -144.5008,3.63566 -356.11545,0.75984 -445.23187,0 -15.3824,0 -26.70095,-10.79719 -26.70074,-26.12907 l 7.8e-4,-56.64968 c 1.8e-4,-13.27403 12.64918,-26.12903 28.06737,-26.12903 z"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
<div class="sponsor_message_copy dynamic-text p-[30px] mr-16 absolute z-20 max-h-[340px]">{{ version.sponsor_message|safe }}</div>
|
<div class="sponsor_message_copy dynamic-text p-[30px] mr-16 absolute z-20 max-h-[340px]">{{ report_configuration.sponsor_message|safe }}</div>
|
||||||
<div class="committee_members flex flex-wrap mt-2 text-sm text-center absolute" style="">
|
<div class="committee_members flex flex-wrap mt-2 text-sm text-center absolute" style="">
|
||||||
{% for user in committee_members|dictsort:"display_name" %}
|
{% for user in committee_members|dictsort:"display_name" %}
|
||||||
<figure class="w-32 m-2">
|
<figure class="w-32 m-2">
|
||||||
@@ -247,12 +249,12 @@ body {
|
|||||||
{% else %}
|
{% else %}
|
||||||
no
|
no
|
||||||
{% endif %}
|
{% endif %}
|
||||||
mailing list post{{ mailinglist_total|pluralize }} in version {{ version.display_name }} and
|
mailing list post{{ mailinglist_total|pluralize }} in version {{ report_configuration.display_name }} and
|
||||||
<span class="font-bold">{{ mailinglist_contributor_release_count }}</span>
|
<span class="font-bold">{{ mailinglist_contributor_release_count }}</span>
|
||||||
poster{{ mailinglist_contributor_release_count|pluralize }}
|
poster{{ mailinglist_contributor_release_count|pluralize }}
|
||||||
in this version. (<span class="font-bold">{{ mailinglist_contributor_new_count }}</span> New)
|
in this version. (<span class="font-bold">{{ mailinglist_contributor_new_count }}</span> New)
|
||||||
</div>
|
</div>
|
||||||
<div class="text-center my-2">Weekly mailing list posts from {{prior_version.release_date}} to {{version.release_date}} on the Boost Developers mailing list.</div>
|
<div class="text-center my-2">Weekly mailing list posts from {{prior_version.release_date}} to {% firstof version.release_date today %} on the Boost Developers mailing list.</div>
|
||||||
<div id="release_post_stats"></div>
|
<div id="release_post_stats"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -261,7 +263,7 @@ body {
|
|||||||
<div class="pdf-page flex {{ bg_color }}" style="background-image: url('{% static 'img/release_report/bg6.png' %}');">
|
<div class="pdf-page flex {{ bg_color }}" style="background-image: url('{% static 'img/release_report/bg6.png' %}');">
|
||||||
<div class="flex flex-col h-full mx-auto w-full">
|
<div class="flex flex-col h-full mx-auto w-full">
|
||||||
<h2 class="mx-auto mb-10">Mailing List New Subscribers</h2>
|
<h2 class="mx-auto mb-10">Mailing List New Subscribers</h2>
|
||||||
<div class="text-center my-2">Mailing list new subscribers from from {{prior_version.release_date}} to {{version.release_date}} on the Boost Developers mailing list.</div>
|
<div class="text-center my-2">Mailing list new subscribers from from {{prior_version.release_date}} to {% firstof version.release_date today %} on the Boost Developers mailing list.</div>
|
||||||
|
|
||||||
<div id="subscriptions_stats"></div>
|
<div id="subscriptions_stats"></div>
|
||||||
</div>
|
</div>
|
||||||
@@ -403,7 +405,7 @@ body {
|
|||||||
{{ item.version_count.commit_count|pluralize:"was,were" }}
|
{{ item.version_count.commit_count|pluralize:"was,were" }}
|
||||||
<span class="font-bold">{{ item.version_count.commit_count }}</span>
|
<span class="font-bold">{{ item.version_count.commit_count }}</span>
|
||||||
commit{{ item.version_count.commit_count|pluralize }}
|
commit{{ item.version_count.commit_count|pluralize }}
|
||||||
in release {{ version.display_name }}
|
in release {{ report_configuration.display_name }}
|
||||||
</div>
|
</div>
|
||||||
{% with insertions=item.library_version.insertions deletions=item.library_version.deletions %}
|
{% with insertions=item.library_version.insertions deletions=item.library_version.deletions %}
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -99,3 +99,9 @@ class ReviewResultAdmin(admin.ModelAdmin):
|
|||||||
|
|
||||||
def get_queryset(self, request: HttpRequest) -> QuerySet:
|
def get_queryset(self, request: HttpRequest) -> QuerySet:
|
||||||
return super().get_queryset(request).select_related("review")
|
return super().get_queryset(request).select_related("review")
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(models.ReportConfiguration)
|
||||||
|
class ReportConfigurationAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ["version"]
|
||||||
|
filter_horizontal = ["financial_committee_members"]
|
||||||
|
|||||||
58
versions/migrations/0019_reportconfiguration.py
Normal file
58
versions/migrations/0019_reportconfiguration.py
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
# Generated by Django 4.2.16 on 2025-07-03 21:09
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
("versions", "0018_version_financial_committee_members"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="ReportConfiguration",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"version",
|
||||||
|
models.CharField(
|
||||||
|
help_text="The version name this report configuration is for.",
|
||||||
|
max_length=256,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"release_report_cover_image",
|
||||||
|
models.ImageField(
|
||||||
|
blank=True, null=True, upload_to="release_report_cover/"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"sponsor_message",
|
||||||
|
models.TextField(
|
||||||
|
blank=True,
|
||||||
|
default="",
|
||||||
|
help_text='Message to show in release reports on the "Fiscal Sponsorship Committee" page.',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"financial_committee_members",
|
||||||
|
models.ManyToManyField(
|
||||||
|
blank=True,
|
||||||
|
help_text="Financial Committee members who are responsible for this release.",
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
# Generated by Django 4.2.16 on 2025-07-03 21:12
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
def copy_report_configuration(apps, schema_editor):
|
||||||
|
ReportConfiguration = apps.get_model("versions", "ReportConfiguration")
|
||||||
|
Version = apps.get_model("versions", "Version")
|
||||||
|
version_data = [ {
|
||||||
|
"name": vd.name,
|
||||||
|
"release_report_cover_image": vd.release_report_cover_image,
|
||||||
|
"sponsor_message": vd.sponsor_message,
|
||||||
|
"financial_committee_members": list(vd.financial_committee_members.values_list("id", flat=True)),
|
||||||
|
} for vd in Version.objects.all().prefetch_related("financial_committee_members")]
|
||||||
|
for vd in version_data:
|
||||||
|
configuration = ReportConfiguration.objects.create(
|
||||||
|
version=vd["name"],
|
||||||
|
release_report_cover_image=vd["release_report_cover_image"],
|
||||||
|
sponsor_message=vd["sponsor_message"],
|
||||||
|
)
|
||||||
|
configuration.financial_committee_members.set(vd["financial_committee_members"])
|
||||||
|
|
||||||
|
def drop_report_configuration(apps, schema_editor):
|
||||||
|
ReportConfiguration = apps.get_model("versions", "ReportConfiguration")
|
||||||
|
output = ReportConfiguration.objects.all().delete()
|
||||||
|
print(f"\nDeleted {output}...")
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("versions", "0019_reportconfiguration"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [migrations.RunPython(copy_report_configuration, drop_report_configuration)]
|
||||||
@@ -282,3 +282,40 @@ class ReviewResult(models.Model):
|
|||||||
)
|
)
|
||||||
sibling_results.update(is_most_recent=False)
|
sibling_results.update(is_most_recent=False)
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class ReportConfiguration(models.Model):
|
||||||
|
"""
|
||||||
|
Configuration for release reports. Used so these can be set in advance of a version
|
||||||
|
being generated and then used by that version's release report.
|
||||||
|
"""
|
||||||
|
|
||||||
|
version = models.CharField(
|
||||||
|
max_length=256,
|
||||||
|
null=False,
|
||||||
|
blank=False,
|
||||||
|
help_text="The version name for this report configuration. e.g. boost-1.75.0",
|
||||||
|
unique=True,
|
||||||
|
)
|
||||||
|
release_report_cover_image = models.ImageField(
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
upload_to="release_report_cover/",
|
||||||
|
)
|
||||||
|
sponsor_message = models.TextField(
|
||||||
|
default="",
|
||||||
|
blank=True,
|
||||||
|
help_text='Message to show in release reports on the "Fiscal Sponsorship Committee" page.', # noqa: E501
|
||||||
|
)
|
||||||
|
financial_committee_members = models.ManyToManyField(
|
||||||
|
User,
|
||||||
|
blank=True,
|
||||||
|
help_text="Financial Committee members who are responsible for this release.",
|
||||||
|
)
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def display_name(self):
|
||||||
|
return self.version.replace("boost-", "")
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.version
|
||||||
|
|||||||
Reference in New Issue
Block a user