lino 25.7.2__py3-none-any.whl → 25.7.3__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.
lino/__init__.py CHANGED
@@ -31,7 +31,7 @@ from django import VERSION
31
31
  from django.apps import AppConfig
32
32
  from django.conf import settings
33
33
  import warnings
34
- __version__ = '25.7.2'
34
+ __version__ = '25.7.3'
35
35
 
36
36
  # import setuptools # avoid UserWarning "Distutils was imported before Setuptools"?
37
37
 
lino/api/doctest.py CHANGED
@@ -14,6 +14,7 @@ tested document. It includes
14
14
 
15
15
  """
16
16
 
17
+ from lino.mixins.clonable import Clonable
17
18
  from lino.utils.fieldutils import get_fields, fields_help
18
19
  from lino.core.boundaction import BoundAction
19
20
  from lino.core.tables import AbstractTable
@@ -23,7 +24,9 @@ from lino.core.actions import ShowTable
23
24
  from lino.core.menus import Menu
24
25
  from lino.utils.html import html2text
25
26
  from lino.utils import dbhash
26
- from lino.core.utils import full_model_name, get_models
27
+ from lino.core.utils import get_models
28
+ from lino.core.utils import full_model_name
29
+ from lino.core.utils import full_model_name as fmn
27
30
  from lino.utils.diag import visible_for
28
31
  from lino.sphinxcontrib.actordoc import menuselection_text
29
32
  from lino import logger
@@ -746,7 +749,7 @@ def show_change_watchers():
746
749
  ws = m.change_watcher_spec
747
750
  if ws:
748
751
  rows.append(
749
- [full_model_name(m), ws.master_key, " ".join(sorted(ws.ignored_fields))]
752
+ [fmn(m), ws.master_key, " ".join(sorted(ws.ignored_fields))]
750
753
  )
751
754
  print(rstgen.table(headers, rows, max_width=40))
752
755
 
@@ -801,3 +804,26 @@ def checkdb(m, num):
801
804
  if m.objects.count() != num:
802
805
  raise Exception(
803
806
  f"Model {m} should have {num} rows but has {m.objects.count()}")
807
+
808
+
809
+ def show_clonables():
810
+ """
811
+ Print a list of all :class:`Clonable <lino.mixins.clonable.Clonable>`
812
+ models, together with their related slaves, i.e. the data that will be
813
+ cloned in cascade with their master.
814
+ """
815
+ items = []
816
+ for m in get_models():
817
+ if issubclass(m, Clonable):
818
+ rels = []
819
+ if (obj := m.objects.first()) is not None:
820
+ new, related = obj.duplication_plan()
821
+ for fk, qs in related:
822
+ rels.append(f"{fmn(qs.model)}.{fk.name}")
823
+ if len(rels):
824
+ x = ", ".join(rels)
825
+ items.append(f"{fmn(m)} : {x}")
826
+ else:
827
+ items.append(fmn(m))
828
+ items = sorted(items)
829
+ print(rstgen.ul(items).strip())
lino/core/actions.py CHANGED
@@ -98,6 +98,13 @@ class Action(Parametrizable, Permittable):
98
98
  raise Exception(
99
99
  "Unkonwn icon_name '{0}'".format(self.icon_name))
100
100
 
101
+ params = {}
102
+ if self.parameters is not None:
103
+ params.update(self.parameters)
104
+ self.setup_parameters(params)
105
+ if len(params):
106
+ self.parameters = params
107
+
101
108
  register_params(self)
102
109
 
103
110
  if self.callable_from is not None:
@@ -151,6 +158,9 @@ class Action(Parametrizable, Permittable):
151
158
 
152
159
  return decorator
153
160
 
161
+ def setup_parameters(self, params):
162
+ pass
163
+
154
164
  def get_help_text(self, ba):
155
165
  if ba is ba.actor.default_action:
156
166
  if self.default_record_id is not None:
lino/core/atomizer.py CHANGED
@@ -309,10 +309,10 @@ def fields_list(model, field_names):
309
309
  Return a set with the names of the specified fields, checking
310
310
  whether each of them exists.
311
311
 
312
- Arguments: `model` is any subclass of `django.db.models.Model`. It
313
- may be a string with the full name of a model
314
- (e.g. ``"myapp.MyModel"``). `field_names` is a single string with
315
- a space-separated list of field names.
312
+ Arguments: `model` is any subclass of `django.db.models.Model`. It may be a
313
+ string with the full name of a model (e.g. ``"myapp.MyModel"``).
314
+ `field_names` is an iterable of field names or a single string with a
315
+ space-separated list of field names.
316
316
 
317
317
  If one of the names refers to a dummy field, this name will be ignored
318
318
  silently.
@@ -326,16 +326,17 @@ def fields_list(model, field_names):
326
326
  iterable on the fields.
327
327
  """
328
328
  lst = set()
329
- names_list = field_names.split()
329
+ if isinstance(field_names, str):
330
+ field_names = field_names.split()
330
331
 
331
- for name in names_list:
332
+ for name in field_names:
332
333
  if name == "*":
333
334
  explicit_names = set()
334
335
  for name in names_list:
335
336
  if name != "*":
336
337
  explicit_names.add(name)
337
338
  for de in fields.wildcard_data_elems(model):
338
- if not isinstance(de, DummyField):
339
+ if not isinstance(de, fields.DummyField):
339
340
  if de.name not in explicit_names:
340
341
  if fields.use_as_wildcard(de):
341
342
  lst.add(de.name)
@@ -343,7 +344,7 @@ def fields_list(model, field_names):
343
344
  e = model.get_data_elem(name)
344
345
  if e is None:
345
346
  raise fields.FieldDoesNotExist(
346
- "No data element %r in %s" % (name, model))
347
+ f"No data element '{name}' in {model}")
347
348
  if not hasattr(e, "name"):
348
349
  raise fields.FieldDoesNotExist(
349
350
  "%s %r in %s has no name" % (e.__class__, name, model)
lino/core/auth/utils.py CHANGED
@@ -73,7 +73,8 @@ class AnonymousUser:
73
73
 
74
74
 
75
75
  def activate_social_auth_testing(
76
- globals_dict, google=True, github=True, wikimedia=True, facebook=True
76
+ globals_dict, google=True, github=True, wikimedia=True, facebook=True,
77
+ smart_id=False
77
78
  ):
78
79
  """
79
80
  Used for testing a development server.
@@ -97,6 +98,13 @@ def activate_social_auth_testing(
97
98
  # 'social_core.backends.google.GoogleOAuth2',
98
99
  # 'social_core.backends.google.GoogleOAuth',
99
100
  # 'social_core.backends.facebook.FacebookOAuth2',
101
+ if smart_id:
102
+ Site.social_auth_backends.append("lino.utils.smart_id.SmartID")
103
+ # https://oauth.ee/docs
104
+ globals_dict.update(
105
+ SOCIAL_AUTH_SMART_ID_KEY="xxx",
106
+ SOCIAL_AUTH_SMART_ID_SECRET="yyy",
107
+ )
100
108
  if wikimedia:
101
109
  Site.social_auth_backends.append("social_core.backends.mediawiki.MediaWiki")
102
110
  globals_dict.update(
lino/core/callbacks.py CHANGED
@@ -72,9 +72,9 @@ class Callback(object):
72
72
  - func: a callable to be executed when user selects this choice
73
73
  - the label of the button
74
74
  """
75
- assert not name in self.choices_dict
75
+ assert name not in self.choices_dict
76
76
  allowed_names = ("yes", "no", "ok", "cancel")
77
- if not name in allowed_names:
77
+ if name not in allowed_names:
78
78
  raise Exception("Sorry, name must be one of %s" % allowed_names)
79
79
  cbc = CallbackChoice(name, func, label)
80
80
  self.choices.append(cbc)
lino/core/fields.py CHANGED
@@ -1492,7 +1492,6 @@ class TableRow(object):
1492
1492
 
1493
1493
  def save_existing_instance(self, ar):
1494
1494
  watcher = ChangeWatcher(self)
1495
- # print("20210213 save_existing_instance", ar.ah, ar.rqdata, self.disabled_fields)
1496
1495
  ar.ah.store.form2obj(ar, ar.rqdata, self, False)
1497
1496
  self.full_clean()
1498
1497
  pre_ui_save.send(sender=self.__class__, instance=self, ar=ar)
lino/core/kernel.py CHANGED
@@ -230,25 +230,15 @@ class Kernel(object):
230
230
 
231
231
  Model.django2lino(model)
232
232
 
233
- if isinstance(model.hidden_columns, str):
234
- model.hidden_columns = frozenset(
235
- atomizer.fields_list(model, model.hidden_columns)
236
- )
237
-
238
- if isinstance(model.active_fields, str):
239
- model.active_fields = frozenset(
240
- atomizer.fields_list(model, model.active_fields)
241
- )
242
-
243
- if isinstance(model.allow_cascaded_delete, str):
244
- model.allow_cascaded_delete = frozenset(
245
- atomizer.fields_list(model, model.allow_cascaded_delete)
246
- )
247
-
248
- if isinstance(model.allow_cascaded_copy, str):
249
- model.allow_cascaded_copy = frozenset(
250
- atomizer.fields_list(model, model.allow_cascaded_copy)
251
- )
233
+ if model.allow_cascaded_copy is None:
234
+ model.allow_cascaded_copy = model.allow_cascaded_delete
235
+
236
+ # 'suppress_cascaded_copy'
237
+ for k in ('hidden_columns', 'active_fields',
238
+ 'allow_cascaded_delete', 'allow_cascaded_copy'):
239
+ # resolve_fields_list(model, k, frozenset, frozenset())
240
+ if (v := getattr(model, k, None)) is not None:
241
+ setattr(model, k, atomizer.fields_list(model, v))
252
242
 
253
243
  # Note how to inherit this from from parent model.
254
244
  if model.quick_search_fields is None:
lino/core/model.py CHANGED
@@ -47,8 +47,9 @@ class Model(models.Model, fields.TableRow):
47
47
  class Meta(object):
48
48
  abstract = True
49
49
 
50
- allow_cascaded_copy = frozenset()
51
50
  allow_cascaded_delete = frozenset()
51
+ allow_cascaded_copy = None
52
+ # suppress_cascaded_copy = frozenset()
52
53
  grid_post = actions.CreateRow()
53
54
  submit_insert = actions.SubmitInsert()
54
55
  allow_merge_action = False
@@ -479,14 +480,20 @@ class Model(models.Model, fields.TableRow):
479
480
  elem.after_ui_save(ar, None)
480
481
 
481
482
  def save_watched_instance(elem, ar, watcher):
483
+ # raise Exception("20250726")
482
484
  if watcher.is_dirty():
483
485
  # pre_ui_save.send(sender=elem.__class__, instance=elem, ar=ar)
484
486
  # elem.before_ui_save(ar, watcher)
485
487
  elem.save(force_update=True)
486
488
  watcher.send_update(ar)
487
489
  ar.success(_("%s has been updated.") % obj2unicode(elem))
488
- else:
489
- ar.success(_("%s : nothing to save.") % obj2unicode(elem))
490
+ # else:
491
+ # ar.success(_("%s : nothing to save.") % obj2unicode(elem))
492
+ # 20250726 The "nothing to save" message is confusing e.g. in
493
+ # ratings.ResponsesByExam when setting score1 or score2. These virtual
494
+ # fields store the value in their respective ChallengeRating object but
495
+ # the examResponse remains unchanged.
496
+
490
497
  elem.after_ui_save(ar, watcher)
491
498
 
492
499
  def delete_instance(self, ar):
@@ -948,6 +955,7 @@ LINO_MODEL_ATTRIBS = (
948
955
  "before_ui_save",
949
956
  "allow_cascaded_delete",
950
957
  "allow_cascaded_copy",
958
+ # "suppress_cascaded_copy",
951
959
  "workflow_state_field",
952
960
  "workflow_owner_field",
953
961
  "disabled_fields",
lino/core/store.py CHANGED
@@ -203,7 +203,6 @@ class StoreField(object):
203
203
 
204
204
  def set_value_in_object(self, ar, instance, v):
205
205
  # logger.info("20180712 super set_value_in_object() %s", v)
206
-
207
206
  old_value = self.value_from_object(instance, ar.request)
208
207
  # old_value = getattr(instance,self.field.attname)
209
208
  if old_value != v:
@@ -865,8 +864,8 @@ class FileFieldStoreField(StoreField):
865
864
 
866
865
 
867
866
  class MethodStoreField(StoreField):
868
- """Deprecated. See `/blog/2012/0327`.
869
- Still used for DISPLAY_MODE_HTML.
867
+ """
868
+ Still used for DISPLAY_MODE_HTML and writable virtual fields.
870
869
  """
871
870
 
872
871
  def full_value_from_object(self, obj, ar=None):
@@ -898,6 +897,7 @@ class MethodStoreField(StoreField):
898
897
  # pass
899
898
 
900
899
  def form2obj(self, request, instance, post_data, is_new):
900
+ # print("20250726", self, post_data)
901
901
  pass
902
902
  # return instance
903
903
  # raise Exception("Cannot update a virtual field")
lino/core/utils.py CHANGED
@@ -810,29 +810,30 @@ def error2str(self, e):
810
810
 
811
811
 
812
812
  def resolve_fields_list(model, k, collection_type=tuple, default=None):
813
- qsf = getattr(model, k)
814
- if qsf is None:
813
+ value = getattr(model, k)
814
+ if value is None:
815
815
  setattr(model, k, default)
816
- elif qsf == default:
817
- pass
818
- elif isinstance(qsf, collection_type):
819
- pass
820
- elif isinstance(qsf, str):
816
+ return
817
+ elif value == default:
818
+ return
819
+ elif isinstance(value, collection_type):
820
+ return
821
+ if isinstance(value, str):
822
+ value = value.split()
823
+ if isinstance(value, (list, tuple)):
821
824
  lst = []
822
- for n in qsf.split():
825
+ for n in value:
823
826
  f = model.get_data_elem(n)
824
827
  if f is None:
825
- msg = "Invalid field {} in {} of {}"
826
- msg = msg.format(n, k, model)
828
+ msg = f"Invalid field {n} in {k} of {model}"
827
829
  raise Exception(msg)
828
830
  lst.append(f)
829
831
  setattr(model, k, collection_type(lst))
830
832
  # fields.fields_list(model, model.quick_search_fields))
831
833
  else:
832
834
  raise ChangedAPI(
833
- "{0}.{1} must be None or a string "
834
- "of space-separated field names (not {2})".format(model, k, qsf)
835
- )
835
+ f"{model}.{k} must be None or a string "
836
+ f"of space-separated field names (not {value})")
836
837
 
837
838
 
838
839
  def class_dict_items(cl, exclude=None):
lino/help_texts.py CHANGED
@@ -17,6 +17,8 @@ help_texts = {
17
17
  'lino.mixins.ProjectRelated' : _("""Mixin for models that are related to a “project”, i.e. to an object of the type given by your lino.core.site.Site.project_model."""),
18
18
  'lino.mixins.ProjectRelated.project' : _("""Pointer to the project to which this object is related."""),
19
19
  'lino.mixins.ProjectRelated.update_owned_instance' : _("""When a project-related object controls another project-related object, then the controlled automatically inherits the project of its controller."""),
20
+ 'lino.mixins.clonable.CloneRow' : _("""See /dev/duplicate."""),
21
+ 'lino.mixins.clonable.Clonable' : _("""See /dev/duplicate."""),
20
22
  'lino.mixins.dupable.CheckedSubmitInsert' : _("""Like the standard lino.core.actions.SubmitInsert, but adds a confirmation if there is a possible duplicate record."""),
21
23
  'lino.mixins.dupable.PhoneticWordBase' : _("""Base class for the table of phonetic words of a given dupable model. For every (non-abstract) dupable model there must be a subclass of PhoneticWordBase. The subclass must define a field owner which points to the Dupable, and the Dupable’s dupable_word_model must point to its subclass of PhoneticWordBase."""),
22
24
  'lino.mixins.dupable.Dupable' : _("""Base class for models that can be “dupable”."""),
@@ -30,9 +32,6 @@ help_texts = {
30
32
  'lino.mixins.dupable.DupableChecker' : _("""Checks for the following repairable problem:"""),
31
33
  'lino.mixins.dupable.DupableChecker.model' : _("""alias of Dupable"""),
32
34
  'lino.mixins.dupable.SimilarObjects' : _("""Shows the other objects that are similar to this one."""),
33
- 'lino.mixins.duplicable.Duplicate' : _("""Duplicate the selected row."""),
34
- 'lino.mixins.duplicable.Duplicate.run_from_ui' : _("""This actually runs the action."""),
35
- 'lino.mixins.duplicable.Duplicable' : _("""Adds a row action “Duplicate”, which duplicates (creates a clone of) the object it was called on."""),
36
35
  'lino.mixins.human.Human' : _("""Base class for models that represent a human."""),
37
36
  'lino.mixins.human.Human.title' : _("""Used to specify a professional position or academic qualification like “Dr.” or “PhD”."""),
38
37
  'lino.mixins.human.Human.first_name' : _("""The first name, also known as given name."""),
@@ -87,10 +86,9 @@ help_texts = {
87
86
  'lino.mixins.sequenced.MoveByN' : _("""Move this row N rows upwards or downwards."""),
88
87
  'lino.mixins.sequenced.MoveUp' : _("""Move this row one row upwards."""),
89
88
  'lino.mixins.sequenced.MoveDown' : _("""Move this row one row downwards."""),
90
- 'lino.mixins.sequenced.DuplicateSequenced' : _("""Duplicate this row."""),
91
89
  'lino.mixins.sequenced.Sequenced' : _("""Mixin for models that have a field seqno containing a “sequence number”."""),
92
90
  'lino.mixins.sequenced.Sequenced.seqno' : _("""The sequence number of this item with its parent."""),
93
- 'lino.mixins.sequenced.Sequenced.duplicate' : _("""Create a duplicate of this object and insert the new object below this one."""),
91
+ 'lino.mixins.sequenced.Sequenced.clone_row' : _("""Create a duplicate of this row and insert the new row below this one."""),
94
92
  'lino.mixins.sequenced.Sequenced.move_up' : _("""Exchange the seqno of this item and the previous item."""),
95
93
  'lino.mixins.sequenced.Sequenced.move_down' : _("""Exchange the seqno of this item and the next item."""),
96
94
  'lino.mixins.sequenced.Sequenced.move_buttons' : _("""Displays buttons for certain actions on this row:"""),
@@ -311,7 +309,6 @@ help_texts = {
311
309
  'lino.core.model.Model.submit_insert' : _("""The SubmitInsert action to be executed when the when the users submits an insert window."""),
312
310
  'lino.core.model.Model.create_from_choice' : _("""Called when a learning combo has been submitted. Create a persistent database object if the given text contains enough information."""),
313
311
  'lino.core.model.Model.choice_text_to_dict' : _("""Return a dict of the fields to fill when the given text contains enough information for creating a new database object."""),
314
- 'lino.core.model.Model.allow_cascaded_delete' : _("""A set of names of ForeignKey or GenericForeignKey fields of this model that allow for cascaded delete."""),
315
312
  'lino.core.model.Model.__str__' : _("""Return a translatable text that describes this database row."""),
316
313
  'lino.core.model.Model.as_str' : _("""Return a translatable text that describes this database row. Unlike __str__() this method gets an action request when it is called, so it knows the context."""),
317
314
  'lino.core.model.Model.get_str_words' : _("""Yield a series of words that describe this database row in plain text."""),
@@ -717,4 +714,9 @@ help_texts = {
717
714
  'lino.core.model.Model.get_choices_text' : _("""Return the text to be displayed when an instance of this model is being used as a choice in a combobox of a ForeignKey field pointing to this model. request is the web request, actor is the requesting actor."""),
718
715
  'lino.core.model.Model.disable_delete' : _("""Decide whether this database object may be deleted. Return None when there is no veto against deleting this database row, otherwise a translatable message that explains to the user why they can’t delete this row."""),
719
716
  'lino.core.model.Model.update_field' : _("""Shortcut to call lino.core.inject.update_field() for usage during lino.core.site.Site.do_site_startup() in a settings.py or similar place."""),
717
+ 'lino.core.model.Model.on_duplicate' : _("""Called after duplicating a row."""),
718
+ 'lino.core.model.Model.after_duplicate' : _("""Called by lino.mixins.clonable.Duplicate on the new copied row instance, after the row and its related fields have been saved."""),
719
+ 'lino.core.model.Model.delete_veto_message' : _("""Return the message Cannot delete X because N Ys refer to it."""),
720
+ 'lino.core.model.Model.allow_cascaded_delete' : _("""A set of foreign key fields that link this model to a master in a “possessive” way, i.e. objects of this model should get deleted together with their master."""),
721
+ 'lino.core.model.Model.allow_cascaded_copy' : _("""A set of foreign key fields that that cause database rows to be automatically cloned when their master gets cloned."""),
720
722
  }
lino/mixins/__init__.py CHANGED
@@ -8,8 +8,9 @@ by applications and the :ref:`xl`. But none of them is mandatory.
8
8
  .. autosummary::
9
9
  :toctree:
10
10
 
11
- duplicable
11
+
12
12
  dupable
13
+ clonable
13
14
  sequenced
14
15
  human
15
16
  periods
@@ -50,7 +51,7 @@ from .polymorphic import Polymorphic
50
51
  from .periods import ObservedDateRange, Yearly, Monthly, Today
51
52
  from .periods import DateRange
52
53
  from .sequenced import Sequenced, Hierarchical
53
- from .duplicable import Duplicable, Duplicate
54
+ from .clonable import Clonable, CloneRow
54
55
  from .registrable import Registrable, RegistrableState
55
56
  from .ref import Referrable, StructuredReferrable
56
57
 
@@ -1,13 +1,10 @@
1
1
  # -*- coding: UTF-8 -*-
2
2
  # Copyright 2012-2025 Rumma & Ko Ltd
3
3
  # License: GNU Affero General Public License v3 (see file COPYING for details)
4
- """Defines the model mixin :class:`Duplicable`. "duplicable"
5
- [du'plikəblə] means "able to produce a duplicate
6
- ['duplikət], ['du:plikeit]".
7
-
8
- """
4
+ "See :doc:`/dev/duplicate`."
9
5
 
10
6
  from django.utils.translation import gettext_lazy as _
7
+ from django.utils.text import format_lazy
11
8
 
12
9
  from lino import logger
13
10
  from lino.core import actions
@@ -16,16 +13,12 @@ from lino.core.diff import ChangeWatcher
16
13
  # from lino.core.roles import Expert
17
14
 
18
15
 
19
- class Duplicate(actions.Action):
20
- """Duplicate the selected row.
21
-
22
- This will call :meth:`lino.core.model.Model.on_duplicate` on the
23
- new object and on related objects.
16
+ class CloneRow(actions.Action):
17
+ "See :doc:`/dev/duplicate`."
24
18
 
25
- """
26
-
27
- label = "\u2687" # ⚇ "white circle with two dots"
28
- # label = _("Duplicate")
19
+ # button_text = "" # \u2687 "white circle with two dots"
20
+ button_text = "🗗" # OVERLAP (U+1F5D7)
21
+ label = _("Duplicate")
29
22
  # icon_name = 'arrow_divide'
30
23
  sort_index = 11
31
24
  show_in_workflow = False
@@ -34,6 +27,11 @@ class Duplicate(actions.Action):
34
27
 
35
28
  # required_roles = set([Expert])
36
29
 
30
+ def get_help_text(self, ba):
31
+ return format_lazy(
32
+ _("Insert a new {} as a copy of this."),
33
+ ba.actor.model._meta.verbose_name)
34
+
37
35
  def get_action_view_permission(self, actor, user_type):
38
36
  # the action is readonly because it doesn't write to the
39
37
  # current object, but since it does modify the database we
@@ -48,30 +46,9 @@ class Duplicate(actions.Action):
48
46
  return super().get_action_view_permission(actor, user_type)
49
47
 
50
48
  def run_from_code(self, ar, **known_values):
49
+ # CloneSequenced uses known_values to set seqno to seqno + 1
51
50
  obj = ar.selected_rows[0]
52
- related = []
53
- for m, fk in obj._lino_ddh.fklist:
54
- # print(fk.name, m.allow_cascaded_delete, m.allow_cascaded_copy, obj)
55
- if fk.name in m.allow_cascaded_delete or fk.name in m.allow_cascaded_copy:
56
- related.append((fk, m.objects.filter(**{fk.name: obj})))
57
-
58
- fields_list = obj._meta.concrete_fields
59
- if True:
60
- for f in fields_list:
61
- if not f.primary_key:
62
- if f.name not in known_values:
63
- known_values[f.name] = getattr(obj, f.name)
64
- new = obj.__class__(**known_values)
65
- # 20120704 create_instances causes fill_from_person() on a
66
- # CBSS request.
67
- else:
68
- # doesn't seem to want to work
69
- new = obj
70
- for f in fields_list:
71
- if f.primary_key:
72
- # causes Django to consider this an unsaved instance
73
- setattr(new, f.name, None)
74
-
51
+ new, related = obj.duplication_plan(**known_values)
75
52
  new.on_duplicate(ar, None)
76
53
  new.full_clean()
77
54
  new.save(force_insert=True)
@@ -94,11 +71,9 @@ class Duplicate(actions.Action):
94
71
 
95
72
  logger.info("%s has been duplicated to %s (%d related rows)",
96
73
  obj, new, relcount)
97
-
98
74
  return new
99
75
 
100
76
  def run_from_ui(self, ar, **kw):
101
- """This actually runs the action."""
102
77
 
103
78
  if (msg := ar.actor.model.disable_create(ar)) is not None:
104
79
  ar.error(msg)
@@ -126,18 +101,38 @@ class Duplicate(actions.Action):
126
101
  )
127
102
 
128
103
 
129
- class Duplicable(model.Model):
130
- """Adds a row action "Duplicate", which duplicates (creates a clone
131
- of) the object it was called on.
104
+ class Clonable(model.Model):
105
+ "See :doc:`/dev/duplicate`."
132
106
 
133
- Subclasses may override :meth:`lino.core.model.Model.on_duplicate`
134
- to customize the default behaviour, which is to copy all fields
135
- except the primary key and all related objects that are
136
- duplicable.
107
+ class Meta:
108
+ abstract = True
137
109
 
138
- """
110
+ clone_row = CloneRow()
139
111
 
140
- class Meta(object):
141
- abstract = True
112
+ def duplication_plan(obj, **known_values):
113
+ related = []
114
+ for m, fk in obj._lino_ddh.fklist:
115
+ # if fk.name in m.suppress_cascaded_copy:
116
+ # continue
117
+ # print(fk.name, m.allow_cascaded_delete, m.allow_cascaded_copy, obj)
118
+ # if fk.name in m.allow_cascaded_delete or fk.name in m.allow_cascaded_copy:
119
+ if fk.name in m.allow_cascaded_copy:
120
+ related.append((fk, m.objects.filter(**{fk.name: obj})))
142
121
 
143
- duplicate = Duplicate()
122
+ fields_list = obj._meta.concrete_fields
123
+ if True:
124
+ for f in fields_list:
125
+ if not f.primary_key:
126
+ if f.name not in known_values:
127
+ known_values[f.name] = getattr(obj, f.name)
128
+ new = obj.__class__(**known_values)
129
+ # 20120704 create_instances causes fill_from_person() on a
130
+ # CBSS request.
131
+ else:
132
+ # doesn't seem to want to work
133
+ new = obj
134
+ for f in fields_list:
135
+ if f.primary_key:
136
+ # causes Django to consider this an unsaved instance
137
+ setattr(new, f.name, None)
138
+ return new, related
lino/mixins/dupable.py CHANGED
@@ -5,8 +5,8 @@
5
5
  Defines the :class:`Dupable` model mixin and related functionality
6
6
  to assist users in finding unwanted duplicate database records.
7
7
 
8
- Don't mix up this module with :mod:`lino.mixins.duplicable`. Models
9
- are "duplicable" if users may *want* to duplicate some instance
8
+ Don't mix up this module with :mod:`lino.mixins.`. Models
9
+ are "" if users may *want* to duplicate some instance
10
10
  thereof, while "dupable" implies that the duplicates are *unwanted*.
11
11
  To dupe *somebody* means "to make a dupe of; deceive; delude; trick."
12
12
  (`reference.com <https://dictionary.reference.com/browse/dupe>`_), and
@@ -143,7 +143,7 @@ class Registrable(model.Model):
143
143
  # if not ar.bound_action.action.readonly:
144
144
  if not ba.action.readonly:
145
145
  return False
146
- return super(Registrable, self).get_row_permission(ar, state, ba)
146
+ return super().get_row_permission(ar, state, ba)
147
147
 
148
148
  def register(self, ar):
149
149
  """
@@ -193,7 +193,7 @@ class Registrable(model.Model):
193
193
 
194
194
  @classmethod
195
195
  def get_simple_parameters(cls):
196
- for p in super(Registrable, cls).get_simple_parameters():
196
+ for p in super().get_simple_parameters():
197
197
  yield p
198
198
  # if isinstance(cls.workflow_state_field, str):
199
199
  # raise Exception("Unresolved workflow state field in {}".format(cls))
@@ -203,4 +203,4 @@ class Registrable(model.Model):
203
203
 
204
204
  def on_duplicate(self, ar, master):
205
205
  self.state = self.workflow_state_field.choicelist.draft
206
- super(Registrable, self).on_duplicate(ar, master)
206
+ super().on_duplicate(ar, master)
lino/mixins/sequenced.py CHANGED
@@ -24,7 +24,7 @@ from lino.utils.html import E
24
24
  from lino.utils import AttrDict
25
25
  from lino.utils import join_elems
26
26
 
27
- from .duplicable import Duplicable, Duplicate
27
+ from lino.mixins.clonable import Clonable, CloneRow
28
28
 
29
29
 
30
30
  class MoveByN(actions.Action):
@@ -167,8 +167,7 @@ class MoveDown(actions.Action):
167
167
  ar.success(**kw)
168
168
 
169
169
 
170
- class DuplicateSequenced(Duplicate):
171
- """Duplicate this row."""
170
+ class CloneSequenced(CloneRow):
172
171
 
173
172
  def run_from_code(self, ar, **kw):
174
173
  obj = ar.selected_rows[0]
@@ -184,7 +183,7 @@ class DuplicateSequenced(Duplicate):
184
183
  return super().run_from_code(ar, **kw)
185
184
 
186
185
 
187
- class Sequenced(Duplicable):
186
+ class Sequenced(Clonable):
188
187
  """Mixin for models that have a field :attr:`seqno` containing a
189
188
  "sequence number".
190
189
 
@@ -192,12 +191,11 @@ class Sequenced(Duplicable):
192
191
 
193
192
  The sequence number of this item with its parent.
194
193
 
195
- .. method:: duplicate
194
+ .. method:: clone_row
196
195
 
197
- Create a duplicate of this object and insert the new object
198
- below this one.
196
+ Create a duplicate of this row and insert the new row below this one.
199
197
 
200
- Implemented by :class:`DuplicateSequenced`
198
+ Implemented by :class:`CloneSequenced`
201
199
 
202
200
  .. attribute:: move_up
203
201
 
@@ -212,28 +210,28 @@ class Sequenced(Duplicable):
212
210
  Displays buttons for certain actions on this row:
213
211
 
214
212
  - :attr:`move_up` and :attr:`move_down`
215
- - duplicate
213
+ - clone_row
216
214
 
217
215
  .. attribute:: move_by_n
218
216
 
219
217
  """
220
218
 
221
- move_action_names = ("move_up", "move_down", "duplicate")
219
+ move_action_names = ("move_up", "move_down", "clone_row")
222
220
  """The names of the actions to display in the `move_buttons`
223
221
  column.
224
222
 
225
223
  Overridden by :class:`lino.modlib.dashboard.Widget` where the
226
- duplicate button would be irritating.
224
+ clone_row button would be irritating.
227
225
 
228
226
  """
229
227
 
230
- class Meta(object):
228
+ class Meta:
231
229
  abstract = True
232
230
  ordering = ["seqno"]
233
231
 
234
232
  seqno = models.IntegerField(_("No."), blank=True, null=False)
235
233
 
236
- duplicate = DuplicateSequenced()
234
+ clone_row = CloneSequenced()
237
235
 
238
236
  move_up = MoveUp()
239
237
  move_down = MoveDown()
@@ -352,7 +350,7 @@ Sequenced.set_widget_options("move_buttons", width=5)
352
350
  Sequenced.set_widget_options("seqno", hide_sum=True)
353
351
 
354
352
 
355
- class Hierarchical(Duplicable):
353
+ class Hierarchical(Clonable):
356
354
  """Model mixin for things that have a "parent" and "siblings".
357
355
 
358
356
  Pronounciation: [hai'ra:kikl]
@@ -4,8 +4,8 @@
4
4
  """Defines the :class:`Dupable` model mixin and related functionality
5
5
  to assist users in finding unwanted duplicate database records.
6
6
 
7
- Don't mix up this module with :mod:`lino.mixins.duplicable`. Models
8
- are "duplicable" if users may *want* to duplicate some instance
7
+ Don't mix up this module with :mod:`lino.mixins.`. Models
8
+ are "" if users may *want* to duplicate some instance
9
9
  thereof, while "dupable" implies that the duplicates are *unwanted*.
10
10
  To dupe *somebody* means "to make a dupe of; deceive; delude; trick."
11
11
  (`reference.com <https://dictionary.reference.com/browse/dupe>`_), and
lino/modlib/notify/api.py CHANGED
@@ -8,6 +8,7 @@ from django.conf import settings
8
8
  from django.utils.timezone import now
9
9
  from lino.api import rt, dd
10
10
  from lino.modlib.linod.utils import CHANNEL_NAME, BROADCAST_CHANNEL, get_channel_name
11
+ from lino_xl.lib.matrix.utils import send_notification_to_matrix_room, send_notification_direct
11
12
 
12
13
  NOTIFICATION = "NOTIFICATION"
13
14
  CHAT = "CHAT"
@@ -67,6 +68,10 @@ def send_notification(
67
68
  if user is None or settings.SITE.loading_from_dump:
68
69
  return
69
70
 
71
+ if dd.is_installed("matrix") and dd.plugins.matrix.credentials_file is not None:
72
+ # send_notification_to_matrix_room(f"{subject}\n\n{body}")
73
+ send_notification_direct(f"{subject}\n\n{body}", user)
74
+
70
75
  if dd.get_plugin_setting("linod", "use_channels"):
71
76
  # importing channels at module level would cause certain things to fail
72
77
  # when channels isn't installed, e.g. `manage.py prep` in `lino_book.projects.workflows`.
@@ -16,7 +16,7 @@ from lino.modlib.checkdata.choicelists import Checker
16
16
 
17
17
  from lino.utils.choosers import chooser
18
18
  from lino.core.model import Model
19
- from lino.mixins.duplicable import Duplicable
19
+ from lino.mixins.clonable import Clonable
20
20
 
21
21
  from .choicelists import BuildMethods
22
22
  from .actions import (
@@ -148,7 +148,7 @@ class Printable(Model):
148
148
  return True
149
149
 
150
150
 
151
- class CachedPrintable(Duplicable, Printable):
151
+ class CachedPrintable(Clonable, Printable):
152
152
  class Meta(object):
153
153
  abstract = True
154
154
 
@@ -38,7 +38,8 @@ class UpdateSummariesByMaster(ComputeResults):
38
38
 
39
39
 
40
40
  class Summarized(dd.Model):
41
- class Meta(object):
41
+
42
+ class Meta:
42
43
  abstract = True
43
44
 
44
45
  compute_results = ComputeResults()
@@ -86,15 +87,17 @@ class Summarized(dd.Model):
86
87
 
87
88
  def get_summary_collectors(self):
88
89
  raise NotImplementedError(
89
- "{} must define get_summary_collectors()".format(self.__class__)
90
+ f"{self.__class__} must define get_summary_collectors()"
90
91
  )
91
92
 
92
93
 
93
94
  class SlaveSummarized(Summarized):
94
- class Meta(object):
95
+
96
+ class Meta:
95
97
  abstract = True
96
98
 
97
99
  allow_cascaded_delete = "master"
100
+ allow_cascaded_copy = set()
98
101
 
99
102
  @classmethod
100
103
  def check_all_summaries(cls):
@@ -181,7 +184,6 @@ class DateSummarized(Summarized):
181
184
  flt.update(month=period)
182
185
  if year is not None:
183
186
  flt.update(year=year)
184
- # obj = cls.get_for_period(**flt)
185
187
  for obj in cls.get_for_filter(**flt):
186
188
  obj.compute_summary_values()
187
189
 
@@ -495,6 +495,11 @@ def get_social_auth_links_func(content_header, flex_row, ar=None):
495
495
  else:
496
496
  el = E.span(anchor, style=style)
497
497
  links.append(el)
498
+ anchor = E.a(E.span(gettext("Smart ID")), href="/auth/smart_id")
499
+ if flex_row:
500
+ links.append(E.div(anchor, style=style))
501
+ else:
502
+ links.append(E.span(anchor, style=style))
498
503
  elems.append(E.div(*links, style="text-align: center;"))
499
504
  return tostring(E.div(*elems))
500
505
 
@@ -102,7 +102,7 @@ def configure(globals_dict, django_settings_module=None):
102
102
  rstgen.sphinxconf.configure()
103
103
 
104
104
  You can specify an additional positional argument `django_settings_module`
105
- (the name of a Django settings module). If this argument is specified, call
105
+ (the name of a Django settings file). If this argument is specified, call
106
106
  :meth:`lino.startup` with it.
107
107
 
108
108
  """
@@ -11,7 +11,7 @@ for a Lino application.
11
11
  source code. This is like :rst:dir:`py2rst` but with the following
12
12
  names defined:
13
13
 
14
- :settings: The Django settings module which is active while building
14
+ :settings: The Django settings file which is active while building
15
15
  the docs.
16
16
 
17
17
  :dd: The :mod:`lino.api.dd` module.
lino/utils/diag.py CHANGED
@@ -341,9 +341,9 @@ class Analyzer(object):
341
341
  return "{0}.{1}".format(fmn(mfk[0]), mfk[1].name)
342
342
 
343
343
  items1 = []
344
- for target, dp in list(tdp.items()):
344
+ for target, dp in tdp.items():
345
345
  items2 = []
346
- for dh, pl in list(dp.items()):
346
+ for dh, pl in dp.items():
347
347
  items2.append(
348
348
  "{0} : {1}".format(
349
349
  dh.__name__, ", ".join([fk2str(mfk) for mfk in pl])
@@ -215,13 +215,33 @@ def create_and_get(model, **kw):
215
215
  def make_if_needed(model, **values):
216
216
  qs = model.objects.filter(**values)
217
217
  if qs.count() == 1:
218
- pass # ok, nothing to do
218
+ return qs.first()
219
+ # pass # ok, nothing to do
219
220
  elif qs.count() == 0:
220
221
  return model(**values)
221
222
  else:
222
223
  raise Exception(f"Multiple {model._meta.verbose_name_plural} for {values}")
223
224
 
224
225
 
226
+ def get_or_create(model, **kwargs):
227
+ # similar to Djanop's QuerySet.get_or_create() but calls full_clean
228
+ try:
229
+ obj = model.objects.get(**kwargs)
230
+ except model.DoesNotExist:
231
+ obj = model(**kwargs)
232
+ obj.full_clean()
233
+ obj.save()
234
+ return obj
235
+
236
+
237
+ def update_or_create(m, **kwargs):
238
+ if (obj := m.objects.filter(id=kwargs['id']).first()) is not None:
239
+ for k, v in kwargs.items():
240
+ setattr(obj, k, v)
241
+ return obj
242
+ return m(**kwargs)
243
+
244
+
225
245
  def _test():
226
246
  import doctest
227
247
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lino
3
- Version: 25.7.2
3
+ Version: 25.7.3
4
4
  Summary: A framework for writing desktop-like web applications using Django and ExtJS or React
5
5
  Project-URL: Homepage, https://www.lino-framework.org
6
6
  Project-URL: Repository, https://gitlab.com/lino-framework/lino
@@ -1,14 +1,14 @@
1
1
  lino/.cvsignore,sha256=1vrrWoP-WD8hPfCszHHIiJEi8KUMRCt5WvoKB9TSB1k,28
2
2
  lino/SciTEDirectory.properties,sha256=rCYi_e-6h8Yx5DwXhAa6MBPlVINcl6Vv9BQDYZV2_go,28
3
- lino/__init__.py,sha256=odCk-YexuT5Jr0q8uV4WtTzrvQX1tAWdIVO-yZuaEYo,6176
3
+ lino/__init__.py,sha256=Z8-O-349Z6KOAuKypQdw30uEIz-6BXs2vR96MDswJSQ,6176
4
4
  lino/ad.py,sha256=AQ-vJ4scac1mx3xegXezxnxyOQpV-a0q3VFMJSDbj2s,142
5
5
  lino/apps.py,sha256=ECq-dPARDkuhngwNrcipse3b4Irj70HxJs44uWEZFc4,27
6
6
  lino/hello.py,sha256=7-PJg7PnEiznyETqGjOwXcKh8rda0qLetpbS2gvRYy0,532
7
- lino/help_texts.py,sha256=i-Dm3syEE7R8AZy50L5oIyG2VKXoifvLHeCwMTNJceI,92136
7
+ lino/help_texts.py,sha256=vGF0OWysfFG0MYrTV9N1zqCQL-1T45aIS1iHR5Ypuxw,92484
8
8
  lino/api/__init__.py,sha256=WmzHU-rHdZ68se_nI0SmepQTGE8-cd9tPpovHRH9aag,512
9
9
  lino/api/ad.py,sha256=F6SrcKPRRalHKOZ7QLwsRWGq9hhykQIeo0b85cEk9NQ,314
10
10
  lino/api/dd.py,sha256=w-5qIpWdXu5yXyCC4Qh4ApB3yZTn0AwdxAjEhH8PyVE,7510
11
- lino/api/doctest.py,sha256=xyVvEuW8udXaCa699rgD_25kLWKlFejxlbuwwqzM1mQ,25244
11
+ lino/api/doctest.py,sha256=XPcuBVeBQyjsR789G9nIhgGzXKSNAq0MJIstPlhuQdU,26119
12
12
  lino/api/rt.py,sha256=OCYWhrWnMcL988MdvBLBEP8qKQJEGXQhVoam_X0sotU,1376
13
13
  lino/api/selenium.py,sha256=bOu8UaNz3Q7lGVvxjmvrtYtSWn1xfI1f5MN5sVcdYr8,9383
14
14
  lino/api/shell.py,sha256=epyjwEZ396TiJ0AHqhVIvzX8TBIXU8xR4UHJlYOrRhc,536
@@ -28,11 +28,11 @@ lino/config/unused/403.html,sha256=ePwDIUXhz1iD8xXWWyt5xEvpcGIHU4LMnXma8x0ik1c,2
28
28
  lino/config/unused/404.html,sha256=GOJrAyF6NcM69ETdSHgjff_-lvYs_-bOYhyZBem7x3I,220
29
29
  lino/config/unused/500.html,sha256=aWmP37uPoMS-PJgPuBloxdx0nEreU7AvkXxsex3yVYs,544
30
30
  lino/core/__init__.py,sha256=I4X69XK6Y1MZ8X6tC13Wmg13C3r5iTfYcFDiPJKpUdw,726
31
- lino/core/actions.py,sha256=3lJTiTQKEArzPr-MhuPF70IlpZgPmOZBBNQncLwfOZQ,32370
31
+ lino/core/actions.py,sha256=opZ9_EyFaFxXk8prwzVnurFqmswFsKrf6ms38Rlc_wQ,32627
32
32
  lino/core/actors.py,sha256=hbbzTpP7iBjQ2Eup4JhV5zx8w64arqPpkv4RnCptyKY,72585
33
- lino/core/atomizer.py,sha256=T2hrXJoGRy_mYfYT4fILcicpNZ1v-wMw8iF7Zqq-tNQ,13401
33
+ lino/core/atomizer.py,sha256=yK_l9-g8RZIjy2_iBB_efpyO1CrvegCBbjobD5mTTVc,13476
34
34
  lino/core/boundaction.py,sha256=06NNPjCesEr-R1YQKkiuy8JKzDrMJJ948x9jczOkZqY,7850
35
- lino/core/callbacks.py,sha256=uu1-znzxVDD-JETUebw-hYsNg_9ExQb1vfwbc7Psjro,7549
35
+ lino/core/callbacks.py,sha256=45lg153pzY7w94XLvh7QqHH7qOH5yTxLyRH7Vn_l1Ec,7549
36
36
  lino/core/choicelists.py,sha256=5Xu3M5ZVOis2JoNSuNiJGBHdkqCwLofUxSd19iLIlKs,36503
37
37
  lino/core/classproperty.py,sha256=_E95WPAs7BWbAuFpPvoYM2ZwW_mbq3rvF7o43WsMq_8,4316
38
38
  lino/core/constants.py,sha256=GwSyViDk3wClZzgbrCo6N-JYOa3LCP46FSof-iZ5RRU,5085
@@ -43,17 +43,17 @@ lino/core/ddh.py,sha256=dYScxWKTOCDEgow7wJNJe812ESasmmITPK2ovraBQno,3172
43
43
  lino/core/diff.py,sha256=XQ-oQQDS_v3kXd4eRP9Hwr5UCgp-TPZIPVav9ZblUno,5882
44
44
  lino/core/elems.py,sha256=vGAqGwWkNUG9mtQkeu-EOlqPpOHyvnI6uhHn9ivsNdw,110631
45
45
  lino/core/exceptions.py,sha256=QDxDo5cllSyXQ8VWet9hGXzNadxCOmwMVrFXc6V-vpE,665
46
- lino/core/fields.py,sha256=kM6HL3e8S3tdIljXbJd1us4hjR_ZdjxbKu76Jdgd6bU,54866
46
+ lino/core/fields.py,sha256=r8ZySAvyn2ZCkppPdgTVmqa0IPygM6xWU9GEgoCSQ-A,54775
47
47
  lino/core/frames.py,sha256=ISxgq9zyZfqW3tDZMWdKi9Ij455lT_81qBH0xex0bfE,1161
48
48
  lino/core/gfks.py,sha256=6VXn2FSIXOrwVq0stfbPevT37EWg1tg4Fn-HMNVnbmk,1970
49
49
  lino/core/help.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
50
50
  lino/core/inject.py,sha256=Qd_PGEn0yMXNYVPI0wCv1vvo2CNdlPkyoBmKZELOtGM,13422
51
- lino/core/kernel.py,sha256=YWsw9_6RmJtJT1dlg9zaAzBbnjFRjJKHL7qGyM-lyNc,48831
51
+ lino/core/kernel.py,sha256=WbVyCANXcth5b-QGknJjidWc2mlmNjEUXiHStXzm8c0,48522
52
52
  lino/core/keyboard.py,sha256=W3jA6qtB5HMppoNnd_6PgIM7ZlyHilJEhBvdeZTY7Xk,1283
53
53
  lino/core/layouts.py,sha256=CWhKgCEBnjn39RMFRYUklPEvvCnZrDXhtigZpGuiR8o,28176
54
54
  lino/core/menus.py,sha256=oZN93iwQU4vfUCuxQzaVz2xcccN5AG6NG2wGruL74sU,10713
55
55
  lino/core/merge.py,sha256=sKtTeZtHdoDKerdHj4NXuXXNzpKDsfdPaiq-CY0NqQc,9094
56
- lino/core/model.py,sha256=LZE1De2sboRMqlmLycu4utuIsACSeBtG9COaj9CSeXE,37041
56
+ lino/core/model.py,sha256=jcjCuB_Rg3JBvXUHT8f5kz-DlwZDBQla7kB19e1BQBs,37427
57
57
  lino/core/permissions.py,sha256=sNReMhnjIpOZcJ79Z4k-Emp55evblExJZwH-PM-efcA,7579
58
58
  lino/core/plugin.py,sha256=ZuA2d1oa3Mqx_jyPIwIV68cyzawLw_grthliIo83nuM,6835
59
59
  lino/core/renderer.py,sha256=HhRC_gtrapNLw2Xl-cs67YdI_NdEdJ2ULsbvs5gb2wA,48252
@@ -61,12 +61,12 @@ lino/core/requests.py,sha256=_kwmx8VAwdaPssIdsZt_GzCst67BTR7WwVv4z_KCsNo,96389
61
61
  lino/core/roles.py,sha256=PXwk436xUupxdbJcygRSYFu7ixfKjAJPQRUQ8sy0lB0,4425
62
62
  lino/core/signals.py,sha256=ORY2s3Krlh9n24XyHetfwbeUhCqzib6YSqWeFTTY7ps,979
63
63
  lino/core/site.py,sha256=HidiqVqXkTi-OKvNg8tvlR_FEKYEaowZ9h3ly0Zx-Fk,83583
64
- lino/core/store.py,sha256=lv5fNf8LPzYcO1rpS-5T-Dcw6haQrLc9aaIFO7m1ukI,46055
64
+ lino/core/store.py,sha256=bbWrbsV-hPS1FF3e2ABiQHQ_Q61tD2XSRBDSwoTW6jQ,46093
65
65
  lino/core/tables.py,sha256=sjmVu3A8gnamxwy16n76UJy2BXgiqeNeCEbI4oGd93Q,25431
66
66
  lino/core/urls.py,sha256=06QlmN1vpxjmb5snO3SPpP6lX1pMdE60bTiBiC77_vQ,2677
67
67
  lino/core/user_types.py,sha256=0iSYmzr2M9v2Mn2y6hzAZeqareUT-gD7l3MfIPyG9ZI,867
68
68
  lino/core/userprefs.py,sha256=cmufIS9xJErKDrw05uoWtQTUR6WRWIGkU1KdAqzNr5M,2850
69
- lino/core/utils.py,sha256=FAIJPRYFIVoyOrpc9AWwNaM4f5Z8OEgbYwGu6v4RQPA,40462
69
+ lino/core/utils.py,sha256=DD3v75WK3sCmAaRQH3WTX3phbEsunPvepA0O5g1PBaw,40495
70
70
  lino/core/views.py,sha256=qLjEN6GSXScbmAnKN7yDHySmsjL0h4sMKRIQCpOEdPU,7026
71
71
  lino/core/widgets.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
72
72
  lino/core/workflows.py,sha256=6DJ-sCcZ7kwUWYvArD2_j4NZBia0S3OgHySifC-NSuQ,10584
@@ -74,7 +74,7 @@ lino/core/auth/__init__.py,sha256=gbO35DCpyHQV9zjV1l349HQMME9-YgQ1h4amVOHywAc,98
74
74
  lino/core/auth/apps.py,sha256=XCo18f23cEWZiZd4DUgL0JMTgxeERWFuQA8cQFQfUls,47
75
75
  lino/core/auth/backends.py,sha256=IN3UHtfCb0AQqQL6MzIjhu-ESEphNDu1Q86qT48RsBI,8183
76
76
  lino/core/auth/middleware.py,sha256=1c_z4lP_Rhbw7pdCapprGkjIXX40OJgIlFyQew4O-Nc,9708
77
- lino/core/auth/utils.py,sha256=Nv7QbINYi64hGZZxV-Ix1iwhrb_KOfUfhJXAzoKy2ng,4130
77
+ lino/core/auth/utils.py,sha256=wPR0rxkuYuLv7_-eU-Qm5MsQ0nlM8GqlFZF2Iwzam8U,4401
78
78
  lino/core/management/__init__.py,sha256=fp1cONBXgq1IftPk5c4b63gRlYOWpT5VzCIKrx0aGlE,61
79
79
  lino/fake_migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
80
80
  lino/locale/django.pot,sha256=iyeDILZFawKvi1w-4END2Xa9TDBw81ldWgbWegdGC9c,158276
@@ -116,16 +116,16 @@ lino/management/commands/run.py,sha256=MiK53KIACYKDTKF6knJGwU-uzEApGnDxByi-3_nrT
116
116
  lino/management/commands/show.py,sha256=p59mer0ZDhNuasUzJpY0HbKr_st5_BMuwygInzp_yew,1371
117
117
  lino/management/commands/syncscreenshots.py,sha256=XYZhqfm5_RwJzVFNGhHJKucl4j6T6mYo2GsDaUzvjAs,1561
118
118
  lino/management/commands/update_conf.py,sha256=saAaQdPhn3mNOoHcBxOFSf_vBEgM-aopTHq1sJN20Bo,1026
119
- lino/mixins/__init__.py,sha256=rpLMX0MoHv79uaTC7n4aau51CZ1cTOhDXEOQiu2Z0x8,8794
120
- lino/mixins/dupable.py,sha256=9DNZ9xsgSFsxchqzlo86jQVecXABuDeEZ62tamj-X9A,10235
121
- lino/mixins/duplicable.py,sha256=MalSQJuZDRTyFq6XfcnwClaSDCphV4p9d4OesZWpv8o,4637
119
+ lino/mixins/__init__.py,sha256=M5P13IaRIfz-73Lz1EKCArARXHd5Dz8jKoV5c0Z0bZc,8788
120
+ lino/mixins/clonable.py,sha256=6wJeUl_dHuVv60fFVZlCMcqsrFBbo5gGzXNWxQjOAoM,4631
121
+ lino/mixins/dupable.py,sha256=_uXPiD29dYzzzAIPuw0urBRApJ2Y4-kNDJg5ye-lGf4,10215
122
122
  lino/mixins/human.py,sha256=YDIfIHHAaVmzd3uGsJp_vDvkaBWOp09I7i4AGNy9YsQ,13255
123
123
  lino/mixins/periods.py,sha256=b2vucnjFXYalxiDlH_LsSfPwJU5tcluKfFAaLS3vTck,11519
124
124
  lino/mixins/polymorphic.py,sha256=MLbfOeIYRoDZO4048X2oWhG5cxds2pLkwciXcw1xjVQ,9393
125
125
  lino/mixins/printable.py,sha256=4U8M1lrTjUeuaPwrcWoanCBo53iAxiNpSTsVctI-gI0,199
126
126
  lino/mixins/ref.py,sha256=qV8CprGCvwzzWTl6LQc9I1RwD8pziD0F7Ykoaznm8wM,5550
127
- lino/mixins/registrable.py,sha256=RXNaQS76X8_vLs2c57hxQ9BcCuPWF1CBH2T3hjnB61o,7258
128
- lino/mixins/sequenced.py,sha256=6j3slKU_nX2O0gymicNsWAcx93DfpNgg-Pe7Tzp6q24,16630
127
+ lino/mixins/registrable.py,sha256=DYiwiIoYMO488ZGX7C5PRbKz9HBpzk1rG6q9-_WvymQ,7208
128
+ lino/mixins/sequenced.py,sha256=3DvjBEHLHacbBBq6iiVGftcvOPhBVpA9FN_tvNc6Bpo,16567
129
129
  lino/modlib/__init__.py,sha256=cO31gNu2oRkp7o2v3D9gK2H7j4jF9rbVyPxPZhZQwrQ,940
130
130
  lino/modlib/about/__init__.py,sha256=jhqGQIXU1o7KkmmQjfwPKJc3buibB09Fy55carAmi7U,342
131
131
  lino/modlib/about/choicelists.py,sha256=2bxDb2u7cFacBOgLoEWrMmzQ4BJ3x1pmhdgqxzUp-Rw,1424
@@ -184,7 +184,7 @@ lino/modlib/dashboard/__init__.py,sha256=_PFbmakUn8DShXyJY3EfAcuZtX5ofWnS-8dk7s1
184
184
  lino/modlib/dashboard/models.py,sha256=EgRNg88dmz-OlIdi1SyEuerWMsRqKIfqE2MgKL7kApw,3510
185
185
  lino/modlib/dupable/__init__.py,sha256=fIQ8wj-T8ZbkjwQgW_-ankJsHLjPMepOTo32mJXNCvI,532
186
186
  lino/modlib/dupable/mixins.py,sha256=SZ2Exe5q3ANYsP7gnEXQuQczwCDKdRfFvOavreu4mYI,6324
187
- lino/modlib/dupable/models.py,sha256=0watviKwTiVwlArC54V3IxVVfcB1Yg5kO6ed2xCM9a0,4595
187
+ lino/modlib/dupable/models.py,sha256=fcPbWRLwAVOQ4y_E3kaeHUanxmZTS69dlg9TLNCeyZs,4575
188
188
  lino/modlib/export_excel/__init__.py,sha256=HrsrhXjIMvMHRGu8whH3A_WijZWrH35p2cQsFXK60DY,356
189
189
  lino/modlib/export_excel/models.py,sha256=u9COKVdVsPwGk5WjgVarjLSkxmS-KOdohXfH4CruF_c,5353
190
190
  lino/modlib/extjs/__init__.py,sha256=6UBWAWSROwy3DfTXQmVUVJTF6eZ_e2k3BEfE4wtqVhU,10172
@@ -3550,7 +3550,7 @@ lino/modlib/memo/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeu
3550
3550
  lino/modlib/memo/management/commands/removeurls.py,sha256=wZfy-u2ylCF-oKEgaMgvw9zvFIbgV3TZNUcJz4qvE2k,2133
3551
3551
  lino/modlib/notify/__init__.py,sha256=suo7EMVdyTPTPFwuU2KZL25PwIEstTvvR4Turgdz7UE,6926
3552
3552
  lino/modlib/notify/actions.py,sha256=ClRKDjmgx3m43IZ5cx0TdxXN_pU6hLIlo_jCwEW2LhY,2468
3553
- lino/modlib/notify/api.py,sha256=xs5gAqIEJjkRYbRHAIMy47a1gHuIR6ZaPeK_0gk7EcU,4054
3553
+ lino/modlib/notify/api.py,sha256=oZemK9G8Ie6O8iEMDl5mv7BbpCpbfBHbk0xI9125RBY,4366
3554
3554
  lino/modlib/notify/choicelists.py,sha256=7ZkYNMOXNDfyTvdx8Sn-Gsma9f31o-6G1CtivXmQDmA,1324
3555
3555
  lino/modlib/notify/consumers.py,sha256=YaEAhAi_Oq5ovakuP5DI21YIAMvRqQcf4K8MBilcN2w,1529
3556
3556
  lino/modlib/notify/mixins.py,sha256=rwsTgqpzvyiekOqHpxQRTPPdOhv2ZIPrW03YSATfdeE,3903
@@ -3582,7 +3582,7 @@ lino/modlib/periods/fixtures/std.py,sha256=aWzt-frGjzPDwQ2pCKU1nT3oE4xzm7AQ8uLTJ
3582
3582
  lino/modlib/printing/__init__.py,sha256=u1fq44d073-IDH_t8hWs1sQdlAHdsCP85sfEOMSW5L4,689
3583
3583
  lino/modlib/printing/actions.py,sha256=gn4XqIvToXUumymDA20sl7RRsPOejCMu8dKZ3NJJKcE,11669
3584
3584
  lino/modlib/printing/choicelists.py,sha256=UeOVlkYsLV7gxmVWuKqgqrU5zRlYyUck_3ebKDYnqDA,9743
3585
- lino/modlib/printing/mixins.py,sha256=1ETtco9A0Err6Vw46LnQOGEd7bSB5AzC7KCa-O7uXfE,8341
3585
+ lino/modlib/printing/mixins.py,sha256=meu4JvOJ_ErAOOR7qF9tEZZI-9NlRGa3z8f7KG9uHik,8335
3586
3586
  lino/modlib/printing/models.py,sha256=fd-BEKSLpgxPnkh9U7fg2tjNy39exBi3xJC9VzJuXdU,185
3587
3587
  lino/modlib/printing/utils.py,sha256=LUO9769wJvHCPZIqeVQ9XAS6UKJ6BfJbiwO8Dt1kHc4,737
3588
3588
  lino/modlib/printing/config/report/Default.odt,sha256=4X8UD9H_5Th2CELP0C6DTe4g0ZNUPXAg1C00xP3Qluc,10930
@@ -3629,7 +3629,7 @@ lino/modlib/smtpd/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NM
3629
3629
  lino/modlib/smtpd/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3630
3630
  lino/modlib/smtpd/management/commands/recmail.py,sha256=_0co9uNx3Cq0zudrho-630yupuSCRM-Ilc5cIug3exE,1980
3631
3631
  lino/modlib/summaries/__init__.py,sha256=IPoKcanVyKdWx2M1uSekQbsDUcdpw8yHmGKhiWtJIr0,538
3632
- lino/modlib/summaries/mixins.py,sha256=PYD2ITpHbiIAUnMeaAWa1xBfZtSwCHjBQhSoNA7nXpg,5880
3632
+ lino/modlib/summaries/mixins.py,sha256=dUf8Lwo8ZSgXCAeUIXgB2G0PrW7415lBsSzPfZDXzQM,5844
3633
3633
  lino/modlib/summaries/models.py,sha256=vx28VP6ApD9D9tgoemw6x2VTZZNLbMCfnRo75f4jjTw,2039
3634
3634
  lino/modlib/summaries/fixtures/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3635
3635
  lino/modlib/summaries/fixtures/checksummaries.py,sha256=WOBdRAZkdje_2CGJof6BxhSNTQkCgBT0ri4E1heXl-8,382
@@ -4407,7 +4407,7 @@ lino/modlib/uploads/fixtures/demo.py,sha256=rhidbKN4vOyzqnRLMXu3gkx4zAtxGj3FoSz0
4407
4407
  lino/modlib/uploads/fixtures/demo3.py,sha256=q0bwZrx5XtRsRlFpsa33fL0sCl7IdCYaP9E1rhCnJt4,547
4408
4408
  lino/modlib/uploads/fixtures/std.py,sha256=nb5oRcX_WrkTLaGoch6PT7GA0FPKmqbN-BdlPq-hHSc,516
4409
4409
  lino/modlib/users/__init__.py,sha256=P9am0pJURsgCmx9J55q8LFChF8Ga9mody1PtJRXWbvM,4578
4410
- lino/modlib/users/actions.py,sha256=4ln4ojxoesA8i6eAiy-CSa-kQne_DFDCtHZ_3MpVItM,18230
4410
+ lino/modlib/users/actions.py,sha256=NcJVCfIhP3moN2MvhIonyCKzld7ruUtnCX4tFVMK7_c,18445
4411
4411
  lino/modlib/users/choicelists.py,sha256=-X76C1NxIs5e7rFHp5Z0kjJkA1NlOP2vdLKGkI2wZRU,3876
4412
4412
  lino/modlib/users/mixins.py,sha256=Ukib2jUnDR2MVJrASpmzoPS_eke-Z431U78JDPe8ysk,14275
4413
4413
  lino/modlib/users/models.py,sha256=V5r_aY7M7gXfY1wAcIknacwsF8k9vtiMY898-y7GSjE,17842
@@ -4433,8 +4433,8 @@ lino/projects/__init__.py,sha256=clYlClMVuUD0O5h_0rtmolCoA_V4fn7f5bUb6iSJn6I,111
4433
4433
  lino/projects/std/__init__.py,sha256=F1MQWdg7KpmHmKW-vWKRI0kwrkFd1nefWviKf4IIMD0,169
4434
4434
  lino/projects/std/settings.py,sha256=wZBvXavMo3sTsSOj4vm4Kgd9Cw9EgoGYFJJcJ1JzxeA,2831
4435
4435
  lino/projects/std/settings_test.py,sha256=qkxqAanlEvcuqOSuMtO8oGKnevftXflRmAxeyOicuSg,392
4436
- lino/sphinxcontrib/__init__.py,sha256=cz8sRK--NVr9thlToiuHRfJVelqdiYdRYmK2kyQKHqU,3970
4437
- lino/sphinxcontrib/actordoc.py,sha256=CrHbqW15V1w_6Rm1bZzRBZv1CduDRZKpCcJQtOJqOCM,20710
4436
+ lino/sphinxcontrib/__init__.py,sha256=sg03m76tbuhgXW4JranBaVj0HvBPPSVMwFATG413KWA,3968
4437
+ lino/sphinxcontrib/actordoc.py,sha256=Q5tuu1gRN0WJ_m-x_72Tq6UiZXK2_EznB4OoEUnnFwM,20708
4438
4438
  lino/sphinxcontrib/base.py,sha256=pq5u4bFSxMgPm9OMDo8xPVGhuS6MZAUd9bF6rQCCHJs,1193
4439
4439
  lino/sphinxcontrib/help_texts_extractor.py,sha256=mlHyeiIdzbfzQXkT8j8skdcBQ-FV3zAPW2DXnY8Hs1I,10385
4440
4440
  lino/sphinxcontrib/logo/__init__.py,sha256=dCHNLzhlMdSYVMui3vc2ybzPU6D4GikiRP-G9SCu1TA,2257
@@ -4592,7 +4592,7 @@ lino/utils/dates.py,sha256=eWF5WxA5uJf51Y9PKvDVBWD8yIf6yBF6oO6TeU3ujzw,1030
4592
4592
  lino/utils/dbfreader.py,sha256=KrGsBAFV2tF9pAd9jsmBAFpZ-yw-CRymZHEn_q9IL90,13784
4593
4593
  lino/utils/dbhash.py,sha256=tG1IHe6Bz9MaagTI-131gpcLcNw3g642QVvv7GsJH2g,3303
4594
4594
  lino/utils/dblogger.py,sha256=kr0YxQY6veymvNg5A4tsvkqW8haRWdwqL0C-_9_QTg0,721
4595
- lino/utils/diag.py,sha256=IVEn1CVqB_R_kN_hEp_JUQTdHysBVlZ9LCHkKDZ-s8c,18766
4595
+ lino/utils/diag.py,sha256=quiGcv-e0pwn_yjjt7OUMo8kr1qKYmIFBjDnHxr_5X4,18754
4596
4596
  lino/utils/djangotest.py,sha256=Phz1qNp0wDonZRja5dxbCk0Xl3a73gZNiKK8v9tAgZg,8334
4597
4597
  lino/utils/dpy.py,sha256=8eL5SE6YfTFLvNjFJlFr2SpR_eubU0Rb3ckCbGgrsU8,20661
4598
4598
  lino/utils/fieldutils.py,sha256=6GwPOfL-Jv-uh5-tZrTqC1hJccqHhdLbVSy4CAeegDA,2957
@@ -4600,7 +4600,7 @@ lino/utils/format_date.py,sha256=zJu8PO45hGsk6Znq8_93D3vUz9HcY7CjHduAFxoU0v8,312
4600
4600
  lino/utils/html.py,sha256=nR2h6oa_47Baq5rdSln0aGbqzS6SFsWzl-uqjnGIUWU,3273
4601
4601
  lino/utils/html2odf.py,sha256=Hxw4HiIHY1ZCjb4_JLykVHbr6yAMhhHrnrCnLNDYKAs,4826
4602
4602
  lino/utils/html2xhtml.py,sha256=fvrIoLBFpiXtYO3UYaIgAIDjf6ATvrxolQX4etxS57Y,2119
4603
- lino/utils/instantiator.py,sha256=RWC0uG1XkYa7yjQfWl9rmA_g9tErjeO5mM06u-BatnE,7027
4603
+ lino/utils/instantiator.py,sha256=w_yt1ETFa4DruvF3clTUr1xQ-bVCRN4m5oi2zKljj5w,7553
4604
4604
  lino/utils/jinja.py,sha256=1FZgWNKEP9wcokFuIfYysl_VD5mwVHxBHtpnmO17nRQ,1444
4605
4605
  lino/utils/jscompressor.py,sha256=j9UTaaPCfRZLrWUh6PBp0KDDM0QshG7XAFzp-R_elOs,5225
4606
4606
  lino/utils/jsgen.py,sha256=p5BSoCYs67AgD-Q9gmeJp_uQffskT3HXYdp7SrniDsI,15252
@@ -4635,8 +4635,8 @@ lino/utils/xml.py,sha256=EGDnO1UaREst9fS7KTESdbHnrrVCwKbRQdvut6B6GmQ,1612
4635
4635
  lino/utils/mldbc/__init__.py,sha256=QqWRlzeXaOmFfbCk-vTY3SZMn1-FCf67XnpZdd_Nim0,1134
4636
4636
  lino/utils/mldbc/fields.py,sha256=tAX8G5UKigr9c6g0F3ARIjZZtg406mdaZ--PWSbiH9E,2873
4637
4637
  lino/utils/mldbc/mixins.py,sha256=CkYe5jDa7xp9fJq_V8zcZf8ocxgIjUgHc9KZccvA_Yw,1945
4638
- lino-25.7.2.dist-info/METADATA,sha256=r02SpeYvS8PQ1F4MbZXwjctI20OpjU72PE0h51a-wfo,42534
4639
- lino-25.7.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
4640
- lino-25.7.2.dist-info/licenses/AUTHORS.rst,sha256=8VEm_G4HOmYEa4oi1nVoKKsdo4JanekEJCefWd2E8vk,981
4641
- lino-25.7.2.dist-info/licenses/COPYING,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
4642
- lino-25.7.2.dist-info/RECORD,,
4638
+ lino-25.7.3.dist-info/METADATA,sha256=V8-UPwVsw07yLV9_UI7YAxRmj6CntfLiivjBLfswdlo,42534
4639
+ lino-25.7.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
4640
+ lino-25.7.3.dist-info/licenses/AUTHORS.rst,sha256=8VEm_G4HOmYEa4oi1nVoKKsdo4JanekEJCefWd2E8vk,981
4641
+ lino-25.7.3.dist-info/licenses/COPYING,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
4642
+ lino-25.7.3.dist-info/RECORD,,
File without changes