More tests, development instructions and small changes.
This commit is contained in:
parent
7cd70a568c
commit
ac9c9f4ec3
@ -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
|
||||||
|
@ -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.
|
||||||
"""
|
"""
|
||||||
|
@ -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.
|
||||||
"""
|
"""
|
||||||
|
@ -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):
|
||||||
|
@ -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)
|
||||||
|
@ -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": {
|
||||||
|
@ -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):
|
||||||
|
@ -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'))
|
||||||
|
@ -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')
|
||||||
|
Loading…
Reference in New Issue
Block a user