diff --git a/DEVELOPMENT.rst b/DEVELOPMENT.rst index 97628c1fe..047fb03ad 100644 --- a/DEVELOPMENT.rst +++ b/DEVELOPMENT.rst @@ -109,3 +109,31 @@ a. Running Angular.js test cases '''''''''''''''''''''''''''''''' $ 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 diff --git a/openslides/core/models.py b/openslides/core/models.py index c1dd6a509..4088c26e9 100644 --- a/openslides/core/models.py +++ b/openslides/core/models.py @@ -116,7 +116,7 @@ class Projector(RESTModelMixin, models.Model): result[key]['error'] = str(e) 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. """ diff --git a/openslides/utils/autoupdate.py b/openslides/utils/autoupdate.py index 33517477b..a1209d7bb 100644 --- a/openslides/utils/autoupdate.py +++ b/openslides/utils/autoupdate.py @@ -23,7 +23,7 @@ def get_logged_in_users(): 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. @@ -81,7 +81,7 @@ def ws_add_projector(message, projector_id): Group('projector-{}'.format(projector_id)).add(message.reply_channel) # 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. 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) -def send_data(message): #TODO +def send_data(message): # TODO autoupdate """ Informs all users about changed data. """ diff --git a/openslides/utils/collection.py b/openslides/utils/collection.py index 1aa0f35b0..3cd25aefd 100644 --- a/openslides/utils/collection.py +++ b/openslides/utils/collection.py @@ -35,7 +35,7 @@ class CollectionElement: self.instance = instance self.deleted = deleted self.full_data = full_data - self.information = information + self.information = information or {} if instance is not None: self.collection_string = instance.get_collection_string() self.id = instance.pk @@ -56,6 +56,16 @@ class CollectionElement: # then update the 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): """ 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) if raw: - key = self.make_key(key) + key = cache.make_key(key) return key def get_model(self): diff --git a/openslides/utils/models.py b/openslides/utils/models.py index 14b73deb0..eac60b948 100644 --- a/openslides/utils/models.py +++ b/openslides/utils/models.py @@ -61,15 +61,15 @@ class RESTModelMixin: 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 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 autoupdate system. """ - #TODO: Add example in docstring. - #TODO: Fix circular imports + # We don't know how to fix this circular import from .autoupdate import inform_changed_data return_value = super().save(*args, **kwargs) if not skip_autoupdate: @@ -80,16 +80,27 @@ class RESTModelMixin: """ 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 instance_pk = self.pk return_value = super().delete(*args, **kwargs) if not skip_autoupdate: if self != self.get_root_rest_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) else: inform_deleted_data(self.get_collection_string(), instance_pk, information=information) diff --git a/openslides/utils/settings.py.tpl b/openslides/utils/settings.py.tpl index 756d31f7d..7ca33e101 100644 --- a/openslides/utils/settings.py.tpl +++ b/openslides/utils/settings.py.tpl @@ -46,6 +46,17 @@ DEBUG = %(debug)s # 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 = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', @@ -64,7 +75,12 @@ DATABASES = { # 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 = { # "default": { diff --git a/tests/integration/agenda/test_viewsets.py b/tests/integration/agenda/test_viewsets.py index 34287a271..e72d6313b 100644 --- a/tests/integration/agenda/test_viewsets.py +++ b/tests/integration/agenda/test_viewsets.py @@ -40,7 +40,7 @@ class RetrieveItem(TestCase): permission = group.permissions.get(content_type__app_label=app_label, codename=codename) group.permissions.remove(permission) 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): diff --git a/tests/integration/core/test_viewset.py b/tests/integration/core/test_viewset.py index 402c7fc61..926bfa39b 100644 --- a/tests/integration/core/test_viewset.py +++ b/tests/integration/core/test_viewset.py @@ -19,7 +19,7 @@ class TestProjectorDBQueries(TestCase): self.client = APIClient() config['general_system_enable_anonymous'] = True for index in range(10): - Projector.objects.create() + Projector.objects.create(name="Projector{}".format(index)) def test_admin(self): """ @@ -137,9 +137,9 @@ class TestConfigDBQueries(TestCase): * 2 requests to get the permission for anonymous (config and permissions), * 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')) diff --git a/tests/unit/utils/test_collection.py b/tests/unit/utils/test_collection.py index 83b1c2555..9886fdba2 100644 --- a/tests/unit/utils/test_collection.py +++ b/tests/unit/utils/test_collection.py @@ -90,7 +90,8 @@ class TestCollectionElement(TestCase): collection_element.as_channels_message(), {'collection_string': 'testmodule/model', 'id': 42, - 'deleted': False}) + 'deleted': False, + 'information': {}}) def test_as_autoupdate_for_user(self): 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().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): @patch('openslides.utils.collection.CollectionElement')