=========================== Saturday, November 14, 2015 =========================== Hamza has been working on :ticket:`601` (Add general timezone support). The topic is new (at last for me) and I have to keep the docs about `Django Time zones `_ and the `pytz `_ package under my pillow for some time. Hamza did not yet file a pull request because he is not finished. But I can see his changes using:: $ git fetch $ git diff master hamza/master Remarks about these changes (I publish them here so that future Lino core developers might learn from them, too)... Note about copyright ==================== When you create a new file, you will have to decide about the copyright holder in the file header. Until now most Lino modules are marked "Copyright Luc Saffre". The most obvious approach is that you write your name as copyright holder when you created the file, and *add* your name when you contribute some relevant change to an existing module. An example this approach is the :mod:`lino.modlib.export_excel` plugin, created by Joe who contributed it to Lino, and later I continued to maintain it. The problem with this approach is that copyright is getting spread over several people. I believe that it is better to avoid this situation because it makes handling copyright issues very complicated. Imagine for example that (some day in some fare future) we discover that somebody else violated the copyright by using Lino code without referring to Lino. In such cases, the Lino maintainer (whoever they may be then) would have to check with three physical persons, one sitting in Estonia, a second in Czechia and the third in Tunesia. The best solution is to found a "Lino Software Foundation" and to assign copyright to this foundation. But for the moment this would be overkill. A workaround as long as no official foundation exists might be that we change Lino's copyright to "The Lino Team". But I am not sure whether this is okay because "The Lino Team" has no official "definition". Or should we do it like Django: we *remove* copyright notes from the headers of individual files? I updated the files AUTHORS.txt file. Lino settings versus Django settings ==================================== In :mod:`lino.api.dd` you added:: use_tz = settings.SITE.use_tz The intention is encapsulate this setting and to make it usable in application code as follows:: from lino.api import dd if dd.use_tz: ... But it has a dangerous side effect: what happens if applicaton code would change this value! The :mod:`lino.api.dd` module provides shortcuts to methods and functions, but should not create yet another copy of some attribute containing a simple value. In such a situation we should rather have application code use:: from django.conf import settings if settings.SITE.use_tz: ... But even this is useless! Since Lino does not add anything special about timezones, application code should directly use the plain Django settings :setting:`USE_TZ` and :setting:`TIME_ZONE`. So Hamza, I suggest to undo your changes in :mod:`lino.api.dd` and :mod:`lino.core.site`, and e.g. in:mod:`lino.modlib.users.models` you replace:: if dd.use_tz: ... by:: if settings.USE_TZ: ... Where to put the new TimezoneField ================================== The :mod:`lino.utils.mldbc.fields` is not a good place for the new `TimezoneField` because the two features are not necessarily related. The `TIMEZONE_CHOICES` variable seems not a good solution. It creates yet another copy of the list of available timezone strings. Some tests: >>> from pytz import all_timezones >>> print all_timezones #doctest: +ELLIPSIS [('1', 'Africa/Accra'), ('2', 'Africa/Addis_Ababa'), ('3', 'Africa/Algiers'), ('4', 'Africa/Asmara'), ('5', 'Africa/Asmera'), ('6', 'Africa/Bamako'), ('7', 'Africa/Bangui'), ('8', 'Africa/Banjul'), ('9', 'Africa/Bissau'), ('10', 'Africa/Blantyre'), ('11', 'Africa/Brazzaville'), ('12', 'Africa/Bujumbura'), ('13', 'Africa/Cairo'), ('14', 'Africa/Casablanca'), ('15', 'Africa/Ceuta'), ('16', 'Africa/Conakry'), ('17', 'Africa/Dakar'), ('18', 'Africa/Dar_es_Salaam'), ('19', 'Africa/Djibouti'), ...] >>> TIMEZONE_CHOICES = [(str(i), all_timezones[i]) for i in range(1, len(all_timezones))] >>> print TIMEZONE_CHOICES It is better to define either a *callable* as the `choices` of the `timezone` field, or --even better:-- a `simple_values` chooser. A chooser has the advantage that we can return a dynamic list of choices depending on the country of the user. See example below. An example of a simple_values chooser is the `template` field of a :class:`ExcerptType `. So as as summary, I suggest to add a new module :mod:`lino.mixins.timezone` instead which basically contains only this:: import ... class TimezoneHolder(Model): class Meta: abstract = True if settings.USE_TZ: timezone = models.CharField(_("Time zone"), max_lenght=15, blank=True) else: timezone = dd.DummyField() @dd.chooser(simple_values=True) def timezone_choices(cls, partner): if partner and partner.country: return pytz.country_timezones[partner.country.isocode] return pytz.common_timezones And then to have :class:`lino.modlib.users.models.User` inherit from this mixin. As for :mod:`lino.core.auth`: I (currently) think that it is not necessary to add a `user_timezone` variable to each incoming request object. I think we must rather access the user's `timezone` field each time we call the :func:`datetime.datetime.now` or :func:`datetime.date.today` functions. For Lino Noi this happens e.g. in :mod:`lino_noi.lib.clocking.models`. Note also that Lino has two methods :meth:`dd.now ` or :meth:`dd.today `. I guess that we cannot make them timezone-aware since they don't have access to the incoming request. And then (I have now idea yet) we must probably adapt the rendering of DateTime fields, maybe only in :mod:`lino.core.store`. A notification system ===================== I had an inspiration about how to do :ticket:`559`. Lino Noi should of course send emails when needed. The first approach used :mod:`lino.utils.sendchanges`. But that was not exactly what we need because it causes an inbox overflow in many situation. This experience confirms that :mod:`lino.utils.sendchanges` is probably useless and should be deprecated. What we need is a notification system that sends only one email per object, and which "collects" subsequent changes until the user visited the given object. Idea: - we are going to use the :mod:`stars ` plugin. - :mod:`stars ` will depend on :mod:`changes ` and use this. - every starred object causes notification - add a model `stars.Notification` with these fields: - `star` : pointer to `stars.Star` (i.e. the database object that is being observed and the user who is observing) - `change` : pointer to the first `changes.Change` which caused a notification email) - `seen` : `None` as long as the user did not dismiss this notification, otherwise the timestamp when it was seen. - Each time a changes.Change object is being created, Lino should potentially create a Notification. If `self` is the Change object, we would have something like this:: star = stars.Star.objects.get(user=ar.user, owner=self.owner) qs = stars.Notification.objects.filter( change__owner=self, star=star, seen__isnull=True) if not qs.exists(): stars.Notification(star=star, change=self, seen=False).save() How should the "dismissing" work? The code to run would be something like:: qs = stars.Notification.objects.filter( change__owner=obj, star__user=ar.user, seen__isnull=True) if qs.exists(): qs[0].seen = now() qs[0].save() But how would we trigger that code? - It should happen automatically when the detail information of that object is shown to the user. Hmm... but that means that for *every* (starrable) detail response Lino must do an additional database query in order to see whether this object is starred by the requesting user. One problem here is that Django has no signal for "when a detail of an object is being sent". I could define yet another signal and send it somewhere in :meth:`lino.core.kernel.Kernel.run_action`. But that feels a bit intrusive. - Alternatively we could say that notifications are displayed to the user in the welcome screen so that the user can dismiss them with one click.