Use Django models to store variables

- Remove dependency on withsqlite and use Django models.
  This avoids depending on a module that is not available in PyPi.
  Withsqlite does not have Python3 support. It does not work when
  we choose a different database backend. Atleast partly duplicates
  what Django models are meant for.

- Check and update database schema on every run so that
  newly added modules can add tables and old ones can update.
This commit is contained in:
Sunil Mohan Adapa 2014-09-10 12:28:09 +05:30
parent 2bbaa11c96
commit 42d05bfe1f
8 changed files with 119 additions and 67 deletions

View File

@ -13,7 +13,7 @@ actions_dir = /usr/share/plinth/actions
doc_dir = /usr/share/doc/plinth doc_dir = /usr/share/doc/plinth
# file locations # file locations
store_file = %(data_dir)s/store.sqlite3 store_file = %(data_dir)s/plinth.sqlite3
status_log_file = %(log_dir)s/status.log status_log_file = %(log_dir)s/status.log
access_log_file = %(log_dir)s/access.log access_log_file = %(log_dir)s/access.log
pidfile = %(pid_dir)s/plinth.pid pidfile = %(pid_dir)s/plinth.pid

View File

@ -13,7 +13,7 @@ actions_dir = %(file_root)s/actions
doc_dir = %(file_root)s/doc doc_dir = %(file_root)s/doc
# file locations # file locations
store_file = %(data_dir)s/store.sqlite3 store_file = %(data_dir)s/plinth.sqlite3
status_log_file = %(log_dir)s/status.log status_log_file = %(log_dir)s/status.log
access_log_file = %(log_dir)s/access.log access_log_file = %(log_dir)s/access.log
pidfile = %(pid_dir)s/plinth.pid pidfile = %(pid_dir)s/plinth.pid

View File

@ -1,4 +1,4 @@
#!/usr/bin/env python #!/usr/bin/python
import argparse import argparse
import django.conf import django.conf
@ -160,8 +160,6 @@ def configure_django():
} }
} }
data_file = os.path.join(cfg.data_dir, 'plinth.sqlite3')
template_directories = module_loader.get_template_directories() template_directories = module_loader.get_template_directories()
sessions_directory = os.path.join(cfg.data_dir, 'sessions') sessions_directory = os.path.join(cfg.data_dir, 'sessions')
django.conf.settings.configure( django.conf.settings.configure(
@ -170,12 +168,13 @@ def configure_django():
{'BACKEND': 'django.core.cache.backends.dummy.DummyCache'}}, {'BACKEND': 'django.core.cache.backends.dummy.DummyCache'}},
DATABASES={'default': DATABASES={'default':
{'ENGINE': 'django.db.backends.sqlite3', {'ENGINE': 'django.db.backends.sqlite3',
'NAME': data_file}}, 'NAME': cfg.store_file}},
DEBUG=cfg.debug, DEBUG=cfg.debug,
INSTALLED_APPS=['bootstrapform', INSTALLED_APPS=['bootstrapform',
'django.contrib.auth', 'django.contrib.auth',
'django.contrib.contenttypes', 'django.contrib.contenttypes',
'django.contrib.messages'], 'django.contrib.messages',
'plinth'],
LOGGING=logging_configuration, LOGGING=logging_configuration,
LOGIN_URL='lib:login', LOGIN_URL='lib:login',
LOGIN_REDIRECT_URL='apps:index', LOGIN_REDIRECT_URL='apps:index',
@ -199,10 +198,9 @@ def configure_django():
LOGGER.info('Configured Django') LOGGER.info('Configured Django')
LOGGER.info('Template directories - %s', template_directories) LOGGER.info('Template directories - %s', template_directories)
if not os.path.isfile(data_file): LOGGER.info('Creating or adding new tables to data file')
LOGGER.info('Creating and initializing data file') django.core.management.call_command('syncdb', interactive=False)
django.core.management.call_command('syncdb', interactive=False) os.chmod(cfg.store_file, stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP)
os.chmod(data_file, stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP)
def main(): def main():

View File

@ -70,7 +70,3 @@ def read():
global port # pylint: disable-msg=W0603 global port # pylint: disable-msg=W0603
port = int(port) port = int(port)
global store_file # pylint: disable-msg=W0603
if store_file.endswith(".sqlite3"):
store_file = os.path.splitext(store_file)[0]

42
plinth/kvstore.py Normal file
View File

@ -0,0 +1,42 @@
#
# This file is part of Plinth.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
"""
Simple key/value store using Django models
"""
from plinth.models import KVStore
def get(key):
"""Return the value of a key"""
# pylint: disable-msg=E1101
return KVStore.objects.get(pk=key).value
def get_default(key, default_value):
"""Return the value of the key if key exists else return default_value"""
try:
return get(key)
except Exception:
return default_value
def set(key, value): # pylint: disable-msg=W0622
"""Store the value of a key"""
store = KVStore(key=key, value=value)
store.save()

39
plinth/models.py Normal file
View File

@ -0,0 +1,39 @@
#
# This file is part of Plinth.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
"""
Django models for the main application
"""
from django.db import models
import json
class KVStore(models.Model):
"""Model to store retrieve key/value configuration"""
key = models.TextField(primary_key=True)
value_json = models.TextField()
@property
def value(self):
"""Return the JSON decoded value of the key/value pair"""
return json.loads(self.value_json)
@value.setter
def value(self, val):
"""Store the value of the key/value pair by JSON encoding it"""
self.value_json = json.dumps(val)

View File

@ -26,10 +26,9 @@ from django.http.response import HttpResponseRedirect
from django.template.response import TemplateResponse from django.template.response import TemplateResponse
from gettext import gettext as _ from gettext import gettext as _
from plinth import cfg
from plinth.modules.config import config from plinth.modules.config import config
from plinth.modules.lib.auth import add_user from plinth.modules.lib.auth import add_user
from withsqlite.withsqlite import sqlite_db from plinth import kvstore
## TODO: flesh out these tests values ## TODO: flesh out these tests values
@ -94,7 +93,7 @@ def state0(request):
user. It's a good place to put error messages. user. It's a good place to put error messages.
""" """
try: try:
if _read_state() >= 5: if kvstore.get_default('firstboot_state', 0) >= 5:
return HttpResponseRedirect(reverse('index')) return HttpResponseRedirect(reverse('index'))
except KeyError: except KeyError:
pass pass
@ -112,7 +111,7 @@ def state0(request):
if success: if success:
# Everything is good, permanently mark and move to page 2 # Everything is good, permanently mark and move to page 2
_write_state(1) kvstore.set('firstboot_state', 1)
return HttpResponseRedirect(reverse('first_boot:state1')) return HttpResponseRedirect(reverse('first_boot:state1'))
else: else:
form = State0Form(initial=status, prefix='firstboot') form = State0Form(initial=status, prefix='firstboot')
@ -124,35 +123,29 @@ def state0(request):
def get_state0(): def get_state0():
"""Return the state for form state 0""" """Return the state for form state 0"""
with sqlite_db(cfg.store_file, table="thisbox", autocommit=True) as \ return {'hostname': config.get_hostname(),
database: 'box_key': kvstore.get_default('box_key', None)}
return {'hostname': config.get_hostname(),
'box_key': database.get('box_key', None)}
def _apply_state0(request, old_state, new_state): def _apply_state0(request, old_state, new_state):
"""Apply changes in state 0 form""" """Apply changes in state 0 form"""
success = True success = True
with sqlite_db(cfg.store_file, table="thisbox", autocommit=True) as \
database:
database['about'] = 'Information about this FreedomBox'
if new_state['box_key']: if new_state['box_key']:
database['box_key'] = new_state['box_key'] kvstore.set('box_key', new_state['box_key'])
elif not old_state['box_key']: elif not old_state['box_key']:
database['box_key'] = generate_box_key() kvstore.set('box_key', generate_box_key())
if old_state['hostname'] != new_state['hostname']: if old_state['hostname'] != new_state['hostname']:
config.set_hostname(new_state['hostname']) config.set_hostname(new_state['hostname'])
error = add_user(new_state['username'], new_state['password'], error = add_user(new_state['username'], new_state['password'],
'First user, please change', '', True) 'First user, please change', '', True)
if error: if error:
messages.error( messages.error(request, _('User account creation failed: %s') % error)
request, _('User account creation failed: %s') % error) success = False
success = False else:
else: messages.success(request, _('User account created'))
messages.success(request, _('User account created'))
return success return success
@ -168,21 +161,7 @@ def state1(request):
""" """
# TODO complete first_boot handling # TODO complete first_boot handling
# Make sure the user is not stuck on a dead end for now. # Make sure the user is not stuck on a dead end for now.
_write_state(5) kvstore.set('firstboot_state', 5)
return TemplateResponse(request, 'firstboot_state1.html', return TemplateResponse(request, 'firstboot_state1.html',
{'title': _('Installing the Certificate')}) {'title': _('Installing the Certificate')})
def _read_state():
"""Read the current state from database"""
with sqlite_db(cfg.store_file, table='firstboot',
autocommit=True) as database:
return database['state']
def _write_state(state):
"""Write state to database"""
with sqlite_db(cfg.store_file, table='firstboot',
autocommit=True) as database:
database['state'] = state

View File

@ -24,8 +24,7 @@ from django.core.urlresolvers import reverse
from django.http.response import HttpResponseRedirect from django.http.response import HttpResponseRedirect
import logging import logging
from plinth import cfg from plinth import kvstore
from withsqlite.withsqlite import sqlite_db
LOGGER = logging.getLogger(__name__) LOGGER = logging.getLogger(__name__)
@ -42,14 +41,13 @@ class FirstBootMiddleware(object):
if request.path.startswith(reverse('first_boot:index')): if request.path.startswith(reverse('first_boot:index')):
return return
with sqlite_db(cfg.store_file, table='firstboot') as database: state = kvstore.get_default('firstboot_state', 0)
if 'state' not in database: if not state:
# Permanent redirect causes the browser to cache the redirect, # Permanent redirect causes the browser to cache the redirect,
# preventing the user from navigating to /plinth until the # preventing the user from navigating to /plinth until the
# browser is restarted. # browser is restarted.
return HttpResponseRedirect(reverse('first_boot:index')) return HttpResponseRedirect(reverse('first_boot:index'))
if database['state'] < 5: if state < 5:
LOGGER.info('First boot state - %d', database['state']) LOGGER.info('First boot state - %d', state)
return HttpResponseRedirect(reverse('first_boot:state%d' % return HttpResponseRedirect(reverse('first_boot:state%d' % state))
database['state']))