wbreport 1.49.5__py2.py3-none-any.whl → 1.60.1__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -22,7 +22,6 @@ from reportlab.platypus import (
22
22
  Flowable,
23
23
  NextPageTemplate,
24
24
  PageTemplate,
25
- Paragraph,
26
25
  Spacer,
27
26
  Table,
28
27
  TableStyle,
@@ -33,6 +32,7 @@ from wbcore.utils.figures import (
33
32
  get_horizontal_barplot,
34
33
  get_piechart,
35
34
  )
35
+ from wbcore.utils.reportlab import FormattedParagraph as Paragraph
36
36
  from wbportfolio.models.products import InvestmentIndex
37
37
 
38
38
  from wbreport.mixins import ReportMixin
@@ -199,7 +199,7 @@ class ReportClass(FactsheetReportMixin, ReportMixin):
199
199
  return template.render(context)
200
200
 
201
201
  @classmethod
202
- def generate_file(cls, context):
202
+ def generate_file(cls, context): # noqa: C901
203
203
  debug = False
204
204
  # Product Data
205
205
  # Main Feature table as dictionary
@@ -446,7 +446,7 @@ class ReportClass(FactsheetReportMixin, ReportMixin):
446
446
  # Price Timeseries Chart
447
447
  elements.append(
448
448
  get_timeseries_chart(
449
- data=[list(zip(prices.index, prices.net_value))],
449
+ data=[list(zip(prices.index, prices.net_value, strict=False))],
450
450
  width=CONTENT_WIDTH_PAGE1_LEFT - CONTENT_OFFSET,
451
451
  height=4.34 * cm,
452
452
  color=c_product,
@@ -467,7 +467,7 @@ class ReportClass(FactsheetReportMixin, ReportMixin):
467
467
  "<strong>Top 3 Contributors</strong>",
468
468
  "<strong>Bottom 3 Contributors</strong>",
469
469
  ],
470
- data=list(zip(top_3_holdings, top_3_contributors, bottom_3_contributors)),
470
+ data=list(zip(top_3_holdings, top_3_contributors, bottom_3_contributors, strict=False)),
471
471
  width=CONTENT_WIDTH_PAGE1_LEFT,
472
472
  header_row_height=0.85 * cm,
473
473
  data_row_height=0.39 * cm,
@@ -636,7 +636,7 @@ class ReportClass(FactsheetReportMixin, ReportMixin):
636
636
 
637
637
  data = list()
638
638
  categories = list()
639
- for index, row in enumerate(df.itertuples()):
639
+ for row in df.itertuples():
640
640
  data.append(row.weighting * 100)
641
641
  categories.append(f"{row.aggregated_title} ({row.weighting * 100:.1f}%)")
642
642
 
@@ -21,7 +21,6 @@ from reportlab.platypus import (
21
21
  BaseDocTemplate,
22
22
  NextPageTemplate,
23
23
  PageTemplate,
24
- Paragraph,
25
24
  Spacer,
26
25
  Table,
27
26
  TableStyle,
@@ -32,6 +31,7 @@ from wbcore.utils.figures import (
32
31
  get_horizontal_barplot,
33
32
  get_piechart,
34
33
  )
34
+ from wbcore.utils.reportlab import FormattedParagraph as Paragraph
35
35
  from wbportfolio.models.products import InvestmentIndex
36
36
 
37
37
  from wbreport.mixins import ReportMixin
@@ -199,7 +199,7 @@ class ReportClass(FactsheetReportMixin, ReportMixin):
199
199
  return template.render(context)
200
200
 
201
201
  @classmethod
202
- def generate_file(cls, context):
202
+ def generate_file(cls, context): # noqa: C901
203
203
  debug = False
204
204
 
205
205
  main_features_dict = context["information_table"]["Main Features"]
@@ -450,7 +450,7 @@ class ReportClass(FactsheetReportMixin, ReportMixin):
450
450
  # Price Timeseries Chart
451
451
  elements.append(
452
452
  get_timeseries_chart(
453
- data=[list(zip(prices.index, prices.net_value))],
453
+ data=[list(zip(prices.index, prices.net_value, strict=False))],
454
454
  width=CONTENT_WIDTH_PAGE1_LEFT - CONTENT_OFFSET,
455
455
  height=4.34 * cm,
456
456
  color=c_product,
@@ -471,7 +471,7 @@ class ReportClass(FactsheetReportMixin, ReportMixin):
471
471
  "<strong>Top 3 Contributors</strong>",
472
472
  "<strong>Bottom 3 Contributors</strong>",
473
473
  ],
474
- data=list(zip(top_3_holdings, top_3_contributors, bottom_3_contributors)),
474
+ data=list(zip(top_3_holdings, top_3_contributors, bottom_3_contributors, strict=False)),
475
475
  width=CONTENT_WIDTH_PAGE1_LEFT,
476
476
  header_row_height=0.85 * cm,
477
477
  data_row_height=0.39 * cm,
@@ -639,7 +639,7 @@ class ReportClass(FactsheetReportMixin, ReportMixin):
639
639
 
640
640
  data = list()
641
641
  categories = list()
642
- for index, row in enumerate(df.itertuples()):
642
+ for row in df.itertuples:
643
643
  data.append(row.weighting * 100)
644
644
  categories.append(f"{row.aggregated_title} ({row.weighting * 100:.1f}%)")
645
645
 
wbreport/filters.py CHANGED
@@ -4,13 +4,22 @@ from wbreport.models import Report, ReportVersion
4
4
 
5
5
 
6
6
  class ReportFilterSet(wb_filters.FilterSet):
7
- is_active = wb_filters.BooleanFilter(default=True)
7
+ is_active = wb_filters.BooleanFilter(initial=True)
8
+
9
+ parent_report = wb_filters.ModelChoiceFilter(
10
+ label="Parent",
11
+ queryset=Report.objects.all(),
12
+ endpoint=Report.get_representation_endpoint(),
13
+ value_key=Report.get_representation_value_key(),
14
+ label_key=Report.get_representation_label_key(),
15
+ hidden=True,
16
+ )
17
+ parent_report__isnull = wb_filters.BooleanFilter(field_name="parent_report", lookup_expr="isnull", hidden=True)
8
18
 
9
19
  class Meta:
10
20
  model = Report
11
21
  fields = {
12
22
  "category": ["exact"],
13
- "parent_report": ["exact", "isnull"],
14
23
  "permission_type": ["exact"],
15
24
  "base_color": ["exact"],
16
25
  "mailing_list": ["exact"],
@@ -18,7 +27,7 @@ class ReportFilterSet(wb_filters.FilterSet):
18
27
 
19
28
 
20
29
  class ReportVersionFilterSet(wb_filters.FilterSet):
21
- disabled = wb_filters.BooleanFilter(method="boolean_is_disabled", default=False)
30
+ disabled = wb_filters.BooleanFilter(method="boolean_is_disabled", initial=False)
22
31
 
23
32
  def boolean_is_disabled(self, queryset, name, value):
24
33
  if value is True:
wbreport/mixins.py CHANGED
@@ -2,13 +2,11 @@ from datetime import date
2
2
  from io import BytesIO
3
3
  from typing import Any, Dict
4
4
 
5
- from django.contrib.auth import get_user_model
6
5
  from django.template.loader import get_template
6
+ from wbcore.contrib.authentication.models.users import User
7
7
 
8
8
  from wbreport.models import Report, ReportVersion
9
9
 
10
- User = get_user_model()
11
-
12
10
 
13
11
  class ReportMixin:
14
12
  HTML_TEMPLATE_FILE = ""
wbreport/models.py CHANGED
@@ -8,7 +8,6 @@ from typing import Any, Dict, List, Optional
8
8
  from celery import shared_task
9
9
  from colorfield.fields import ColorField
10
10
  from django.conf import settings
11
- from django.contrib.auth import get_user_model
12
11
  from django.contrib.contenttypes.fields import GenericForeignKey
13
12
  from django.contrib.contenttypes.models import ContentType
14
13
  from django.core.serializers.json import DjangoJSONEncoder
@@ -22,14 +21,14 @@ from guardian.shortcuts import get_objects_for_user
22
21
  from mptt.models import MPTTModel, TreeForeignKey
23
22
  from ordered_model.models import OrderedModel
24
23
  from rest_framework.reverse import reverse
24
+ from wbcore.contrib.authentication.models.users import User
25
25
  from wbcore.contrib.guardian.models.mixins import PermissionObjectModelMixin
26
26
  from wbcore.contrib.notifications.dispatch import send_notification
27
27
  from wbcore.contrib.notifications.utils import create_notification_type
28
28
  from wbcore.models import WBModel
29
+ from wbcore.workers import Queue
29
30
  from wbmailing.models import MailTemplate, MassMail
30
31
 
31
- User = get_user_model()
32
-
33
32
 
34
33
  class ReportAsset(models.Model):
35
34
  """
@@ -112,9 +111,9 @@ class ReportClass(WBModel):
112
111
  """
113
112
  if (len(args) == 3 and (class_path := args[2])) or (class_path := kwargs.get("class_path", None)):
114
113
  try:
115
- if ReportClassModule := getattr(importlib.import_module(class_path), "ReportClass", None):
114
+ if class_module := getattr(importlib.import_module(class_path), "ReportClass", None):
116
115
  for method in self.REPORT_CLASS_DEFAULT_METHODS:
117
- setattr(self, method, getattr(ReportClassModule, method))
116
+ setattr(self, method, getattr(class_module, method))
118
117
  except ModuleNotFoundError:
119
118
  for method in self.REPORT_CLASS_DEFAULT_METHODS:
120
119
  setattr(self, method, lambda *a, **k: None)
@@ -612,16 +611,18 @@ class ReportVersion(models.Model):
612
611
  **kwargs: Divers keyword arguments to be injected in the get_context function
613
612
  """
614
613
  if not self.lock or force_context_update:
615
- if silent:
616
- try:
617
- self.context = self.get_context(**kwargs)
618
- self.disabled = False
619
- except Exception as e:
620
- print(f"Error while updating Context for snap {self.id} {e}") # noqa: T201
621
- self.disabled = True
622
- else:
623
- self.context = self.get_context(**kwargs)
624
- self.context = self.report.report_class.serialize_context(self.context)
614
+ context = {}
615
+ try:
616
+ context = self.get_context(**kwargs)
617
+ self.disabled = False
618
+ except Exception as e:
619
+ self.comment = f"Error while updating this version context: {e}"
620
+ self.disabled = True
621
+ if not silent:
622
+ raise e
623
+ context = self.report.report_class.serialize_context(context)
624
+ if context:
625
+ self.context = context
625
626
  self.save()
626
627
 
627
628
  def generate_file(self) -> BytesIO:
@@ -674,6 +675,10 @@ class ReportVersion(models.Model):
674
675
  mass_mail.send()
675
676
  mass_mail.save()
676
677
 
678
+ @classmethod
679
+ def get_endpoint_basename(cls) -> str:
680
+ return "wbreport:reportversion"
681
+
677
682
  @classmethod
678
683
  def get_representation_endpoint(cls) -> str:
679
684
  return "wbreport:reportversionrepresentation-list"
@@ -696,7 +701,7 @@ def generate_version_context_if_null(sender, instance, created, raw, **kwargs):
696
701
  update_context_as_task.apply_async((instance.id,), countdown=30)
697
702
 
698
703
 
699
- @shared_task()
704
+ @shared_task(queue=Queue.DEFAULT.value)
700
705
  def generate_next_reports_as_task(report_id, parameters=None, user=None, comment=None, max_depth_only=False):
701
706
  """
702
707
  Trigger the Report generate_next_reports as a task
@@ -712,7 +717,7 @@ def generate_next_reports_as_task(report_id, parameters=None, user=None, comment
712
717
  )
713
718
 
714
719
 
715
- @shared_task()
720
+ @shared_task(queue=Queue.DEFAULT.value)
716
721
  def bulk_create_child_reports_as_task(report_id, start_parameters, end_parameters, user=None):
717
722
  """
718
723
  Trigger the Report generate_next_reports as a task
@@ -728,16 +733,17 @@ def bulk_create_child_reports_as_task(report_id, start_parameters, end_parameter
728
733
  )
729
734
 
730
735
 
731
- @shared_task()
732
- def update_context_as_task(report_version_id, user=None, comment=None):
736
+ @shared_task(queue=Queue.DEFAULT.value)
737
+ def update_context_as_task(
738
+ report_version_id: int, user: User | None = None, comment: str | None = None, force_context_update: bool = False
739
+ ):
733
740
  """
734
741
  Trigger the Report Version update_report_context as a task
735
742
  """
736
743
  version = ReportVersion.objects.get(id=report_version_id)
737
744
  if comment:
738
745
  version.comment = comment
739
- version.save()
740
- version.update_context()
746
+ version.update_context(force_context_update=force_context_update)
741
747
  if user:
742
748
  send_notification(
743
749
  code="wbreport.report.background_task",
@@ -747,7 +753,7 @@ def update_context_as_task(report_version_id, user=None, comment=None):
747
753
  )
748
754
 
749
755
 
750
- @shared_task()
756
+ @shared_task(queue=Queue.DEFAULT.value)
751
757
  def update_version_context_as_task(report_id, parameters=None, user=None):
752
758
  """
753
759
  Trigger the Report Version update_report_context as a task
@@ -756,7 +762,7 @@ def update_version_context_as_task(report_id, parameters=None, user=None):
756
762
  models.Q(is_active=True) & (models.Q(id=report_id) | models.Q(parent_report=report_id))
757
763
  ).distinct():
758
764
  print( # noqa: T201
759
- f'Updating context for report {str(report)} and version parameters {parameters if parameters else "{all}"}'
765
+ f"Updating context for report {str(report)} and version parameters {parameters if parameters else '{all}'}"
760
766
  )
761
767
  versions = report.versions.filter(disabled=False)
762
768
  if parameters:
@@ -772,7 +778,7 @@ def update_version_context_as_task(report_id, parameters=None, user=None):
772
778
  )
773
779
 
774
780
 
775
- @shared_task()
781
+ @shared_task(queue=Queue.HIGH_PRIORITY.value)
776
782
  def set_primary_report_version_as_task(report_id, parameters=None, user=None):
777
783
  """
778
784
  Trigger the Report set_primary_versions as a task
@@ -4,7 +4,7 @@ from reportlab.lib.colors import transparent
4
4
 
5
5
 
6
6
  class CustomLegend(Legend):
7
- def _defaultSwatch(self, x, thisy, dx, dy, fillColor, strokeWidth, strokeColor):
7
+ def _defaultSwatch(self, x, thisy, dx, dy, fillColor, strokeWidth, strokeColor): # noqa: N803, N802
8
8
  return Circle(
9
9
  x,
10
10
  thisy + dx / 2,
@@ -2,6 +2,7 @@ from reportlab.graphics.charts.piecharts import Pie
2
2
  from reportlab.graphics.shapes import Drawing
3
3
  from reportlab.lib import colors
4
4
  from reportlab.lib.units import cm
5
+
5
6
  from wbreport.pdf.charts.legend import CustomLegend
6
7
 
7
8
 
@@ -17,7 +17,7 @@ class LogScaleTimeSeriesPlot(LinePlot):
17
17
  super().__init__()
18
18
 
19
19
  class CustomYAxis(LogYValueAxis):
20
- def _calcTickPositions(self):
20
+ def _calcTickPositions(self): # noqa: N802
21
21
  return self._calcStepAndTickPositions()[1]
22
22
 
23
23
  self.xValueAxis = NormalDateXValueAxis()
@@ -1,7 +1,8 @@
1
1
  from reportlab.lib.colors import HexColor, black, white
2
2
  from reportlab.lib.units import cm
3
3
  from reportlab.pdfbase.pdfmetrics import stringWidth
4
- from reportlab.platypus import Flowable, Paragraph
4
+ from reportlab.platypus import Flowable
5
+ from wbcore.utils.reportlab import FormattedParagraph as Paragraph
5
6
 
6
7
 
7
8
  class RiskScale(Flowable):
@@ -2,8 +2,9 @@ from reportlab.graphics import renderPDF
2
2
  from reportlab.lib.colors import black
3
3
  from reportlab.lib.units import cm
4
4
  from reportlab.pdfbase.pdfmetrics import stringWidth
5
- from reportlab.platypus import Flowable, Image, Paragraph, Table, TableStyle
5
+ from reportlab.platypus import Flowable, Image, Table, TableStyle
6
6
  from svglib.svglib import svg2rlg
7
+ from wbcore.utils.reportlab import FormattedParagraph as Paragraph
7
8
 
8
9
 
9
10
  class TextBox(Flowable):
@@ -22,7 +22,6 @@ from reportlab.platypus import (
22
22
  Image,
23
23
  NextPageTemplate,
24
24
  PageTemplate,
25
- Paragraph,
26
25
  Spacer,
27
26
  Table,
28
27
  TableStyle,
@@ -30,6 +29,8 @@ from reportlab.platypus import (
30
29
  from reportlab.platypus.flowables import KeepTogether, TopPadder
31
30
  from reportlab.platypus.frames import Frame
32
31
  from svglib.svglib import svg2rlg
32
+ from wbcore.utils.reportlab import FormattedParagraph as Paragraph
33
+
33
34
  from wbreport.models import ReportAsset
34
35
  from wbreport.pdf.charts.pie import (
35
36
  get_pie_chart_horizontal,
@@ -43,7 +44,7 @@ from wbreport.pdf.tables.aggregated_tables import get_simple_aggregated_table
43
44
  from wbreport.pdf.tables.data_tables import get_simple_data_table
44
45
 
45
46
 
46
- def generate_report(context):
47
+ def generate_report(context): # noqa: C901
47
48
  debug = False
48
49
  ### Product Data ###
49
50
  # Main Feature table as dictionary
@@ -121,7 +122,7 @@ def generate_report(context):
121
122
  s_base_small_justified = ParagraphStyle(
122
123
  name="s_base_small_justified", parent=s_base, fontSize=6.5, leading=7, alignment=TA_JUSTIFY
123
124
  )
124
- s_base_small_justified_indent = ParagraphStyle(
125
+ s_base_small_justified_indent = ParagraphStyle( # noqa: F841
125
126
  name="s_base_small_justified_indent", parent=s_base_small_justified, leftIndent=CONTENT_OFFSET
126
127
  )
127
128
  s_base_indent = ParagraphStyle(name="s_description", parent=s_base, spaceBefore=8, leftIndent=CONTENT_OFFSET)
@@ -135,7 +136,7 @@ def generate_report(context):
135
136
  s_table_medium_leading = ParagraphStyle(name="s_table_medium_leading", parent=s_table_medium, leading=13.9)
136
137
  s_table_large = ParagraphStyle(name="s_table_large", parent=s_table_medium, fontSize=11, leading=11)
137
138
  s_table_large_center = ParagraphStyle(name="s_table_large", parent=s_table_large, alignment=TA_CENTER)
138
- s_table_large_center_padding = ParagraphStyle(
139
+ s_table_large_center_padding = ParagraphStyle( # noqa: F841
139
140
  name="s_table_large", parent=s_table_large_center, spaceBefore=20, spaceAfter=20
140
141
  )
141
142
  s_table_center = ParagraphStyle(
@@ -278,7 +279,7 @@ def generate_report(context):
278
279
  )
279
280
  return title_table
280
281
 
281
- def impress(l):
282
+ def impress(elements):
282
283
  style = s_table_headline_2
283
284
  table_data = [
284
285
  [
@@ -306,7 +307,7 @@ def generate_report(context):
306
307
  ]
307
308
  )
308
309
  )
309
- l.append(KeepTogether(TopPadder(t)))
310
+ elements.append(KeepTogether(TopPadder(t)))
310
311
 
311
312
  # Description
312
313
  elements.append(generate_title(general_data["title"]))
@@ -334,7 +335,7 @@ def generate_report(context):
334
335
  # Price Timeseries Chart
335
336
  elements.append(
336
337
  get_timeseries_chart(
337
- data=[list(zip(prices.index, prices.net_value))],
338
+ data=[list(zip(prices.index, prices.net_value, strict=False))],
338
339
  width=CONTENT_WIDTH_PAGE1_LEFT - CONTENT_OFFSET,
339
340
  height=4.34 * cm,
340
341
  color=c_product,
@@ -355,7 +356,7 @@ def generate_report(context):
355
356
  "<strong>Top 3 Contributors</strong>",
356
357
  "<strong>Bottom 3 Contributors</strong>",
357
358
  ],
358
- data=list(zip(top_3_holdings, top_3_contributors, bottom_3_contributors)),
359
+ data=list(zip(top_3_holdings, top_3_contributors, bottom_3_contributors, strict=False)),
359
360
  width=CONTENT_WIDTH_PAGE1_LEFT,
360
361
  header_row_height=0.85 * cm,
361
362
  data_row_height=0.39 * cm,
@@ -557,7 +558,7 @@ def generate_report(context):
557
558
 
558
559
  data = list()
559
560
  categories = list()
560
- for index, row in enumerate(df.itertuples()):
561
+ for row in df.itertuples():
561
562
  data.append(row.weighting * 100)
562
563
  categories.append(f"{row.aggregated_title} ({row.weighting*100:.1f}%)")
563
564
 
@@ -834,7 +835,7 @@ def generate_report(context):
834
835
 
835
836
  last_row_height = right_height - liquid_height - 0.85 * cm
836
837
 
837
- last_height = min(
838
+ last_height = min( # noqa: F841
838
839
  max(industry_height - liquid_height + 6, 3.564 * cm), max_available_height - liquid_height
839
840
  ) # 6 because of the drawn string
840
841
 
@@ -21,7 +21,6 @@ from reportlab.platypus import (
21
21
  BaseDocTemplate,
22
22
  Image,
23
23
  PageTemplate,
24
- Paragraph,
25
24
  Spacer,
26
25
  Table,
27
26
  TableStyle,
@@ -29,6 +28,8 @@ from reportlab.platypus import (
29
28
  from reportlab.platypus.flowables import KeepTogether, TopPadder
30
29
  from reportlab.platypus.frames import Frame
31
30
  from svglib.svglib import svg2rlg
31
+ from wbcore.utils.reportlab import FormattedParagraph as Paragraph
32
+
32
33
  from wbreport.models import ReportAsset
33
34
  from wbreport.pdf.charts.pie import (
34
35
  get_pie_chart_horizontal,
@@ -44,12 +45,12 @@ from wbreport.pdf.tables.aggregated_tables import (
44
45
  from wbreport.pdf.tables.data_tables import get_simple_data_table
45
46
 
46
47
 
47
- def generate_report(context):
48
+ def generate_report(context): # noqa: C901
48
49
  debug = False
49
50
 
50
51
  main_features_dict_tmp = context["information_table"]
51
52
  main_features_dict = {}
52
- for k, v in main_features_dict_tmp.items():
53
+ for v in main_features_dict_tmp.values():
53
54
  main_features_dict.update(v)
54
55
  # Monthly returns table as dataframe, None is no value
55
56
  monthly_returns = context["monthly_returns"]
@@ -129,7 +130,7 @@ def generate_report(context):
129
130
  s_base_small_justified = ParagraphStyle(
130
131
  name="s_base_small_justified", parent=s_base, fontSize=6.5, leading=7, alignment=TA_JUSTIFY
131
132
  )
132
- s_base_small_justified_indent = ParagraphStyle(
133
+ s_base_small_justified_indent = ParagraphStyle( # noqa: F841
133
134
  name="s_base_small_justified_indent", parent=s_base_small_justified, leftIndent=CONTENT_OFFSET
134
135
  )
135
136
  s_base_indent = ParagraphStyle(name="s_description", parent=s_base, spaceBefore=8, leftIndent=CONTENT_OFFSET)
@@ -143,7 +144,7 @@ def generate_report(context):
143
144
  s_table_medium_leading = ParagraphStyle(name="s_table_medium_leading", parent=s_table_medium, leading=13.9)
144
145
  s_table_large = ParagraphStyle(name="s_table_large", parent=s_table_medium, fontSize=11, leading=11)
145
146
  s_table_large_center = ParagraphStyle(name="s_table_large", parent=s_table_large, alignment=TA_CENTER)
146
- s_table_large_center_padding = ParagraphStyle(
147
+ s_table_large_center_padding = ParagraphStyle( # noqa: F841
147
148
  name="s_table_large", parent=s_table_large_center, spaceBefore=20, spaceAfter=20
148
149
  )
149
150
  s_table_center = ParagraphStyle(
@@ -284,7 +285,7 @@ def generate_report(context):
284
285
  )
285
286
  return title_table
286
287
 
287
- def impress(l):
288
+ def impress(elements):
288
289
  style = s_table_headline_2
289
290
  table_data = [
290
291
  [
@@ -312,7 +313,7 @@ def generate_report(context):
312
313
  ]
313
314
  )
314
315
  )
315
- l.append(KeepTogether(TopPadder(t)))
316
+ elements.append(KeepTogether(TopPadder(t)))
316
317
 
317
318
  # Description
318
319
  elements.append(generate_title(general_data["title"]))
@@ -347,7 +348,7 @@ def generate_report(context):
347
348
  # Price Timeseries Chart
348
349
  elements.append(
349
350
  get_timeseries_chart(
350
- data=[list(zip(prices.index, prices.net_value))],
351
+ data=[list(zip(prices.index, prices.net_value, strict=False))],
351
352
  width=CONTENT_WIDTH_PAGE1_LEFT - CONTENT_OFFSET,
352
353
  height=4.34 * cm,
353
354
  color=c_product,
@@ -567,7 +568,7 @@ def generate_report(context):
567
568
 
568
569
  data = list()
569
570
  categories = list()
570
- for index, row in enumerate(df.itertuples()):
571
+ for row in df.itertuples():
571
572
  data.append(row.weighting * 100)
572
573
  categories.append(f"{row.aggregated_title} ({row.weighting*100:.1f}%)")
573
574
 
@@ -22,7 +22,6 @@ from reportlab.platypus import (
22
22
  Image,
23
23
  NextPageTemplate,
24
24
  PageTemplate,
25
- Paragraph,
26
25
  Spacer,
27
26
  Table,
28
27
  TableStyle,
@@ -30,6 +29,8 @@ from reportlab.platypus import (
30
29
  from reportlab.platypus.flowables import KeepTogether, TopPadder
31
30
  from reportlab.platypus.frames import Frame
32
31
  from svglib.svglib import svg2rlg
32
+ from wbcore.utils.reportlab import FormattedParagraph as Paragraph
33
+
33
34
  from wbreport.models import ReportAsset
34
35
  from wbreport.pdf.charts.pie import (
35
36
  get_pie_chart_horizontal,
@@ -42,7 +43,7 @@ from wbreport.pdf.tables.aggregated_tables import get_simple_aggregated_table
42
43
  from wbreport.pdf.tables.data_tables import get_simple_data_table
43
44
 
44
45
 
45
- def generate_report(context):
46
+ def generate_report(context): # noqa: C901
46
47
  debug = False
47
48
  ### Product Data ###
48
49
  # Main Feature table as dictionary
@@ -120,7 +121,7 @@ def generate_report(context):
120
121
  s_base_small_justified = ParagraphStyle(
121
122
  name="s_base_small_justified", parent=s_base, fontSize=6.5, leading=7, alignment=TA_JUSTIFY
122
123
  )
123
- s_base_small_justified_indent = ParagraphStyle(
124
+ s_base_small_justified_indent = ParagraphStyle( # noqa: F841
124
125
  name="s_base_small_justified_indent", parent=s_base_small_justified, leftIndent=CONTENT_OFFSET
125
126
  )
126
127
  s_base_indent = ParagraphStyle(name="s_description", parent=s_base, spaceBefore=8, leftIndent=CONTENT_OFFSET)
@@ -134,7 +135,7 @@ def generate_report(context):
134
135
  s_table_medium_leading = ParagraphStyle(name="s_table_medium_leading", parent=s_table_medium, leading=13.9)
135
136
  s_table_large = ParagraphStyle(name="s_table_large", parent=s_table_medium, fontSize=11, leading=11)
136
137
  s_table_large_center = ParagraphStyle(name="s_table_large", parent=s_table_large, alignment=TA_CENTER)
137
- s_table_large_center_padding = ParagraphStyle(
138
+ s_table_large_center_padding = ParagraphStyle( # noqa: F841
138
139
  name="s_table_large", parent=s_table_large_center, spaceBefore=20, spaceAfter=20
139
140
  )
140
141
  s_table_center = ParagraphStyle(
@@ -277,7 +278,7 @@ def generate_report(context):
277
278
  )
278
279
  return title_table
279
280
 
280
- def impress(l):
281
+ def impress(elements):
281
282
  style = s_table_headline_2
282
283
  table_data = [
283
284
  [
@@ -305,7 +306,7 @@ def generate_report(context):
305
306
  ]
306
307
  )
307
308
  )
308
- l.append(KeepTogether(TopPadder(t)))
309
+ elements.append(KeepTogether(TopPadder(t)))
309
310
 
310
311
  ############################
311
312
  ## Page 1: BEGINING
@@ -337,7 +338,7 @@ def generate_report(context):
337
338
  # Price Timeseries Chart
338
339
  elements.append(
339
340
  get_timeseries_chart(
340
- data=[list(zip(prices.index, prices.net_value))],
341
+ data=[list(zip(prices.index, prices.net_value, strict=False))],
341
342
  width=CONTENT_WIDTH_PAGE1_LEFT - CONTENT_OFFSET,
342
343
  height=4.34 * cm,
343
344
  color=c_product,
@@ -358,7 +359,7 @@ def generate_report(context):
358
359
  "<strong>Top 3 Contributors</strong>",
359
360
  "<strong>Bottom 3 Contributors</strong>",
360
361
  ],
361
- data=list(zip(top_3_holdings, top_3_contributors, bottom_3_contributors)),
362
+ data=list(zip(top_3_holdings, top_3_contributors, bottom_3_contributors, strict=False)),
362
363
  width=CONTENT_WIDTH_PAGE1_LEFT,
363
364
  header_row_height=0.85 * cm,
364
365
  data_row_height=0.39 * cm,
@@ -559,7 +560,7 @@ def generate_report(context):
559
560
 
560
561
  data = list()
561
562
  categories = list()
562
- for index, row in enumerate(df.itertuples()):
563
+ for row in df.itertuples():
563
564
  data.append(row.weighting * 100)
564
565
  categories.append(f"{row.aggregated_title} ({row.weighting*100:.1f}%)")
565
566
 
@@ -21,7 +21,6 @@ from reportlab.platypus import (
21
21
  Image,
22
22
  NextPageTemplate,
23
23
  PageTemplate,
24
- Paragraph,
25
24
  Spacer,
26
25
  Table,
27
26
  TableStyle,
@@ -29,6 +28,8 @@ from reportlab.platypus import (
29
28
  from reportlab.platypus.flowables import KeepTogether, TopPadder
30
29
  from reportlab.platypus.frames import Frame
31
30
  from svglib.svglib import svg2rlg
31
+ from wbcore.utils.reportlab import FormattedParagraph as Paragraph
32
+
32
33
  from wbreport.models import ReportAsset
33
34
  from wbreport.pdf.charts.pie import (
34
35
  get_pie_chart_horizontal,
@@ -42,7 +43,7 @@ from wbreport.pdf.tables.aggregated_tables import get_simple_aggregated_table
42
43
  from wbreport.pdf.tables.data_tables import get_simple_data_table
43
44
 
44
45
 
45
- def generate_report(context):
46
+ def generate_report(context): # noqa: C901
46
47
  debug = False
47
48
 
48
49
  main_features_dict = context["information_table"]["Main Features"]
@@ -133,7 +134,7 @@ def generate_report(context):
133
134
  s_table_medium = ParagraphStyle(name="s_table_medium", parent=s_table_base, fontSize=9, leading=8)
134
135
  s_table_medium_leading = ParagraphStyle(name="s_table_medium_leading", parent=s_table_medium, leading=13.9)
135
136
  s_table_large = ParagraphStyle(name="s_table_large", parent=s_table_medium, fontSize=11, leading=11)
136
- s_table_large_center = ParagraphStyle(name="s_table_large", parent=s_table_large, alignment=TA_CENTER)
137
+ s_table_large_center = ParagraphStyle(name="s_table_large", parent=s_table_large, alignment=TA_CENTER) # noqa: F841
137
138
  s_table_center = ParagraphStyle(
138
139
  name="s_table_center",
139
140
  parent=s_table_base,
@@ -330,7 +331,7 @@ def generate_report(context):
330
331
  # Price Timeseries Chart
331
332
  elements.append(
332
333
  get_timeseries_chart(
333
- data=[list(zip(prices.index, prices.net_value))],
334
+ data=[list(zip(prices.index, prices.net_value, strict=False))],
334
335
  width=CONTENT_WIDTH_PAGE1_LEFT - CONTENT_OFFSET,
335
336
  height=4.34 * cm,
336
337
  color=c_product,
@@ -351,7 +352,7 @@ def generate_report(context):
351
352
  "<strong>Top 3 Contributors</strong>",
352
353
  "<strong>Bottom 3 Contributors</strong>",
353
354
  ],
354
- data=list(zip(top_3_holdings, top_3_contributors, bottom_3_contributors)),
355
+ data=list(zip(top_3_holdings, top_3_contributors, bottom_3_contributors, strict=False)),
355
356
  width=CONTENT_WIDTH_PAGE1_LEFT,
356
357
  header_row_height=0.85 * cm,
357
358
  data_row_height=0.39 * cm,
@@ -553,7 +554,7 @@ def generate_report(context):
553
554
 
554
555
  data = list()
555
556
  categories = list()
556
- for index, row in enumerate(df.itertuples()):
557
+ for row in df.itertuples():
557
558
  data.append(row.weighting * 100)
558
559
  categories.append(f"{row.aggregated_title} ({row.weighting*100:.1f}%)")
559
560
 
@@ -2,7 +2,8 @@ import numpy as np
2
2
  from reportlab.lib.colors import grey
3
3
  from reportlab.lib.enums import TA_CENTER
4
4
  from reportlab.lib.styles import ParagraphStyle
5
- from reportlab.platypus import Paragraph, Spacer, Table, TableStyle
5
+ from reportlab.platypus import Spacer, Table, TableStyle
6
+ from wbcore.utils.reportlab import FormattedParagraph as Paragraph
6
7
 
7
8
 
8
9
  def get_simple_aggregated_table(
@@ -37,7 +38,7 @@ def get_simple_aggregated_table(
37
38
 
38
39
  table_row.append(Paragraph(str(year), style=row_style))
39
40
 
40
- for month, element in row.items():
41
+ for _, element in row.items():
41
42
  if element.get("performance", None) is None:
42
43
  table_row.append(Spacer(width=0, height=0))
43
44
  else:
@@ -110,7 +111,7 @@ def get_fund_table(
110
111
  table_data.append(table_row)
111
112
 
112
113
  # Generate table
113
- for index, row in df.iterrows():
114
+ for _, row in df.iterrows():
114
115
  table_row = list()
115
116
 
116
117
  for value in row:
@@ -1,5 +1,6 @@
1
1
  from reportlab.lib.units import cm
2
- from reportlab.platypus import Paragraph, Spacer, Table, TableStyle
2
+ from reportlab.platypus import Spacer, Table, TableStyle
3
+ from wbcore.utils.reportlab import FormattedParagraph as Paragraph
3
4
 
4
5
 
5
6
  def get_simple_data_table(
wbreport/serializers.py CHANGED
@@ -1,10 +1,10 @@
1
1
  from rest_framework.reverse import reverse
2
2
  from wbcore import serializers as wb_serializers
3
- from wbcore.content_type.serializers import (
3
+ from wbcore.contrib.authentication.authentication import inject_short_lived_token
4
+ from wbcore.contrib.content_type.serializers import (
4
5
  ContentTypeRepresentationSerializer,
5
6
  DynamicObjectIDRepresentationSerializer,
6
7
  )
7
- from wbcore.contrib.authentication.authentication import inject_short_lived_token
8
8
  from wbmailing.serializers import MailingListRepresentationSerializer
9
9
 
10
10
  from .models import Report, ReportCategory, ReportClass, ReportVersion
@@ -48,6 +48,7 @@ class ReportCategoryModelSerializer(wb_serializers.ModelSerializer):
48
48
  class ReportVersionModelSerializer(wb_serializers.ModelSerializer):
49
49
  _report = ReportRepresentationSerializer(source="report")
50
50
  parameters = wb_serializers.JSONTableField()
51
+ parameters_repr = wb_serializers.CharField(read_only=True)
51
52
 
52
53
  @wb_serializers.register_resource()
53
54
  def version_resources(self, instance, request, user):
@@ -65,8 +66,6 @@ class ReportVersionModelSerializer(wb_serializers.ModelSerializer):
65
66
  res["send_email"] = reverse("report:reportversion-sendemail", args=[instance.id], request=request)
66
67
  return res
67
68
 
68
- from wbcore.contrib.authentication.authentication import TokenAuthentication
69
-
70
69
  @wb_serializers.register_resource()
71
70
  @inject_short_lived_token(view_name="public_report:report_version")
72
71
  def public_html_resources(self, instance, request, user):
@@ -86,8 +85,10 @@ class ReportVersionModelSerializer(wb_serializers.ModelSerializer):
86
85
  "update_date",
87
86
  "is_primary",
88
87
  "disabled",
88
+ "lock",
89
89
  "lookup",
90
90
  "parameters",
91
+ "parameters_repr",
91
92
  "comment",
92
93
  "report",
93
94
  "_report",
@@ -114,7 +115,7 @@ class ReportModelSerializer(wb_serializers.ModelSerializer):
114
115
  res = {}
115
116
  if instance.is_accessible(user):
116
117
  res["versions"] = reverse("wbreport:report-version-list", args=[instance.id], request=request)
117
- res["reports"] = f'{reverse("wbreport:report-list", args=[], request=request)}?parent_report={instance.id}'
118
+ res["reports"] = f"{reverse('wbreport:report-list', args=[], request=request)}?parent_report={instance.id}"
118
119
  if instance.child_reports.exists():
119
120
  res["bundle_versions"] = reverse("wbreport:report-bundletreport", args=[instance.id], request=request)
120
121
 
wbreport/tasks.py CHANGED
@@ -8,11 +8,12 @@ from django.template.loader import get_template
8
8
  from slugify import slugify
9
9
  from wbcore.contrib.authentication.models import User
10
10
  from wbcore.utils.html import convert_html2text
11
+ from wbcore.workers import Queue
11
12
 
12
13
  from wbreport.models import Report, ReportVersion
13
14
 
14
15
 
15
- @shared_task()
16
+ @shared_task(queue=Queue.BACKGROUND.value)
16
17
  def generate_and_send_current_report_file(user_id, parent_report_id, parameters=None):
17
18
  zip_buffer = BytesIO()
18
19
  parent_report = Report.objects.get(id=parent_report_id)
@@ -4,6 +4,7 @@ import pytest
4
4
  from django.test import override_settings
5
5
  from faker import Faker
6
6
  from wbmailing.models import MassMail
7
+
7
8
  from wbreport.models import ReportVersion
8
9
 
9
10
  fake = Faker()
@@ -2,6 +2,7 @@ import pytest
2
2
  from django.core import mail
3
3
  from django.test import override_settings
4
4
  from wbcore.contrib.authentication.factories import SuperUserFactory
5
+
5
6
  from wbreport.tasks import generate_and_send_current_report_file
6
7
 
7
8
 
@@ -14,6 +14,19 @@ class CommentSerializer(wb_serializers.Serializer):
14
14
  comment = wb_serializers.TextField(default="")
15
15
 
16
16
 
17
+ class GenerateNextReportSerializer(CommentSerializer):
18
+ max_depth_only = wb_serializers.BooleanField(default=False)
19
+ start_date = wb_serializers.DateField()
20
+ end_date = wb_serializers.DateField()
21
+
22
+
23
+ class UpdateContextSerializer(CommentSerializer):
24
+ override_lock = wb_serializers.BooleanField(
25
+ default=False,
26
+ help_text="Enable this option to force context updates even when the report version is locked. (Warning: this operation may overwrite existing data.)",
27
+ )
28
+
29
+
17
30
  class ReportButtonConfig(ButtonViewConfig):
18
31
  def get_custom_list_instance_buttons(self):
19
32
  return {
@@ -46,6 +59,9 @@ class ReportButtonConfig(ButtonViewConfig):
46
59
  class ParametersSerializer(wb_serializers.Serializer):
47
60
  parameters = wb_serializers.JSONTableField(default=default_parameters, label="Version Parameters")
48
61
 
62
+ class ContextUpdateSerializer(UpdateContextSerializer, ParametersSerializer):
63
+ pass
64
+
49
65
  class StartEndParametersSerializer(wb_serializers.Serializer):
50
66
  start_parameters = wb_serializers.JSONTableField(
51
67
  default=earliest_parent_parameters, label="Start Parameters"
@@ -53,11 +69,6 @@ class ReportButtonConfig(ButtonViewConfig):
53
69
  end_parameters = wb_serializers.JSONTableField(default=latest_parent_paremeters, label="End Parameters")
54
70
  comment = wb_serializers.TextField(default="")
55
71
 
56
- class GenerateNextReportSerializer(CommentSerializer):
57
- max_depth_only = wb_serializers.BooleanField(default=False)
58
- start_date = wb_serializers.DateField()
59
- end_date = wb_serializers.DateField()
60
-
61
72
  return self.get_custom_list_instance_buttons() | {
62
73
  bt.DropDownButton(
63
74
  label="Utility",
@@ -76,7 +87,9 @@ class ReportButtonConfig(ButtonViewConfig):
76
87
  serializer=GenerateNextReportSerializer,
77
88
  action_label="Generate Next Report",
78
89
  title="Generate Report to Next Iteration",
79
- instance_display=create_simple_display([["start_date"], ["end_date"], ["max_depth_only"]]),
90
+ instance_display=create_simple_display(
91
+ [["start_date", "end_date"], ["max_depth_only", "max_depth_only"]]
92
+ ),
80
93
  ),
81
94
  bt.ActionButton(
82
95
  method=RequestType.PATCH,
@@ -84,15 +97,14 @@ class ReportButtonConfig(ButtonViewConfig):
84
97
  key="update_versions_context",
85
98
  label="Regenerate context",
86
99
  icon=WBIcon.REGENERATE.icon,
87
- serializer=ParametersSerializer,
100
+ serializer=ContextUpdateSerializer,
88
101
  description_fields="""
89
102
  <p> Do you want to regenerate this report's versions?</p>
90
- <p> If parameters is unset, it will recompute all report's versions</p>
91
103
  <p> Note: This action takes few minute?</p>
92
104
  """,
93
105
  action_label="Regenerate versions context",
94
106
  title="Regenerate versions context",
95
- instance_display=create_simple_display([["all_versions"], ["parameters"]]),
107
+ instance_display=create_simple_display([["comment"], ["parameters"], ["override_lock"]]),
96
108
  ),
97
109
  bt.ActionButton(
98
110
  method=RequestType.PATCH,
@@ -164,14 +176,15 @@ class ReportVersionButtonConfig(ButtonViewConfig):
164
176
  icon=WBIcon.SAVE.icon,
165
177
  ),
166
178
  bt.ActionButton(
167
- method=RequestType.GET,
179
+ method=RequestType.PATCH,
168
180
  identifiers=("wbreport:reportversion",),
169
181
  key="update_context",
170
182
  label="Update Context",
171
183
  description_fields="""
172
184
  <p>Update and actualize context</p>
173
185
  """,
174
- serializer=CommentSerializer,
186
+ instance_display=create_simple_display([["comment"], ["override_lock"]]),
187
+ serializer=UpdateContextSerializer,
175
188
  icon=WBIcon.REGENERATE.icon,
176
189
  action_label="Update Context",
177
190
  title="Update Context",
@@ -1,6 +1,7 @@
1
1
  from typing import Optional
2
2
 
3
3
  from rest_framework.reverse import reverse
4
+ from wbcore.enums import Unit
4
5
  from wbcore.metadata.configs import display as dp
5
6
  from wbcore.metadata.configs.display.instance_display import Display
6
7
  from wbcore.metadata.configs.display.instance_display.shortcuts import (
@@ -39,13 +40,12 @@ class ReportDisplayConfig(DisplayViewConfig):
39
40
  ],
40
41
  tree=True,
41
42
  # tree_group_lookup="id_repr",
42
- tree_group_pinned="left",
43
43
  tree_group_field="title",
44
- tree_group_label="Title",
45
44
  tree_group_level_options=[
46
45
  dp.TreeGroupLevelOption(
47
46
  filter_key="parent_report",
48
47
  filter_depth=1,
48
+ filter_blacklist=["parent_report__isnull"],
49
49
  list_endpoint=reverse(
50
50
  "wbreport:report-list",
51
51
  args=[],
@@ -76,16 +76,25 @@ class ReportDisplayConfig(DisplayViewConfig):
76
76
  class ReportVersionDisplayConfig(DisplayViewConfig):
77
77
  def get_list_display(self) -> Optional[dp.ListDisplay]:
78
78
  return dp.ListDisplay(
79
- fields=(
79
+ fields=[
80
80
  dp.Field(key="uuid", label="UUID"),
81
81
  dp.Field(key="lookup", label="Lookup"),
82
82
  dp.Field(key="title", label="Title"),
83
83
  dp.Field(key="version_date", label="Date"),
84
84
  dp.Field(key="report", label="Report"),
85
- dp.Field(key="is_primary", label="Is Primary"),
86
- dp.Field(key="disabled", label="Disabled"),
87
- dp.Field(key="parameters", label="Parameters"),
88
- )
85
+ dp.Field(key="comment", label="Comment"),
86
+ dp.Field(
87
+ key=None,
88
+ label="Information",
89
+ open_by_default=False,
90
+ children=[
91
+ dp.Field(key="is_primary", label="Primary", width=Unit.PIXEL(100)),
92
+ dp.Field(key="disabled", label="Disabled", width=Unit.PIXEL(100)),
93
+ dp.Field(key="lock", label="Lock", width=Unit.PIXEL(100), show="open"),
94
+ dp.Field(key="parameters_repr", label="Parameters", show="open"),
95
+ ],
96
+ ),
97
+ ]
89
98
  )
90
99
 
91
100
  def get_instance_display(self) -> Display:
@@ -102,15 +111,22 @@ class ReportVersionDisplayConfig(DisplayViewConfig):
102
111
  class ReportVersionReportDisplayConfig(ReportVersionDisplayConfig):
103
112
  def get_list_display(self) -> Optional[dp.ListDisplay]:
104
113
  return dp.ListDisplay(
105
- fields=(
114
+ fields=[
106
115
  dp.Field(key="uuid", label="UUID"),
107
116
  dp.Field(key="lookup", label="Lookup"),
108
117
  dp.Field(key="title", label="Title"),
109
118
  dp.Field(key="version_date", label="Date"),
110
- dp.Field(key="report", label="Report"),
111
- dp.Field(key="is_primary", label="Is Primary"),
112
- dp.Field(key="disabled", label="Disabled"),
113
- dp.Field(key="parameters", label="Parameters"),
114
119
  dp.Field(key="comment", label="Comment"),
115
- )
120
+ dp.Field(
121
+ key=None,
122
+ label="Information",
123
+ open_by_default=False,
124
+ children=[
125
+ dp.Field(key="is_primary", label="Primary", width=Unit.PIXEL(100)),
126
+ dp.Field(key="disabled", label="Disabled", width=Unit.PIXEL(100)),
127
+ dp.Field(key="lock", label="Lock", width=Unit.PIXEL(100), show="open"),
128
+ dp.Field(key="parameters_repr", label="Parameters", show="open"),
129
+ ],
130
+ ),
131
+ ]
116
132
  )
@@ -6,12 +6,9 @@ class ReportVersionEndpointConfig(EndpointViewConfig):
6
6
  def get_endpoint(self, **kwargs):
7
7
  return None
8
8
 
9
- def get_list_endpoint(self, **kwargs):
9
+ def get_instance_endpoint(self, **kwargs):
10
10
  return reverse("wbreport:reportversion-list", request=self.request)
11
11
 
12
- def get_instance_endpoint(self):
13
- return self.get_list_endpoint()
14
-
15
12
 
16
13
  class ReportEndpointConfig(EndpointViewConfig):
17
14
  def get_endpoint(self, **kwargs):
@@ -19,12 +16,8 @@ class ReportEndpointConfig(EndpointViewConfig):
19
16
 
20
17
 
21
18
  class ReportVersionReportEndpointConfig(EndpointViewConfig):
22
- def get_list_endpoint(self, **kwargs):
23
- return reverse("wbreport:report-version-list", args=[self.view.kwargs["report_id"]], request=self.request)
19
+ pass
24
20
 
25
21
 
26
22
  class ReportVersionReportHTMEndpointConfig(EndpointViewConfig):
27
- def get_list_endpoint(self, **kwargs):
28
- return reverse(
29
- "wbreport:reportversion-rawhtml-list", args=[self.view.kwargs["report_version_id"]], request=self.request
30
- )
23
+ pass
@@ -1,6 +1,7 @@
1
1
  import json
2
2
 
3
- from django.db.models import Case, Exists, F, IntegerField, OuterRef, When
3
+ from django.db.models import Case, Exists, F, IntegerField, OuterRef, TextField, When
4
+ from django.db.models.functions import Cast
4
5
  from django.http import FileResponse, HttpResponse
5
6
  from django.shortcuts import get_object_or_404, render
6
7
  from django.views.decorators.cache import cache_page
@@ -76,7 +77,7 @@ def report(request, namespace):
76
77
  {
77
78
  "title": "Report Unavailable",
78
79
  "header": "Report Unavailable",
79
- "description": "If you thing this report should be enabled, please " "contact a system administrator",
80
+ "description": "If you thing this report should be enabled, please contact a system administrator",
80
81
  },
81
82
  status=status.HTTP_403_FORBIDDEN,
82
83
  )
@@ -116,7 +117,7 @@ def report_version(request, lookup):
116
117
  {
117
118
  "title": "Report Unavailable",
118
119
  "header": "Report Unavailable",
119
- "description": "If you thing this report should be enabled, please " "contact a system administrator",
120
+ "description": "If you thing this report should be enabled, please contact a system administrator",
120
121
  },
121
122
  status=status.HTTP_403_FORBIDDEN,
122
123
  )
@@ -271,7 +272,7 @@ class ReportVersionModelViewSet(viewsets.ModelViewSet):
271
272
  title_config_class = ReportVersionTitleConfig
272
273
  button_config_class = ReportVersionButtonConfig
273
274
 
274
- queryset = ReportVersion.objects.all()
275
+ queryset = ReportVersion.objects.annotate(parameters_repr=Cast("parameters", TextField()))
275
276
 
276
277
  @action(detail=True, methods=["GET"], permission_classes=[IsAuthenticated])
277
278
  def sendemail(self, request, pk=None):
@@ -281,9 +282,12 @@ class ReportVersionModelViewSet(viewsets.ModelViewSet):
281
282
  return Response({}, status=status.HTTP_200_OK)
282
283
  return Response({}, status=status.HTTP_404_NOT_FOUND)
283
284
 
284
- @action(detail=True, methods=["GET"], permission_classes=[IsAuthenticated])
285
+ @action(detail=True, methods=["PATCH"], permission_classes=[IsAuthenticated])
285
286
  def updatecontext(self, request, pk=None):
286
- update_context_as_task.delay(pk, request.user, comment=request.GET.get("comment", ""))
287
+ comment = request.data.get("comment", "")
288
+ override_lock = request.data.get("override_lock", "false").lower() == "true"
289
+
290
+ update_context_as_task.delay(pk, request.user, comment=comment, force_context_update=override_lock)
287
291
  return Response({}, status=status.HTTP_200_OK)
288
292
 
289
293
  @action(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wbreport
3
- Version: 1.49.5
3
+ Version: 1.60.1
4
4
  Author-email: Christopher Wittlinger <c.wittlinger@stainly.com>
5
5
  License-File: LICENSE
6
6
  Requires-Dist: wbcore
@@ -2,19 +2,19 @@ wbreport/__init__.py,sha256=J-j-u0itpEFT6irdmWmixQqYMadNl1X91TxUmoiLHMI,22
2
2
  wbreport/admin.py,sha256=tQPq0BKzXZ7I3aOE0FaGyjuKs37TiT56s7OT0mDfOXo,2364
3
3
  wbreport/apps.py,sha256=4z2N25hswEAlsJsntASC5iOWUwsqMCL99-gvqBlPGFY,148
4
4
  wbreport/dynamic_preferences_registry.py,sha256=nGT-fS5vN-BSVNyGcJ81ORjiXlOngePRN9hipB9olPw,508
5
- wbreport/filters.py,sha256=2onemO-Vpeqi7gXqxZWAmQg-5TT9Vyt3iJX32sLfhwU,1147
6
- wbreport/mixins.py,sha256=bAfMvfPL4YEGfEM8fsX-B_JDnN1spcefOhZU-g30080,5521
7
- wbreport/models.py,sha256=cRKgmdbb2cKRr04jkTtwv6kmRc5fd8CcIUENKlGIegQ,29595
8
- wbreport/serializers.py,sha256=k1hrtd8QodqRkM1Ol9MsLjk0n_XZU8qGLRR62EHJCXM,7236
9
- wbreport/tasks.py,sha256=O1mcYdOlqnj0lA7K52OszA7OBLClMhuzRm6ZiTcXAOY,2271
5
+ wbreport/filters.py,sha256=Q92qUs0_4NvpqWqbs8_D9OLD81herHLuWcZ1sdneZgw,1523
6
+ wbreport/mixins.py,sha256=fXVwHAi6OSsOZcrrvSmQAdOHh3V_bzk0FJehBTB0xFw,5509
7
+ wbreport/models.py,sha256=XVr4NZtSRqkk7vA_WaPloUBMA8JvyB5u2wUm5MrxzNg,29930
8
+ wbreport/serializers.py,sha256=SpQW-Y-63x71DupbUONcRD4ufgm99KZ4qJqijutM2_M,7276
9
+ wbreport/tasks.py,sha256=8Cwb-eKHMmW520fW5m4Xf2t3TSnG_crlB-4MBaLCqlI,2332
10
10
  wbreport/urls.py,sha256=yoIFuuwPCRRjOM0VzMznyMmMQXbSVfdk1l12xZJ7Ecs,1463
11
11
  wbreport/urls_public.py,sha256=oc_gBiKvLqWkSDAm_0yitWgpfEnlh3_hGUugkLrLCLs,370
12
12
  wbreport/defaults/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
13
  wbreport/defaults/factsheets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
- wbreport/defaults/factsheets/base.py,sha256=9dIylgXoUSXdHcspCGm3-TJ8Kei38tJ_6ew3gHoM9M4,41090
14
+ wbreport/defaults/factsheets/base.py,sha256=CliFS0MpfweN8a7GpWcxeerzICgpiDym9G_doPmB3GE,41166
15
15
  wbreport/defaults/factsheets/menu.py,sha256=NTcUuViV4Q-qkffoA7wMSyxnE4mWPysu3KTKWxFmMCM,4580
16
16
  wbreport/defaults/factsheets/mixins.py,sha256=E0RvxmUpvpG7B7YngBPXJ35eD3nwNandJFzLjj1rNRI,1267
17
- wbreport/defaults/factsheets/multitheme.py,sha256=LHM7tceWP9SJ9E8ChtD7eAq21uWMPdLBolnmgYBDTaw,38488
17
+ wbreport/defaults/factsheets/multitheme.py,sha256=OrJEmgcS8vACAk9l0cM_L9D_uOTbZClezGWfM-WLF7Y,38562
18
18
  wbreport/factories/__init__.py,sha256=jyD1_R-xc2JPZIaSwtE7zYATdL9NIXIDqTwjLw1MolA,170
19
19
  wbreport/factories/data_classes.py,sha256=Ms9bqG370OQTmE3Kxvt9xay-6lfTnqEA3Sx7zCQKWXw,1487
20
20
  wbreport/factories/reports.py,sha256=nGWa_fS78XGUbS8E35rGl3IJySHLB81ggq-3JllPOx4,2286
@@ -30,41 +30,41 @@ wbreport/migrations/0014_alter_reportcategory_options_and_more.py,sha256=52ot6Ck
30
30
  wbreport/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
31
31
  wbreport/pdf/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
32
32
  wbreport/pdf/charts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
33
- wbreport/pdf/charts/legend.py,sha256=D-_-ZLDF7q7KlYLiNYVXHWLs4I-LgV8Ce8lk1sLfbPU,461
34
- wbreport/pdf/charts/pie.py,sha256=EcWTDOv1ZD5Az6MnBt_w5pBXusPBB3zTHlpguBPNjfU,4287
35
- wbreport/pdf/charts/timeseries.py,sha256=dN2N66qg9S3bW9OV8q-Vms3wFxUHhLMnaUnymzTNeWY,2381
36
- wbreport/pdf/flowables/risk.py,sha256=KdE5RQU0kPUqZX6mpvqITVxpLFu1uDeA2C5lrtSA0mY,3443
37
- wbreport/pdf/flowables/textboxes.py,sha256=BMzlva7WBD5UZ5DD1cwWV5ZPragt61YaJquikDYk7eU,4369
33
+ wbreport/pdf/charts/legend.py,sha256=g_yAnI6zuMn11pXOphReWiKA1PPIEJ6NzKeeKLHx4t0,481
34
+ wbreport/pdf/charts/pie.py,sha256=QzsP18-s1IKXyiUT8sHi-wjivqEUKkxy42qMVaJP-fk,4288
35
+ wbreport/pdf/charts/timeseries.py,sha256=O1MCneh6hPJD6jVyJfytGL3WdYx9WaQ-vfO66lCjgUI,2395
36
+ wbreport/pdf/flowables/risk.py,sha256=jmqdqDjk21Ob5qQA0GxSv_DPrUTCyGaAmJuGq0PIYcA,3499
37
+ wbreport/pdf/flowables/textboxes.py,sha256=WIdqFyGG_kb_rO1OvB88QISPB36Zi3rTxjqBkGKeINY,4425
38
38
  wbreport/pdf/flowables/themes.py,sha256=Y5LcgSTJDhIZM3uIWAPY68HVMhTY9aY9PDwWsG5_IVk,6548
39
39
  wbreport/pdf/sandbox/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
40
40
  wbreport/pdf/sandbox/run.py,sha256=Vkzp_xVVJHctToS49Uef2Mnpb_5KUOWPxNdTFHOqri8,584
41
41
  wbreport/pdf/sandbox/templates/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
42
- wbreport/pdf/sandbox/templates/basic_factsheet.py,sha256=w_PpPS8rbirkrodgXs538_xOw6aNKGr4GGuVn_XGPPU,33967
43
- wbreport/pdf/sandbox/templates/fund_factsheet.py,sha256=aGf5CfUHAp-kGJfU8tZ3TnEO26re4Sny_G2QCFte4gk,31054
44
- wbreport/pdf/sandbox/templates/long_industry_exposure_factsheet.py,sha256=TnOz_NJLhX7ZT1amnbPkyee719dYug688JGSzVGRaMQ,32684
45
- wbreport/pdf/sandbox/templates/multistrat_factsheet.py,sha256=8-WU-r7x6Yq5v1ml2mUT7e33SUYk7IzKMNXFWuE7Y2k,31481
42
+ wbreport/pdf/sandbox/templates/basic_factsheet.py,sha256=1bU6nPg8TI6QTtqmCn7FO9cio2vrsQ32OtViDa-Okqw,34100
43
+ wbreport/pdf/sandbox/templates/fund_factsheet.py,sha256=WHYMKnTaUW1UtLW4-QeWlRhdHfgP0FasRZWJKB9Riyk,31157
44
+ wbreport/pdf/sandbox/templates/long_industry_exposure_factsheet.py,sha256=nZgy_NUR7JT22jFyBzZnr800T4EkHzErcgLAr9C6_-s,32803
45
+ wbreport/pdf/sandbox/templates/multistrat_factsheet.py,sha256=WR_zlgfNQom257U2b12GIeKY6dM17Or6k1H9Jupeo1Q,31572
46
46
  wbreport/pdf/sandbox/templates/testfile.pdf,sha256=eV55RNFdqxqYzre6mg8EWZoL_cbc_lwPjyTGt1pr1AI,144012
47
47
  wbreport/pdf/tables/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
48
- wbreport/pdf/tables/aggregated_tables.py,sha256=rYikFswLMcaTpW7Em5B5oE1WSItQjkW_MJJTsumRpq8,4763
49
- wbreport/pdf/tables/data_tables.py,sha256=snJlyMMRz4YSR4gcXeIdQR38h-IkOQiO0A5jx5lF45o,2241
48
+ wbreport/pdf/tables/aggregated_tables.py,sha256=ARQD3i2hbclx-IdUUNytePbIWQVMHEb0qRPYlZBjG-E,4811
49
+ wbreport/pdf/tables/data_tables.py,sha256=c3YZsTgOlcupNAPBpZQIxR89bgyk5PgZvyJCucy7KAw,2297
50
50
  wbreport/templates/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
51
51
  wbreport/templatetags/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
52
52
  wbreport/templatetags/portfolio_tags.py,sha256=N2MC_6iFcUAFDH5Wx5rD1-PVK_Ltq4VPIoq7WL4aXGQ,900
53
53
  wbreport/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
54
54
  wbreport/tests/conftest.py,sha256=oMidAuP41_yxMubUDUpqcsW_YslEq_CyPmfD11CUPhY,745
55
- wbreport/tests/test_models.py,sha256=nCBcCLBylZsFMSgkU6mm4iQdoPVRuS42taCI0pb6Gx4,11292
56
- wbreport/tests/test_tasks.py,sha256=icRtEyAtKfhxyx_oN6cYtkUA4QMaGwEpdKKhc07OPKc,762
55
+ wbreport/tests/test_models.py,sha256=jJ_Am79tf8gX7lUpheV9dhe-rZu7UlOSG5dE3RGcqU8,11293
56
+ wbreport/tests/test_tasks.py,sha256=_u2Ns4w8aHTXrMscodv4lOPhrgV2EkVwMhnEttkwcLM,763
57
57
  wbreport/tests/test_viewsets.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
58
58
  wbreport/tests/tests.py,sha256=j2A1uu5DtGu2w056j0S4O7u2OxylldMPCmr7bbSOvfk,282
59
59
  wbreport/viewsets/__init__.py,sha256=iEgYvZsLxQ3Hhf1hwHVdSD7Dm1OZK1Z6qhvTADqs-oc,322
60
- wbreport/viewsets/viewsets.py,sha256=IDHm-38_i-hcwODz9v6zUXQBUQq2rYqHLXICEUqsx-8,13746
60
+ wbreport/viewsets/viewsets.py,sha256=NAk5rdBn8NYPEx94AH8hGthDN1HtjmpG2w7OskSWlNg,13998
61
61
  wbreport/viewsets/configs/__init__.py,sha256=WDvXbmGKD6_DP3nongBzL6lOHYwz5JC0gWtS1C2N4FY,535
62
- wbreport/viewsets/configs/buttons.py,sha256=jJYCTpE9mT1VF6zdrGipenw2fZPE2P5_wbitz4R-XAw,8912
63
- wbreport/viewsets/configs/displays.py,sha256=py3Wcut9uKmer7LV-ftIi0xU97PFctJT-yKZmdgc8qk,4897
64
- wbreport/viewsets/configs/endpoints.py,sha256=LdGFUYMNUHt6qBORnu4IR8Vn2qIIv2Q2TLOs0-cGH0g,1060
62
+ wbreport/viewsets/configs/buttons.py,sha256=e2B3UNMysLWwS4k5HiHrnVVAdABstzAwc0R9oR8mTNk,9364
63
+ wbreport/viewsets/configs/displays.py,sha256=No-JZ9JC-wtFGqsrNk7_NX5I3RaHWZkG2hJuuwlPkGs,5705
64
+ wbreport/viewsets/configs/endpoints.py,sha256=2k2aPoAo4Nq8czbYWLqz2hBbdgPAOk91GSUCt7twA_E,651
65
65
  wbreport/viewsets/configs/menus.py,sha256=trDZsdjXip8KQ4SY1uIudZWFqwb3rQHIRSU36dzRzy0,269
66
66
  wbreport/viewsets/configs/titles.py,sha256=bAfruj40yHrSUPzuJ7WGlfJDl7NXXKSaTqITPNn4eJA,783
67
- wbreport-1.49.5.dist-info/METADATA,sha256=Y3UMiV8gCxtSuGX25dSGQOb7E7CYBPAaOM61IrAu0e4,186
68
- wbreport-1.49.5.dist-info/WHEEL,sha256=tkmg4JIqwd9H8mL30xA7crRmoStyCtGp0VWshokd1Jc,105
69
- wbreport-1.49.5.dist-info/licenses/LICENSE,sha256=jvfVH0SY8_YMHlsJHKe_OajiscQDz4lpTlqT6x24sVw,172
70
- wbreport-1.49.5.dist-info/RECORD,,
67
+ wbreport-1.60.1.dist-info/METADATA,sha256=o8ekY77q4ZL5VKh8XZkxywjHnj3Rk0gC-EKXqq6xidk,186
68
+ wbreport-1.60.1.dist-info/WHEEL,sha256=aha0VrrYvgDJ3Xxl3db_g_MDIW-ZexDdrc_m-Hk8YY4,105
69
+ wbreport-1.60.1.dist-info/licenses/LICENSE,sha256=jvfVH0SY8_YMHlsJHKe_OajiscQDz4lpTlqT6x24sVw,172
70
+ wbreport-1.60.1.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.27.0
2
+ Generator: hatchling 1.28.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py2-none-any
5
5
  Tag: py3-none-any