lino 24.10.2__py3-none-any.whl → 24.11.0__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.
Files changed (53) hide show
  1. lino/__init__.py +1 -1
  2. lino/api/dd.py +1 -0
  3. lino/api/doctest.py +11 -10
  4. lino/api/rt.py +2 -3
  5. lino/config/admin_main_base.html +2 -2
  6. lino/core/actors.py +68 -33
  7. lino/core/choicelists.py +2 -2
  8. lino/core/dashboard.py +2 -1
  9. lino/core/dbtables.py +3 -3
  10. lino/core/elems.py +8 -4
  11. lino/core/fields.py +11 -3
  12. lino/core/kernel.py +4 -5
  13. lino/core/layouts.py +1 -1
  14. lino/core/model.py +1 -9
  15. lino/core/plugin.py +1 -0
  16. lino/core/renderer.py +19 -19
  17. lino/core/requests.py +73 -38
  18. lino/core/site.py +5 -45
  19. lino/core/store.py +16 -16
  20. lino/core/tables.py +0 -17
  21. lino/core/utils.py +58 -10
  22. lino/core/views.py +2 -1
  23. lino/help_texts.py +13 -0
  24. lino/management/commands/show.py +2 -4
  25. lino/mixins/periods.py +10 -4
  26. lino/mixins/polymorphic.py +3 -3
  27. lino/mixins/ref.py +6 -3
  28. lino/modlib/checkdata/__init__.py +3 -3
  29. lino/modlib/extjs/ext_renderer.py +1 -1
  30. lino/modlib/languages/__init__.py +1 -1
  31. lino/modlib/languages/models.py +1 -1
  32. lino/modlib/linod/consumers.py +2 -3
  33. lino/modlib/memo/mixins.py +1 -1
  34. lino/modlib/periods/__init__.py +31 -0
  35. lino/modlib/periods/fixtures/std.py +23 -0
  36. lino/modlib/periods/mixins.py +126 -0
  37. lino/modlib/periods/models.py +246 -0
  38. lino/modlib/printing/actions.py +2 -0
  39. lino/modlib/publisher/ui.py +2 -2
  40. lino/modlib/system/__init__.py +0 -2
  41. lino/modlib/system/choicelists.py +55 -1
  42. lino/modlib/system/models.py +1 -0
  43. lino/modlib/users/models.py +2 -2
  44. lino/modlib/weasyprint/__init__.py +2 -0
  45. lino/utils/__init__.py +8 -9
  46. lino/utils/djangotest.py +2 -1
  47. lino/utils/html.py +31 -0
  48. lino/utils/ranges.py +5 -15
  49. {lino-24.10.2.dist-info → lino-24.11.0.dist-info}/METADATA +1 -1
  50. {lino-24.10.2.dist-info → lino-24.11.0.dist-info}/RECORD +53 -49
  51. {lino-24.10.2.dist-info → lino-24.11.0.dist-info}/WHEEL +0 -0
  52. {lino-24.10.2.dist-info → lino-24.11.0.dist-info}/licenses/AUTHORS.rst +0 -0
  53. {lino-24.10.2.dist-info → lino-24.11.0.dist-info}/licenses/COPYING +0 -0
lino/mixins/periods.py CHANGED
@@ -214,11 +214,11 @@ class DateRangeObservable(Model):
214
214
  def setup_parameters(cls, fields):
215
215
  fields.update(
216
216
  start_date=models.DateField(
217
- _("Period from"),
217
+ _("Date range from"),
218
218
  blank=True,
219
219
  null=True,
220
220
  default=cls.get_default_start_date,
221
- help_text=_("Start date of observed period"),
221
+ help_text=_("Start date of observed date range."),
222
222
  )
223
223
  )
224
224
  fields.update(
@@ -227,7 +227,7 @@ class DateRangeObservable(Model):
227
227
  blank=True,
228
228
  null=True,
229
229
  default=cls.get_default_end_date,
230
- help_text=_("End date of observed period"),
230
+ help_text=_("End date of observed date range."),
231
231
  )
232
232
  )
233
233
  super().setup_parameters(fields)
@@ -267,6 +267,12 @@ class DateRange(DateRangeObservable):
267
267
  def get_period_text(self):
268
268
  return rangetext(self, fdl, self.empty_period_text)
269
269
 
270
+ def covers_date(self, d):
271
+ if self.start_date and self.start_date > d:
272
+ return False
273
+ if self.end_date and self.end_date < d:
274
+ return False
275
+ return True
270
276
 
271
277
  DateRange.set_widget_options("start_date", width=10)
272
278
  DateRange.set_widget_options("end_date", width=10)
@@ -372,4 +378,4 @@ class Today(ParameterPanel):
372
378
  help_text=_("Date of observation"),
373
379
  ),
374
380
  )
375
- super(Today, self).__init__(**kw)
381
+ super().__init__(**kw)
@@ -247,16 +247,16 @@ class Polymorphic(model.Model):
247
247
  buttons = self.get_mti_buttons(ar)
248
248
  return E.span(*buttons)
249
249
 
250
- def as_summary_item(self, ar, *args, **kwargs):
250
+ def as_summary_item(self, ar, text=None, **kwargs):
251
251
  # a = super(Polymorphic, self).get_detail_action(ar)
252
252
  a = self.get_detail_action(ar)
253
253
  if a is not None:
254
- return super().as_summary_item(ar, *args, **kwargs)
254
+ return super().as_summary_item(ar, text, **kwargs)
255
255
  for m in self._mtinav_models:
256
256
  if m is not self.__class__:
257
257
  obj = mti.get_child(self, m)
258
258
  if obj is not None:
259
259
  if obj.get_detail_action(ar) is not None:
260
- return obj.as_summary_item(ar, *args, **kwargs)
260
+ return obj.as_summary_item(ar, text, **kwargs)
261
261
  return str(self)
262
262
  # return "?!? {} has no detail_action for {}".format(self, ar)
lino/mixins/ref.py CHANGED
@@ -47,7 +47,7 @@ class Referrable(model.Model):
47
47
  @classmethod
48
48
  def on_analyze(cls, site):
49
49
  cls.set_widget_options("ref", width=cls.ref_max_length)
50
- super(Referrable, cls).on_analyze(site)
50
+ super().on_analyze(site)
51
51
 
52
52
  def on_duplicate(self, ar, master):
53
53
  """
@@ -56,7 +56,10 @@ class Referrable(model.Model):
56
56
  """
57
57
  if self.ref:
58
58
  self.ref += " (DUP)"
59
- super(Referrable, self).on_duplicate(ar, master)
59
+ super().on_duplicate(ar, master)
60
+
61
+ def __str__(self):
62
+ return self.ref or super().__str__()
60
63
 
61
64
  @staticmethod
62
65
  def ref_prefix(obj, ar=None):
@@ -96,7 +99,7 @@ class Referrable(model.Model):
96
99
  # if search_text.isdigit():
97
100
  if search_text.startswith("*"):
98
101
  return models.Q(**{prefix + "ref__icontains": search_text[1:]})
99
- return super(Referrable, cls).quick_search_filter(search_text, prefix)
102
+ return super().quick_search_filter(search_text, prefix)
100
103
 
101
104
 
102
105
  class StructuredReferrable(Referrable):
@@ -20,7 +20,8 @@ class Plugin(ad.Plugin):
20
20
  "The config descriptor for this plugin."
21
21
 
22
22
  verbose_name = _("Checkdata")
23
- needs_plugins = ["lino.modlib.users", "lino.modlib.gfks", "lino.modlib.linod"]
23
+ needs_plugins = ["lino.modlib.users", "lino.modlib.gfks",
24
+ "lino.modlib.office", "lino.modlib.linod"]
24
25
 
25
26
  # plugin settings
26
27
  responsible_user = None # the username (a string)
@@ -50,8 +51,7 @@ class Plugin(ad.Plugin):
50
51
  User = self.site.models.users.User
51
52
  try:
52
53
  self._responsible_user = User.objects.get(
53
- username=self.responsible_user
54
- )
54
+ username=self.responsible_user)
55
55
  except User.DoesNotExist:
56
56
  msg = "Invalid username '{0}' in `responsible_user` "
57
57
  msg = msg.format(self.responsible_user)
@@ -986,7 +986,7 @@ class ExtRenderer(JsCacheRenderer):
986
986
  tbl = dh.layout._datasource
987
987
  yield ""
988
988
  yield "Lino.%s = Ext.extend(Lino.ActionFormPanel,{" % dh.layout._formpanel_name
989
- for k, v in list(dh.main.ext_options().items()):
989
+ for k, v in dh.main.ext_options().items():
990
990
  if k != "items":
991
991
  yield " %s: %s," % (k, py2js(v))
992
992
  assert tbl.action_name is not None
@@ -27,7 +27,7 @@ from lino import ad, _
27
27
  class Plugin(ad.Plugin):
28
28
  "See :doc:`/dev/plugins`."
29
29
 
30
- verbose_name = _("Boards")
30
+ verbose_name = _("Languages")
31
31
 
32
32
  def setup_config_menu(self, site, user_type, m, ar=None):
33
33
  p = self.get_menu_group()
@@ -1,5 +1,5 @@
1
1
  # -*- coding: UTF-8 -*-
2
- # Copyright 2008-2017 Luc Saffre
2
+ # Copyright 2008-2017 Rumma & Ko Ltd
3
3
  # License: GNU Affero General Public License v3 (see file COPYING for details)
4
4
  """Defines the :class:`Language` model.
5
5
 
@@ -57,9 +57,8 @@ class LinodConsumer(AsyncConsumer):
57
57
  async def run_background_tasks(self, event: dict):
58
58
  # 'run.background.tasks' in `pm linod`
59
59
  from lino.modlib.linod.mixins import start_task_runner
60
-
61
- # start_task_runner = settings.SITE.models.linod.start_task_runner
62
- ar = settings.SITE.login()
60
+ from lino.core.requests import BaseRequest
61
+ ar = BaseRequest()
63
62
  asyncio.ensure_future(start_task_runner(ar))
64
63
 
65
64
  async def send_push(self, event):
@@ -286,7 +286,7 @@ class MemoReferrable(dd.Model):
286
286
  def memo2html(self, ar, txt, **kwargs):
287
287
  if txt:
288
288
  kwargs.update(title=txt)
289
- e = self.as_summary_item(ar, **kwargs)
289
+ e = self.as_summary_item(ar)
290
290
  return tostring(e)
291
291
  # return ar.obj2str(self, **kwargs)
292
292
 
@@ -0,0 +1,31 @@
1
+ # Copyright 2008-2024 Rumma & Ko Ltd
2
+ # License: GNU Affero General Public License v3 (see file COPYING for details)
3
+ from lino import ad, _
4
+
5
+
6
+ class Plugin(ad.Plugin):
7
+
8
+ verbose_name = _("Stored periods")
9
+ period_name = _("Accounting period")
10
+ period_name_plural = _("Accounting periods")
11
+ year_name = _("Fiscal year")
12
+ year_name_plural = _("Fiscal years")
13
+ start_year = 2012
14
+ start_month = 1
15
+ period_type = "month"
16
+ fix_y2k = False
17
+ short_ref = False
18
+
19
+ def setup_config_menu(self, site, user_type, m, ar=None):
20
+ p = self.get_menu_group()
21
+ m = m.add_menu(p.app_label, p.verbose_name)
22
+ m.add_action("periods.StoredYears")
23
+ m.add_action("periods.StoredPeriods")
24
+
25
+ def before_analyze(self):
26
+ if self.fix_y2k and self.start_month != 1:
27
+ raise Exception("When fix_y2k is set, start_month must be 1")
28
+ if isinstance(self.period_type, str):
29
+ self.period_type = self.site.models.periods.PeriodTypes.get_by_name(
30
+ self.period_type)
31
+ super().before_analyze()
@@ -0,0 +1,23 @@
1
+ # -*- coding: UTF-8 -*-
2
+ # Copyright 2012-2024 Rumma & Ko Ltd
3
+ # License: GNU Affero General Public License v3 (see file COPYING for details)
4
+
5
+ import datetime
6
+ from django.conf import settings
7
+ from lino.api import dd, rt, _
8
+
9
+ start_year = dd.get_plugin_setting("periods", "start_year", None)
10
+
11
+ def objects():
12
+ StoredYear = rt.models.periods.StoredYear
13
+
14
+ cfg = dd.plugins.periods
15
+ site = settings.SITE
16
+ if site.the_demo_date is not None:
17
+ if start_year > site.the_demo_date.year:
18
+ raise Exception("plugins.periods.start_year is after the_demo_date")
19
+ today = site.the_demo_date or datetime.date.today()
20
+ for y in range(start_year, today.year + 6):
21
+ # yield StoredYear.create_from_year(y)
22
+ yield StoredYear.get_or_create_from_date(datetime.date(y, today.month, today.day))
23
+ # StoredYears.add_item(StoredYear.year2value(y), str(y))
@@ -0,0 +1,126 @@
1
+ # -*- coding: UTF-8 -*-
2
+ # Copyright 2008-2024 Rumma & Ko Ltd
3
+ # License: GNU Affero General Public License v3 (see file COPYING for details)
4
+
5
+ from django.db import models
6
+ from django.utils.translation import gettext_lazy as _
7
+
8
+ from lino.api import dd, rt
9
+ from lino import mixins
10
+ from lino.mixins.periods import DateRange
11
+ from lino.mixins import Referrable
12
+
13
+ from lino.modlib.office.roles import OfficeStaff
14
+
15
+
16
+ class PeriodRange(dd.Model):
17
+
18
+ class Meta:
19
+ abstract = True
20
+
21
+ start_period = dd.ForeignKey(
22
+ 'periods.StoredPeriod',
23
+ blank=True,
24
+ verbose_name=_("Start period"),
25
+ related_name="%(app_label)s_%(class)s_set_by_start_period")
26
+
27
+ end_period = dd.ForeignKey(
28
+ 'periods.StoredPeriod',
29
+ blank=True,
30
+ null=True,
31
+ verbose_name=_("End period"),
32
+ related_name="%(app_label)s_%(class)s_set_by_end_period")
33
+
34
+ def get_period_filter(self, fieldname, **kwargs):
35
+ return rt.models.periods.StoredPeriod.get_period_filter(
36
+ fieldname, self.start_period, self.end_period, **kwargs)
37
+
38
+
39
+ class PeriodRangeObservable(dd.Model):
40
+
41
+ class Meta:
42
+ abstract = True
43
+
44
+ observable_period_prefix = ''
45
+
46
+ @classmethod
47
+ def setup_parameters(cls, fields):
48
+ fields.update(start_period=dd.ForeignKey(
49
+ 'periods.StoredPeriod',
50
+ blank=True,
51
+ null=True,
52
+ help_text=_("Start of observed period range."),
53
+ verbose_name=_("Period from")))
54
+ fields.update(end_period=dd.ForeignKey(
55
+ 'periods.StoredPeriod',
56
+ blank=True,
57
+ null=True,
58
+ help_text=_("Optional end of observed period range. "
59
+ "Leave empty to observe only the start period."),
60
+ verbose_name=_("Period until")))
61
+ super().setup_parameters(fields)
62
+
63
+ @classmethod
64
+ def get_request_queryset(cls, ar, **kwargs):
65
+ qs = super().get_request_queryset(ar, **kwargs)
66
+ if (pv := ar.param_values) is None: return qs
67
+ if pv.start_period is not None:
68
+ fkw = dict()
69
+ fkw[cls.observable_period_prefix + "journal__preliminary"] = False
70
+ flt = rt.models.periods.StoredPeriod.get_period_filter(
71
+ cls.observable_period_prefix + "accounting_period",
72
+ pv.start_period, pv.end_period, **fkw)
73
+ qs = qs.filter(**flt)
74
+ return qs
75
+
76
+ @classmethod
77
+ def get_title_tags(cls, ar):
78
+ for t in super().get_title_tags(ar):
79
+ yield t
80
+ pv = ar.param_values
81
+ if pv.start_period is not None:
82
+ if pv.end_period is None:
83
+ yield str(pv.start_period)
84
+ else:
85
+ yield "{}..{}".format(pv.start_period, pv.end_period)
86
+
87
+
88
+ class PeriodRangeParameters(dd.ParameterPanel):
89
+
90
+ def __init__(self,
91
+ verbose_name_start=_("Period from"),
92
+ verbose_name_end=_("until"),
93
+ **kwargs):
94
+ kwargs.update(
95
+ start_period=dd.ForeignKey(
96
+ 'periods.StoredPeriod',
97
+ blank=True,
98
+ null=True,
99
+ help_text=_("Start of observed period range"),
100
+ verbose_name=verbose_name_start),
101
+ end_period=dd.ForeignKey(
102
+ 'periods.StoredPeriod',
103
+ blank=True,
104
+ null=True,
105
+ help_text=_("Optional end of observed period range. "
106
+ "Leave empty to consider only the Start period."),
107
+ verbose_name=verbose_name_end))
108
+ super().__init__(**kwargs)
109
+
110
+ def check_values(self, pv):
111
+ if not pv.start_period:
112
+ raise Warning(_("Select at least a start period"))
113
+ if pv.end_period:
114
+ if str(pv.start_period) > str(pv.end_period):
115
+ raise Warning(_("End period must be after start period"))
116
+
117
+ def get_title_tags(self, ar):
118
+ pv = ar.param_values
119
+ if pv.start_period:
120
+ if pv.end_period:
121
+ yield _("Periods {}...{}").format(pv.start_period,
122
+ pv.end_period)
123
+ else:
124
+ yield _("Period {}").format(pv.start_period)
125
+ else:
126
+ yield str(_("All periods"))
@@ -0,0 +1,246 @@
1
+ # -*- coding: UTF-8 -*-
2
+ # Copyright 2008-2024 Rumma & Ko Ltd
3
+ # License: GNU Affero General Public License v3 (see file COPYING for details)
4
+
5
+ import datetime
6
+ from django.db import models
7
+ from django.utils.translation import gettext_lazy as _
8
+
9
+ from lino.api import dd
10
+ from lino import mixins
11
+ from lino.utils import last_day_of_month, ONE_DAY
12
+ from lino.mixins.periods import DateRange
13
+ from lino.mixins import Referrable
14
+
15
+ from lino.modlib.office.roles import OfficeStaff
16
+
17
+ NEXT_YEAR_SEP = "/"
18
+ YEAR_PERIOD_SEP = "-"
19
+
20
+ class PeriodType(dd.Choice):
21
+ ref_template = None
22
+ ref_template = None
23
+
24
+ def __init__(self, value, text, duration, ref_template):
25
+ super().__init__(value, text, value)
26
+ self.ref_template = ref_template
27
+ self.duration = duration
28
+
29
+ class PeriodTypes(dd.ChoiceList):
30
+ item_class = PeriodType
31
+ verbose_name = _("Period type")
32
+ verbose_name_plural = _("Period types")
33
+ column_names = "value text duration ref_template"
34
+
35
+ @dd.displayfield(_("Duration"))
36
+ def duration(cls, p, ar):
37
+ return str(p.duration)
38
+
39
+ @dd.displayfield(_("Template for reference"))
40
+ def ref_template(cls, p, ar):
41
+ return p.ref_template
42
+
43
+ add = PeriodTypes.add_item
44
+ # value/names, text, duration, ref_template
45
+ add("month", _("Month"), 1, "{month:0>2}")
46
+ add("quarter", _("Quarter"), 3, "Q{period}")
47
+ add("trimester", _("Trimester"), 4, "T{period}")
48
+ add("semester", _("Semester"), 6, "S{period}")
49
+
50
+
51
+ class PeriodStates(dd.Workflow):
52
+ pass
53
+
54
+ add = PeriodStates.add_item
55
+ add('10', _("Open"), 'open')
56
+ add('20', _("Closed"), 'closed')
57
+
58
+
59
+ class StoredYear(DateRange, Referrable):
60
+
61
+ class Meta:
62
+ app_label = 'periods'
63
+ verbose_name = dd.plugins.periods.year_name
64
+ verbose_name_plural = dd.plugins.periods.year_name_plural
65
+ ordering = ['ref']
66
+
67
+ preferred_foreignkey_width = 10
68
+
69
+ state = PeriodStates.field(default='open')
70
+
71
+ @classmethod
72
+ def get_simple_parameters(cls):
73
+ yield super().get_simple_parameters()
74
+ yield "state"
75
+
76
+ @classmethod
77
+ def get_ref_for_date(cls, date):
78
+ year = date.year
79
+ if date.month < dd.plugins.periods.start_month:
80
+ year -= 1
81
+ if dd.plugins.periods.fix_y2k:
82
+ if year < 2000:
83
+ return str(year)[-2:]
84
+ elif year < 3000:
85
+ return chr(int(str(year)[-3:-1]) + 65) + str(year)[-1]
86
+ else:
87
+ raise Exception("fix_y2k not supported after 2999")
88
+ elif dd.plugins.periods.short_ref:
89
+ if dd.plugins.periods.start_month == 1:
90
+ return str(year)[-2:]
91
+ return str(year)[-2:] + NEXT_YEAR_SEP + str(year+1)[-2:]
92
+ elif dd.plugins.periods.start_month == 1:
93
+ return str(year)
94
+ return str(year) + NEXT_YEAR_SEP + str(year+1)[-2:]
95
+
96
+ @classmethod
97
+ def get_or_create_from_date(cls, date):
98
+ ref = cls.get_ref_for_date(date)
99
+ obj = cls.get_by_ref(ref, None)
100
+ if obj is None:
101
+ sd = datetime.date(date.year, dd.plugins.periods.start_month, 1)
102
+ ed = sd.replace(year=date.year+1) - ONE_DAY
103
+ obj = cls(ref=ref, start_date=sd, end_date=ed)
104
+ obj.full_clean()
105
+ obj.save()
106
+ return obj
107
+
108
+ def __str__(self):
109
+ return self.ref
110
+
111
+
112
+ class StoredPeriod(DateRange, Referrable):
113
+
114
+ class Meta:
115
+ ordering = ['ref']
116
+ app_label = 'periods'
117
+ verbose_name = dd.plugins.periods.period_name
118
+ verbose_name_plural = dd.plugins.periods.period_name_plural
119
+
120
+ preferred_foreignkey_width = 10
121
+
122
+ state = PeriodStates.field(default='open')
123
+ year = dd.ForeignKey('periods.StoredYear', blank=True, null=True)
124
+ remark = models.CharField(_("Remark"), max_length=250, blank=True)
125
+
126
+ @classmethod
127
+ def get_simple_parameters(cls):
128
+ yield super().get_simple_parameters()
129
+ yield "state"
130
+ yield "year"
131
+
132
+ @classmethod
133
+ def get_request_queryset(cls, ar):
134
+ qs = super().get_request_queryset(ar)
135
+ if (pv := ar.param_values) is None: return qs
136
+
137
+ # if pv.start_date is None or pv.end_date is None:
138
+ # period = None
139
+ # else:
140
+ # period = (pv.start_date, pv.end_date)
141
+ # if period is not None:
142
+ # qs = qs.filter(dd.inrange_filter('start_date', period))
143
+ if pv.start_date or pv.end_date:
144
+ qs = qs.filter(dd.overlap_range_filter(
145
+ pv.start_date, pv.end_date, "start_date", "end_date"))
146
+ # if pv.start_date:
147
+ # qs = qs.filter(dd.range_filter(pv.start_date, 'start_date', 'end_date'))
148
+ # # qs = qs.filter(start_date__gte=pv.start_date)
149
+ # if pv.end_date:
150
+ # qs = qs.filter(dd.range_filter(pv.end_date, 'start_date', 'end_date'))
151
+ # qs = qs.filter(end_date__lte=pv.end_date)
152
+ return qs
153
+
154
+ @classmethod
155
+ def get_available_periods(cls, today):
156
+ """Return a queryset of periods available for booking."""
157
+ if today is None: # added 20160531
158
+ today = dd.today()
159
+ fkw = dict(start_date__lte=today, end_date__gte=today)
160
+ return cls.objects.filter(**fkw)
161
+
162
+ @classmethod
163
+ def get_periods_in_range(cls, p1, p2):
164
+ return cls.objects.filter(ref__gte=p1.ref, ref__lte=p2.ref)
165
+
166
+ @classmethod
167
+ def get_period_filter(cls, fieldname, p1, p2, **kwargs):
168
+ if p1 is None:
169
+ return kwargs
170
+
171
+ # ignore preliminary movements if a start_period is given:
172
+ # kwargs[voucher_prefix + "journal__preliminary"] = False
173
+
174
+ # accounting_period = voucher_prefix + "accounting_period"
175
+
176
+ if p2 is None:
177
+ kwargs[fieldname] = p1
178
+ else:
179
+ periods = cls.get_periods_in_range(p1, p2)
180
+ kwargs[fieldname + '__in'] = periods
181
+ return kwargs
182
+
183
+ @classmethod
184
+ def get_ref_for_date(cls, date):
185
+ pt = dd.plugins.periods.period_type
186
+ month = date.month
187
+ month_offset = month - dd.plugins.periods.start_month
188
+ if month_offset < 0:
189
+ month_offset += 12
190
+ period = int(month_offset / pt.duration) + 1
191
+ # periods_per_year = int(12 / p.duration)
192
+ # period = (month_offset % (periods_per_year-1)) + 1
193
+ return pt.ref_template.format(**locals())
194
+
195
+ @classmethod
196
+ def get_or_create_from_date(cls, date): # get_default_for_date until 20241020
197
+ ref = date2ref(date)
198
+ obj = cls.get_by_ref(ref, None)
199
+ if obj is None:
200
+ obj = cls(
201
+ ref=ref,
202
+ start_date=date.replace(day=1),
203
+ end_date=last_day_of_month(date))
204
+ obj.full_clean()
205
+ obj.save()
206
+ return obj
207
+
208
+ def full_clean(self, *args, **kwargs):
209
+ if self.start_date is None:
210
+ self.start_date = dd.today().replace(day=1)
211
+ if not self.year:
212
+ self.year = StoredYear.get_or_create_from_date(self.start_date)
213
+ super().full_clean(*args, **kwargs)
214
+
215
+ def __str__(self):
216
+ if not self.ref:
217
+ return dd.obj2str(self)
218
+ # "{0} {1} (#{0})".format(self.pk, self.year)
219
+ return self.ref
220
+
221
+ @property
222
+ def nickname(self):
223
+ if self.year.covers_date(dd.today()):
224
+ if len(parts := self.ref.split(YEAR_PERIOD_SEP)) == 2:
225
+ return parts[1]
226
+ return self.ref
227
+
228
+ StoredPeriod.set_widget_options('ref', width=6)
229
+
230
+ def date2ref(d):
231
+ return StoredYear.get_ref_for_date(d) + YEAR_PERIOD_SEP + StoredPeriod.get_ref_for_date(d)
232
+
233
+ class StoredYears(dd.Table):
234
+ model = 'periods.StoredYear'
235
+ required_roles = dd.login_required(OfficeStaff)
236
+ column_names = "ref start_date end_date state *"
237
+ # detail_layout = """
238
+ # ref id
239
+ # start_date end_date
240
+ # """
241
+
242
+ class StoredPeriods(dd.Table):
243
+ required_roles = dd.login_required(OfficeStaff)
244
+ model = 'periods.StoredPeriod'
245
+ order_by = ["ref", "start_date", "year"]
246
+ column_names = "ref start_date end_date year state remark *"
@@ -119,6 +119,7 @@ class BasePrintAction(Action):
119
119
  class DirectPrintAction(BasePrintAction):
120
120
  url_action_name = None
121
121
  icon_name = "printer"
122
+ # button_text = "🖶" # 1F5B6
122
123
  tplname = None
123
124
 
124
125
  def __init__(self, label=None, tplname=None, build_method=None, **kw):
@@ -162,6 +163,7 @@ class CachedPrintAction(BasePrintAction):
162
163
  # select_rows = False
163
164
  http_method = "POST"
164
165
  icon_name = "printer"
166
+ # button_text = "🖶" # 1F5B6
165
167
 
166
168
  def before_build(self, bm, elem):
167
169
  if elem.build_time:
@@ -172,6 +172,6 @@ class TranslationsByPage(Pages):
172
172
  default_display_modes = {None: constants.DISPLAY_MODE_SUMMARY}
173
173
 
174
174
  @classmethod
175
- def row_as_summary(cls, ar, obj, **kwargs):
175
+ def row_as_summary(cls, ar, obj, text=None, **kwargs):
176
176
  # return format_html("({}) {}", obj.language, obj.as_summary_row(ar, **kwargs))
177
- return E.span("({}) ".format(obj.language), obj.as_summary_item(ar, **kwargs))
177
+ return E.span("({}) ".format(obj.language), obj.as_summary_item(ar, text, **kwargs))
@@ -14,9 +14,7 @@ class Plugin(ad.Plugin):
14
14
  "See :doc:`/dev/plugins`."
15
15
 
16
16
  verbose_name = _("System")
17
-
18
17
  needs_plugins = ["lino.modlib.printing"]
19
-
20
18
  use_dashboard_layouts = False
21
19
  """Whether to use system.DashboardLayouts. This feature is broken.
22
20
  """