Thursday, November 19, 2015¶
Working at night¶
Tonight Hamza and I had a three hour common voice session with shared screen. We wanted to deploy the newest version to testlino (a test site of a customer). I expected a routine session with possibly some surprises due to the fact that we are now in Django 1.8. But it turned out to be much more complicated than expected.
The problem was that initdb
fails on MySQL. This did not
occur in any test suite because all tests currently use SQLite. (TODO
once when we have time: add a test case which reproduces the problem)
While doing the following:
$ python manage.py run ../cpas_eupen/snapshot/restore.py
We get the following output:
INFO `initdb ` started on database test_dsbe.
Operations to perform:
Synchronize unmigrated apps: addresses, office, cbss, sales, cosi, ledger, accounts, newcomers, welfare, aids, excerpts, cv, export_excel, bootstrap3, households, art61, appypod, jinja, contacts, debts, tinymce, system, extjs, languages, humanize, vatless, davlink, extensible, beid, gfks, jobs, users, staticfiles, checkdata, pcsw, dupable_clients, lino_startup, outbox, b2c, properties, about, boards, countries, sepa, notes, isip, integ, courses, printing, humanlinks, uploads, reception, cal, finan, changes, notifier
Apply all migrations: (none)
Synchronizing apps without migrations:
Creating tables...
Creating table system_siteconfig
Creating table gfks_helptext
Creating table users_authority
Creating table users_user
Creating table notifier_notification
Creating table changes_change
Creating table countries_country
Creating table countries_place
Creating table properties_proptype
Creating table properties_propchoice
Creating table properties_propgroup
Creating table properties_property
Creating table properties_personproperty
Creating table contacts_companytype
Creating table contacts_roletype
Creating table contacts_role
Creating table contacts_partner
Creating table contacts_person
Creating table contacts_company
Creating table addresses_address
Creating table uploads_uploadtype
Creating table uploads_upload
Creating table outbox_recipient
Creating table outbox_mail
Creating table outbox_attachment
Creating table excerpts_excerpttype
Creating table excerpts_excerpt
Creating table cal_remotecalendar
Creating table cal_room
Creating table cal_priority
Creating table cal_guestrole
Creating table cal_calendar
Creating table cal_subscription
Creating table cal_recurrentevent
Creating table cal_eventtype
Creating table cal_event
Creating table cal_guest
Creating table cal_task
Creating table accounts_group
Creating table accounts_account
Creating table boards_board
Creating table boards_member
Creating table pcsw_coachingtype
Creating table pcsw_coachingending
Creating table pcsw_coaching
Creating table pcsw_persongroup
Creating table pcsw_activity
Creating table pcsw_dispensereason
Creating table pcsw_dispense
Creating table pcsw_exclusiontype
Creating table pcsw_exclusion
Creating table pcsw_conviction
Creating table pcsw_aidtype
Creating table pcsw_clientcontacttype
Creating table pcsw_clientcontact
Creating table pcsw_client
Creating table ledger_journal
Creating table ledger_paymentterm
Creating table ledger_voucher
Creating table ledger_movement
Creating table ledger_matchrule
Creating table sepa_account
Creating table b2c_account
Creating table b2c_statement
Creating table b2c_movement
Creating table vatless_accountinvoice
Creating table vatless_invoiceitem
Creating table finan_grouper
Creating table finan_journalentry
Creating table finan_paymentorder
Creating table finan_bankstatement
Creating table finan_grouperitem
Creating table finan_journalentryitem
Creating table finan_bankstatementitem
Creating table finan_paymentorderitem
Creating table languages_language
Creating table cv_languageknowledge
Creating table cv_educationlevel
Creating table cv_studytype
Creating table cv_training
Creating table cv_study
Creating table cv_status
Creating table cv_regime
Creating table cv_duration
Creating table cv_sector
Creating table cv_function
Creating table cv_experience
Creating table isip_contracttype
Creating table isip_exampolicy
Creating table isip_contractending
Creating table isip_contractpartner
Creating table isip_contract
Creating table jobs_schedule
Creating table jobs_jobprovider
Creating table jobs_contracttype
Creating table jobs_contract
Creating table jobs_offer
Creating table jobs_job
Creating table jobs_candidature
Creating table jobs_jobtype
Creating table art61_contracttype
Creating table art61_contract
Creating table courses_courseprovider
Creating table courses_coursecontent
Creating table courses_courseoffer
Creating table courses_course
Creating table courses_courserequest
Creating table newcomers_broker
Creating table newcomers_faculty
Creating table newcomers_competence
Creating table cbss_sector
Creating table cbss_purpose
Creating table cbss_identifypersonrequest
Creating table cbss_manageaccessrequest
Creating table cbss_retrievetigroupsrequest
Creating table households_type
Creating table households_household
Creating table households_member
Creating table humanlinks_link
Creating table debts_budget
Creating table debts_actor
Creating table debts_entry
Creating table notes_notetype
Creating table notes_eventtype
Creating table notes_note
Creating table aids_category
Creating table aids_aidtype
Creating table aids_granting
Creating table aids_simpleconfirmation
Creating table aids_incomeconfirmation
Creating table aids_refundconfirmation
Creating table dupable_clients_word
Creating table checkdata_problem
Creating table tinymce_textfieldtemplate
Running deferred SQL...
Traceback (most recent call last):
File "manage.py", line 7, in <module>
execute_from_command_line(sys.argv)
File "/python2.7/site-packages/django/core/management/__init__.py", line 354, in execute_from_command_line
utility.execute()
File "/python2.7/site-packages/django/core/management/__init__.py", line 346, in execute
self.fetch_command(subcommand).run_from_argv(self.argv)
File "/python2.7/site-packages/django/core/management/base.py", line 394, in run_from_argv
self.execute(*args, **cmd_options)
File "/python2.7/site-packages/django/core/management/base.py", line 445, in execute
output = self.handle(*args, **options)
File "/lino/lino/modlib/lino_startup/management/commands/run.py", line 59, in handle
execfile(fn, globals())
File "../cpas_eupen/snapshot/restore.py", line 1900, in <module>
main()
File "../cpas_eupen/snapshot/restore.py", line 1761, in main
call_command('initdb')
File "/python2.7/site-packages/django/core/management/__init__.py", line 120, in call_command
return command.execute(*args, **defaults)
File "/python2.7/site-packages/django/core/management/base.py", line 445, in execute
output = self.handle(*args, **options)
File "/lino/lino/modlib/lino_startup/management/commands/initdb.py", line 202, in handle
call_command('migrate', **options)
File "/python2.7/site-packages/django/core/management/__init__.py", line 120, in call_command
return command.execute(*args, **defaults)
File "/python2.7/site-packages/django/core/management/base.py", line 445, in execute
output = self.handle(*args, **options)
File "/python2.7/site-packages/django/core/management/commands/migrate.py", line 179, in handle
created_models = self.sync_apps(connection, executor.loader.unmigrated_apps)
File "/python2.7/site-packages/django/core/management/commands/migrate.py", line 318, in sync_apps
cursor.execute(statement)
File "/python2.7/site-packages/django/db/backends/utils.py", line 79, in execute
return super(CursorDebugWrapper, self).execute(sql, params)
File "/python2.7/site-packages/django/db/backends/utils.py", line 64, in execute
return self.cursor.execute(sql, params)
File "/python2.7/site-packages/django/db/utils.py", line 98, in __exit__
six.reraise(dj_exc_type, dj_exc_value, traceback)
File "/python2.7/site-packages/django/db/backends/utils.py", line 62, in execute
return self.cursor.execute(sql)
File "/python2.7/site-packages/django/db/backends/mysql/base.py", line 124, in execute
return self.cursor.execute(query, args)
File "/python2.7/site-packages/MySQLdb/cursors.py", line 205, in execute
self.errorhandler(self, exc, value)
File "/python2.7/site-packages/MySQLdb/connections.py", line 36, in defaulterrorhandler
raise errorclass, errorvalue
django.db.utils.OperationalError: (1005, "Can't create table 'test_dsbe.#sql-307_b798d' (errno: 150)")
INFO Done manage.py run ../cpas_eupen/snapshot/restore.py (PID 24652)
On InnoDB Error Codes we can see what 1005 means:
1005 (ER_CANT_CREATE_TABLE)
Cannot create table. If the error message refers to error 150, table creation failed because a foreign key constraint was not correctly formed. If the error message refers to error −1, table creation probably failed because the table includes a column name that matched the name of an internal InnoDB table.
Do we need migrations? And if yes: what must we do to use them? We did not find a quick answer to that question.
The next morning¶
On stackoverflow I saw this thread: Django’s syncdb fails with MySQL errno: 150. It suggests to use MyISAM as database engine instead of the more sever InnoDB, and it reveals a simply method for doing this without changing the system default engine.
And it worked. Sounds like a reasonable workaround at least for testlino. TODO: Dive more into this. Why Django migrations system fails to simply create a stupid virgin database under InnoDB?
Before it worked, I had another little surprise while saving the
Guest
objects:
ERROR 'NoneType' object has no attribute 'find'
Traceback (most recent call last):
File "/lino/lino/utils/dpy.py", line 436, in try_save
obj.full_clean()
File "/lino-welfare/lino_welfare/modlib/cal/models.py", line 185, in full_clean
super(Event, self).full_clean()
File "/lino/lino/modlib/printing/mixins.py", line 498, in full_clean
super(CachedPrintable, self).full_clean(*args, **kwargs)
File "/python2.7/site-packages/django/db/models/base.py", line 1149, in full_clean
self.clean_fields(exclude=exclude)
File "/python2.7/site-packages/django/db/models/base.py", line 1187, in clean_fields
raw_value = getattr(self, f.attname)
File "/lino/lino/core/fields.py", line 481, in __get__
return self.value_from_object(instance, None)
File "/lino/lino/core/fields.py", line 476, in value_from_object
return m(obj, ar)
File "/lino/lino/modlib/cal/models.py", line 691, in linked_date
txt = when_text(self.start_date, self.start_time)
File "/lino/lino/modlib/cal/utils.py", line 113, in when_text
txt = format_date(d, 'EE ')
File "/lino/lino/modlib/cal/utils.py", line 51, in format_date
d, fmt, locale=to_locale(translation.get_language()))
File "/lino/lino/core/site.py", line 73, in to_locale
p = language.find('-')
AttributeError: 'NoneType' object has no attribute 'find'
To get above traceback, I had to do a little optimization of error
reporting to lino.utils.dpy
. Note that there were thousands of
objects causing that error, so it is important to report it only at
the first occurence.
Was this caused by our nightly cheat of removing the default
attribute of choicelist fields (by adding a line
kwargs.pop('default', None)
in the deconstruct
method? I removed
that line (which anyway was dangerous). No, that was not the reason.
No, it was caused by format_date
which under Django 1.7+ gets
called also during initdb
(I guess because Django now also
sets all virtual fields of virgin objects, don’t ask me why). The
linked_date
virtual field of an cal.Event
object for example calls
format_date()
. And in initdb
there is no “current
Django language”.
Working with lazy translation¶
When the data migration (to be more precise or the restore of the snapshot) finally had passed, I still could not watch at the new Lino on their server because accessing it caused yet another surpsie, an error message in the Apache error log:
Traceback (most recent call last):
File "/site-packages/django/core/handlers/wsgi.py", line 170, in __call__
self.load_middleware()
File "/site-packages/django/core/handlers/base.py", line 52, in load_middleware
mw_instance = mw_class()
File "/site-packages/django/middleware/locale.py", line 24, in __init__
for url_pattern in get_resolver(None).url_patterns:
File "/site-packages/django/core/urlresolvers.py", line 401, in url_patterns
patterns = getattr(self.urlconf_module, "urlpatterns", self.urlconf_module)
File "/site-packages/django/core/urlresolvers.py", line 395, in urlconf_module
self._urlconf_module = import_module(self.urlconf_name)
File "/usr/lib/python2.7/importlib/__init__.py", line 37, in import_module
__import__(name)
File "/lino/lino/core/urls.py", line 20, in <module>
site.startup()
File "/lino/lino/core/site.py", line 1615, in startup
app_config.import_models(apps.all_models[app_config.label])
File "/site-packages/django/apps/config.py", line 198, in import_models
self.models_module = import_module(models_module_name)
File "/usr/lib/python2.7/importlib/__init__.py", line 37, in import_module
__import__(name)
File "/lino-welfare/lino_welfare/modlib/reception/models.py", line 43, in <module>
pcsw = dd.resolve_app('pcsw')
File "/lino/lino/core/utils.py", line 406, in resolve_app
return import_module('.models', app_name)
File "/site-packages/django/utils/importlib.py", line 45, in import_module
__import__(name)
File "/lino-welfare/lino_welfare/projects/eupen/modlib/pcsw/models.py", line 33, in <module>
from lino_welfare.modlib.pcsw.models import *
File "/lino-welfare/lino_welfare/modlib/pcsw/models.py", line 68, in <module>
from .coaching import *
File "/lino-welfare/lino_welfare/modlib/pcsw/coaching.py", line 53, in <module>
class CoachingType(mixins.BabelNamed):
File "/lino-welfare/lino_welfare/modlib/pcsw/coaching.py", line 70, in CoachingType
help_text=_("Whether this coaching type does %s.") % INTEG_LABEL)
File "/site-packages/django/utils/functional.py", line 178, in __mod__
return six.text_type(self) % rhs
File "/site-packages/django/utils/functional.py", line 140, in __text_cast
return func(*self.__args, **self.__kw)
File "/site-packages/django/utils/translation/__init__.py", line 84, in ugettext
return _trans.ugettext(message)
File "/site-packages/django/utils/translation/trans_real.py", line 327, in ugettext
return do_translate(message, 'ugettext')
File "/site-packages/django/utils/translation/trans_real.py", line 304, in do_translate
_default = _default or translation(settings.LANGUAGE_CODE)
File "/site-packages/django/utils/translation/trans_real.py", line 206, in translation
_translations[language] = DjangoTranslation(language)
File "/site-packages/django/utils/translation/trans_real.py", line 116, in __init__
self._add_installed_apps_translations()
File "/site-packages/django/utils/translation/trans_real.py", line 164, in _add_installed_apps_translations
"The translation infrastructure cannot be initialized before the "
AppRegistryNotReady: The translation infrastructure cannot be initialized before the apps registry is ready. Check that you don't make non-lazy gettext calls at import time.
The explanation for above traceback is the following piece of
code::
does_integ = models.BooleanField(
INTEG_LABEL, default=True,
help_text=_("Whether this coaching type does %s.") % INTEG_LABEL)
Although _ in above code is lazy (from django.utils.translation import ugettext_lazy as _), it causes the translation to get triggered already when the models are being imported because it is at module level. More details in Working with lazy translation objects.
There were several similar problems. I don’t know why they occured only on their server (maybe multi-threaded operation?). I solved them using quick cycles of “edit, commit, pull, restart and retry”:
@local$ fab ci
remote$ ./pull.sh
remote$ sudo service apache2 graceful
Multithreaded startup¶
Yet another surprise was this:
mod_wsgi (pid=19447): Exception occurred processing WSGI script '/usr/local/django/testlino/wsgi.py'.
Traceback (most recent call last):
File "/site-packages/django/core/handlers/wsgi.py", line 170, in __call__
self.load_middleware()
File "/site-packages/django/core/handlers/base.py", line 52, in load_middleware
mw_instance = mw_class()
File "/site-packages/django/middleware/locale.py", line 24, in __init__
for url_pattern in get_resolver(None).url_patterns:
File "/site-packages/django/core/urlresolvers.py", line 401, in url_patterns
patterns = getattr(self.urlconf_module, "urlpatterns", self.urlconf_module)
File "/site-packages/django/core/urlresolvers.py", line 395, in urlconf_module
self._urlconf_module = import_module(self.urlconf_name)
File "/usr/lib/python2.7/importlib/__init__.py", line 37, in import_module
__import__(name)
File "/lino/lino/core/urls.py", line 20, in <module>
site.startup()
File "/lino/lino/core/site.py", line 1631, in startup
self.do_site_startup()
File "/lino-welfare/lino_welfare/projects/eupen/settings/__init__.py", line 64, in do_site_startup
super(Site, self).do_site_startup()
File "/lino/lino/core/site.py", line 1921, in do_site_startup
self.kernel = Kernel(self)
File "/lino/lino/core/kernel.py", line 187, in __init__
self.kernel_startup(site)
File "/lino/lino/core/kernel.py", line 279, in kernel_startup
models_list = get_models(include_auto_created=True)
File "/site-packages/django/utils/lru_cache.py", line 101, in wrapper
result = user_function(*args, **kwds)
File "/site-packages/django/apps/registry.py", line 168, in get_models
self.check_models_ready()
File "/site-packages/django/apps/registry.py", line 131, in check_models_ready
raise AppRegistryNotReady("Models aren't loaded yet.")
AppRegistryNotReady: Models aren't loaded yet.
The problem occurred only under mod_wsgi (don’t ask me why), thus yet
another series of “edit, commit, pull, restart and retry” cycles. In
the beginning I suspected it to be related to the threading.RLock,
but it turned out that when I explicitly call django.setup()
from lino.core.urls
, the problem vanished. I gues that when
running under mod_wsgi this is not done automatically as with
runserver.
Miscellaneous¶
TravisCI started to report build failures caused by a
SyntaxError in a Django source file under Python 2.6. And yes, they
say it themselves,
Django no longer runs on Python 2.6. So I removed it from Lino’s
.travis.xml
. (I just don’t understand why these failures
started only now and not immediately when I removed the <1.7
condition from setup_info.py
).
Duplicate sequence numbers¶
Now that their testlino runs with the newest version and I had imported their incoming SEPA XML files, I see that most statement numbers are duplicated. But that’s for later. Now I must stop working.