More tests, development instructions and small changes.

This commit is contained in:
Oskar Hahn 2016-09-30 21:43:22 +02:00
parent 7cd70a568c
commit ac9c9f4ec3
9 changed files with 100 additions and 20 deletions

View File

@ -109,3 +109,31 @@ a. Running Angular.js test cases
'''''''''''''''''''''''''''''''' ''''''''''''''''''''''''''''''''
$ node_modules/.bin/karma start tests/karma/karma.conf.js $ node_modules/.bin/karma start tests/karma/karma.conf.js
Installation Openslides in big mode
===================================
1. Install PostgreSQL und redis:
apt-get install postgresql redis-server libpg-dev
TODO: Configure postgresql
2. Install python dependencies
pip install django-redis asgi-redis psycopg2
3. Change settings.py
(See comments in the settings)
The relevant settings are: DATABASES, CHANNEL_LAYERS, CACHES
4. Start one or more workers:
python manage.py runworker
5. Start daphne. Set the DJANGO_SETTINGS_MODULE and the PYTHONPATH
DJANGO_SETTINGS_MODULE=settings PYTHONPATH=personal_data/var/ daphne openslides.asgi:channel_layer

View File

@ -116,7 +116,7 @@ class Projector(RESTModelMixin, models.Model):
result[key]['error'] = str(e) result[key]['error'] = str(e)
return result return result
def get_all_requirements(self, on_slide=None): #TODO: Refactor or rename this. def get_all_requirements(self, on_slide=None): # TODO autoupdate: Refactor or rename this.
""" """
Generator which returns all instances that are shown on this projector. Generator which returns all instances that are shown on this projector.
""" """

View File

@ -23,7 +23,7 @@ def get_logged_in_users():
return User.objects.exclude(session=None).filter(session__expire_date__gte=timezone.now()).distinct() return User.objects.exclude(session=None).filter(session__expire_date__gte=timezone.now()).distinct()
def get_projector_element_data(projector, on_slide=None): #TODO def get_projector_element_data(projector, on_slide=None): # TODO autoupdate
""" """
Returns a list of dicts that are required for a specific projector. Returns a list of dicts that are required for a specific projector.
@ -81,7 +81,7 @@ def ws_add_projector(message, projector_id):
Group('projector-{}'.format(projector_id)).add(message.reply_channel) Group('projector-{}'.format(projector_id)).add(message.reply_channel)
# Send all elements that are on the projector. # Send all elements that are on the projector.
output = get_projector_element_data(projector) #TODO output = get_projector_element_data(projector) # TODO autoupdate
# Send all config elements. # Send all config elements.
collection = Collection(config.get_collection_string()) collection = Collection(config.get_collection_string())
@ -102,7 +102,7 @@ def ws_disconnect_projector(message, projector_id):
Group('projector-{}'.format(projector_id)).discard(message.reply_channel) Group('projector-{}'.format(projector_id)).discard(message.reply_channel)
def send_data(message): #TODO def send_data(message): # TODO autoupdate
""" """
Informs all users about changed data. Informs all users about changed data.
""" """

View File

@ -35,7 +35,7 @@ class CollectionElement:
self.instance = instance self.instance = instance
self.deleted = deleted self.deleted = deleted
self.full_data = full_data self.full_data = full_data
self.information = information self.information = information or {}
if instance is not None: if instance is not None:
self.collection_string = instance.get_collection_string() self.collection_string = instance.get_collection_string()
self.id = instance.pk self.id = instance.pk
@ -56,6 +56,16 @@ class CollectionElement:
# then update the cache. # then update the cache.
self.save_to_cache() self.save_to_cache()
def __eq__(self, collection_element):
"""
Compares two collection_elements.
Two collection elements are equal, if they have the same collection_string
and id.
"""
return (self.collection_string == collection_element.collection_string and
self.id == collection_element.id)
def as_channels_message(self): def as_channels_message(self):
""" """
Returns a dictonary that can be used to send the object through the Returns a dictonary that can be used to send the object through the
@ -224,7 +234,7 @@ class Collection:
""" """
key = get_element_list_cache_key(self.collection_string) key = get_element_list_cache_key(self.collection_string)
if raw: if raw:
key = self.make_key(key) key = cache.make_key(key)
return key return key
def get_model(self): def get_model(self):

View File

@ -61,15 +61,15 @@ class RESTModelMixin:
If skip_autoupdate is set to True, then the autoupdate system is not If skip_autoupdate is set to True, then the autoupdate system is not
informed about the model changed. This also means, that the model cache informed about the model changed. This also means, that the model cache
is not updated. You have to do it manually. Like this: is not updated. You have to do this manually, by creating a collection
element from the instance:
TODO HELP ME CollectionElement.from_instance(instance)
The optional argument information can be a dictionary that is given to The optional argument information can be a dictionary that is given to
the autoupdate system. the autoupdate system.
""" """
#TODO: Add example in docstring. # We don't know how to fix this circular import
#TODO: Fix circular imports
from .autoupdate import inform_changed_data from .autoupdate import inform_changed_data
return_value = super().save(*args, **kwargs) return_value = super().save(*args, **kwargs)
if not skip_autoupdate: if not skip_autoupdate:
@ -80,16 +80,27 @@ class RESTModelMixin:
""" """
Calls Django's delete() method and afterwards hits the autoupdate system. Calls Django's delete() method and afterwards hits the autoupdate system.
See the save method above. If skip_autoupdate is set to True, then the autoupdate system is not
informed about the model changed. This also means, that the model cache
is not updated. You have to do this manually, by creating a collection
element from the instance:
CollectionElement.from_instance(instance, deleted=True)
or
CollectionElement.from_values(collection_string, id, deleted=True)
The optional argument information can be a dictionary that is given to
the autoupdate system.
""" """
#TODO: Fix circular imports # We don't know how to fix this circular import
from .autoupdate import inform_changed_data, inform_deleted_data from .autoupdate import inform_changed_data, inform_deleted_data
instance_pk = self.pk instance_pk = self.pk
return_value = super().delete(*args, **kwargs) return_value = super().delete(*args, **kwargs)
if not skip_autoupdate: if not skip_autoupdate:
if self != self.get_root_rest_element(): if self != self.get_root_rest_element():
# The deletion of a included element is a change of the root element. # The deletion of a included element is a change of the root element.
#TODO: Does this work in any case with self.pk == None?
inform_changed_data(self.get_root_rest_element(), information=information) inform_changed_data(self.get_root_rest_element(), information=information)
else: else:
inform_deleted_data(self.get_collection_string(), instance_pk, information=information) inform_deleted_data(self.get_collection_string(), instance_pk, information=information)

View File

@ -46,6 +46,17 @@ DEBUG = %(debug)s
# Change this setting to use e. g. PostgreSQL or MySQL. # Change this setting to use e. g. PostgreSQL or MySQL.
# DATABASES = {
# 'default': {
# 'ENGINE': 'django.db.backends.postgresql',
# 'NAME': 'mydatabase',
# 'USER': 'mydatabaseuser',
# 'PASSWORD': 'mypassword',
# 'HOST': '127.0.0.1',
# 'PORT': '5432',
# }
# }
DATABASES = { DATABASES = {
'default': { 'default': {
'ENGINE': 'django.db.backends.sqlite3', 'ENGINE': 'django.db.backends.sqlite3',
@ -64,7 +75,12 @@ DATABASES = {
# CHANNEL_LAYERS['default']['BACKEND'] = 'asgi_redis.RedisChannelLayer' # CHANNEL_LAYERS['default']['BACKEND'] = 'asgi_redis.RedisChannelLayer'
# https://niwinz.github.io/django-redis/latest/#_user_guide
# Caching
# Django uses a inmemory cache at default. This supports only one thread. If
# you use more then one thread another caching backend is required. We recommand
# django-redis: https://niwinz.github.io/django-redis/latest/#_user_guide
# CACHES = { # CACHES = {
# "default": { # "default": {

View File

@ -40,7 +40,7 @@ class RetrieveItem(TestCase):
permission = group.permissions.get(content_type__app_label=app_label, codename=codename) permission = group.permissions.get(content_type__app_label=app_label, codename=codename)
group.permissions.remove(permission) group.permissions.remove(permission)
response = self.client.get(reverse('item-detail', args=[self.item.pk])) response = self.client.get(reverse('item-detail', args=[self.item.pk]))
self.assertEqual(response.status_code, status.HTTP_403_PERMISSION_DENIED) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
class TestDBQueries(TestCase): class TestDBQueries(TestCase):

View File

@ -19,7 +19,7 @@ class TestProjectorDBQueries(TestCase):
self.client = APIClient() self.client = APIClient()
config['general_system_enable_anonymous'] = True config['general_system_enable_anonymous'] = True
for index in range(10): for index in range(10):
Projector.objects.create() Projector.objects.create(name="Projector{}".format(index))
def test_admin(self): def test_admin(self):
""" """
@ -137,9 +137,9 @@ class TestConfigDBQueries(TestCase):
* 2 requests to get the permission for anonymous (config and permissions), * 2 requests to get the permission for anonymous (config and permissions),
* 1 to get all config value and * 1 to get all config value and
* 55 requests to find out if anonymous is enabled. * 57 requests to find out if anonymous is enabled.
TODO: The last 55 requests are a bug. TODO: The last 57 requests are a bug.
""" """
with self.assertNumQueries(58): with self.assertNumQueries(60):
self.client.get(reverse('config-list')) self.client.get(reverse('config-list'))

View File

@ -90,7 +90,8 @@ class TestCollectionElement(TestCase):
collection_element.as_channels_message(), collection_element.as_channels_message(),
{'collection_string': 'testmodule/model', {'collection_string': 'testmodule/model',
'id': 42, 'id': 42,
'deleted': False}) 'deleted': False,
'information': {}})
def test_as_autoupdate_for_user(self): def test_as_autoupdate_for_user(self):
collection_element = collection.CollectionElement.from_values('testmodule/model', 42) collection_element = collection.CollectionElement.from_values('testmodule/model', 42)
@ -210,6 +211,20 @@ class TestCollectionElement(TestCase):
mock_Collection.assert_called_once_with('testmodule/model') mock_Collection.assert_called_once_with('testmodule/model')
mock_Collection().add_id_to_cache.assert_called_once_with(42) mock_Collection().add_id_to_cache.assert_called_once_with(42)
def test_equal(self):
self.assertEqual(
collection.CollectionElement.from_values('testmodule/model', 1),
collection.CollectionElement.from_values('testmodule/model', 1))
self.assertEqual(
collection.CollectionElement.from_values('testmodule/model', 1),
collection.CollectionElement.from_values('testmodule/model', 1, deleted=True))
self.assertNotEqual(
collection.CollectionElement.from_values('testmodule/model', 1),
collection.CollectionElement.from_values('testmodule/model', 2))
self.assertNotEqual(
collection.CollectionElement.from_values('testmodule/model', 1),
collection.CollectionElement.from_values('testmodule/other_model', 1))
class TestCollection(TestCase): class TestCollection(TestCase):
@patch('openslides.utils.collection.CollectionElement') @patch('openslides.utils.collection.CollectionElement')