20120713¶
New application attribute lino.Lino.override_modlib_models
¶
Following a sudden inspiration, I added the new
application attribute lino.Lino.override_modlib_models
and adapted the applications under lino.apps.
This is finally a solution to the old problem that all Lino applications
which use lino.modlib.contacts were obliged to define
themselves a non-abstract Person and a Company model.
This problem was a perpetual stumbling block when writing
tutorials and minimal applications.
Fixed a bug in lino.utils.dumpy
¶
Because of the above change I also made a double dump test, which revealed the following problem:
WARNING Abandoning with 6 unsaved instances from t:\data\luc\lino_local\dsbe\fixtures\d20120713.py:
- debts.Entry [u"Cannot set both 'Distribute' and 'Monthly rate'"] (6 object(s) with primary key 135, 136, 137, 140, 141
, 142)
This problem existed already before my sudden inspiration, and it would have caused me much more work if I had discovered it only after the 1.4.4 release.
Here is the application code
(from lino.modlib.debts.models.Entry
)
which caused it:
def full_clean(self,*args,**kw):
if self.periods <= 0:
raise ValidationError(_("Periods must be > 0"))
if self.monthly_rate and self.distribute:
raise ValidationError(
_("Cannot set both 'Distribute' and 'Monthly rate'"))
super(Entry,self).full_clean(*args,**kw)
Changed this code so that the message becomes more specific:
WARNING Abandoning with 6 unsaved instances from t:\data\luc\lino_local\dsbe\fixtures\d20120713.py:
- debts.Entry [u"Cannot set 'Distribute' when 'Monthly rate' is '0'"] (6 object(s) with primary key 135, 136, 137, 140,
141, 142)
The generated dumpy file is as follows:
def create_debts_entry(id, seqno, ..., monthly_rate):
return debts_Entry(id=id,seqno=seqno,...,monthly_rate=monthly_rate)
def debts_entry_objects():
...
yield create_debts_entry(135,48,...,'0')
This is the reason for our problem. A DecimalField accepts a string value and will convert it to a Decimal, but only when reading it from the database. For example (supposing that Company.hourly_rate is a DecimalField):
>>> from lino.apps.pcsw.models import Company
>>> c = Company(name="test",hourly_rate='0.25')
>>> print repr(c.hourly_rate)
'0.25'
>>> c.save()
>>> print repr(c.hourly_rate)
'0.25'
Only after loading it again from database:
>>> c2 = Company.objects.get(pk=c.pk)
>>> print repr(c2.hourly_rate)
Decimal('0.25')
It is one of Django’s oddnesses to allow storing a string
value in a DecimalField and leave it there unconverted.
We’ll forgive that oddness by modifying
lino.utils.dumpy
so that it generates code which instantiates models
using true Decimal values:
def create_debts_entry(id, seqno, ... monthly_rate):
if monthly_rate is not None: monthly_rate = Decimal(monthly_rate)
return debts_Entry(id=id,seqno=seqno,...,monthly_rate=monthly_rate)
Note that another (easier) solution would have been to
modify lino.utils.dumpy.Serializer.value2string()
in order to
write the correct value directly for each debts_entry_objects line
instead of testing and converting each field in create_debts_entry:
def debts_entry_objects():
...
yield create_debts_entry(135,48,...,Decimal('0'))
But we prefer to keep the file size small (and load performance is less important).