Merge pull request #1243 from normanjaeckel/ListOfSpeakersView
New projector view with the current list of speakers.
This commit is contained in:
commit
b3d615ddce
@ -9,6 +9,7 @@ Version 1.6 (unreleased)
|
|||||||
[https://github.com/OpenSlides/OpenSlides/issues?milestone=14]
|
[https://github.com/OpenSlides/OpenSlides/issues?milestone=14]
|
||||||
|
|
||||||
Agenda:
|
Agenda:
|
||||||
|
- New projector view with the current list of speakers.
|
||||||
- Added CSV import.
|
- Added CSV import.
|
||||||
Assignment:
|
Assignment:
|
||||||
- Coupled assignment candidates with list of speakers.
|
- Coupled assignment candidates with list of speakers.
|
||||||
|
@ -137,6 +137,12 @@ Redner. Die Einblendung erscheint nur auf Folien von Tagesordnungspunkten.
|
|||||||
:scale-latex: 80
|
:scale-latex: 80
|
||||||
:alt: Projektor-Ansicht mit Rednerlisten-Overlay
|
:alt: Projektor-Ansicht mit Rednerlisten-Overlay
|
||||||
|
|
||||||
|
Schließlich haben Sie die Möglichkeit, die Rednerliste des jeweiligen
|
||||||
|
Tagesordnungspunktes auf einem gesonderten Projektor anzeigen zu lassen.
|
||||||
|
Klicken Sie auf der Tagesordnungsseite oben rechts auf auf den
|
||||||
|
Glocken-Button |bell| und legen Sie die Seite im Vollbildmodus auf einen
|
||||||
|
eigenen Projektor oder Bildschirm.
|
||||||
|
|
||||||
|
|
||||||
Die Rednerliste verwalten
|
Die Rednerliste verwalten
|
||||||
-------------------------
|
-------------------------
|
||||||
|
@ -101,8 +101,8 @@ def agenda_list_of_speakers(sender, **kwargs):
|
|||||||
if slide is None or isinstance(slide, Item):
|
if slide is None or isinstance(slide, Item):
|
||||||
item = slide
|
item = slide
|
||||||
else:
|
else:
|
||||||
# TODO: If there are more the one items, use the first one in the
|
# TODO: If there is more than one item, use the first one in the
|
||||||
# mptt tree that is not closed
|
# mptt tree that is not closed.
|
||||||
try:
|
try:
|
||||||
item = Item.objects.filter(
|
item = Item.objects.filter(
|
||||||
content_type=ContentType.objects.get_for_model(slide),
|
content_type=ContentType.objects.get_for_model(slide),
|
||||||
|
@ -0,0 +1,14 @@
|
|||||||
|
/*
|
||||||
|
* JavaScript functions for agenda CurrentListOfSpeakersProjectorView
|
||||||
|
*/
|
||||||
|
|
||||||
|
function reloadListOfSpeakers() {
|
||||||
|
$.ajax({
|
||||||
|
url: '',
|
||||||
|
success: function (data) {
|
||||||
|
updater.updateProjector(data);
|
||||||
|
setTimeout('reloadListOfSpeakers()', 2000);
|
||||||
|
},
|
||||||
|
dataType: 'json'
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,4 @@
|
|||||||
|
{% extends 'projector.html' %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block title %}{% trans 'List of speakers' %} – {{ block.super }}{% endblock %}
|
@ -39,6 +39,10 @@
|
|||||||
<a href="{% url 'item_csv_import' %}" class="btn btn-mini" rel="tooltip" data-original-title="{% trans 'Import agenda items' %}"><i class="icon-import"></i> {% trans "Import" %}</a>
|
<a href="{% url 'item_csv_import' %}" class="btn btn-mini" rel="tooltip" data-original-title="{% trans 'Import agenda items' %}"><i class="icon-import"></i> {% trans "Import" %}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a href="{% url 'print_agenda' %}" class="btn btn-mini" rel="tooltip" data-original-title="{% trans 'Print agenda as PDF' %}" target="_blank"><i class="icon-print"></i> PDF</a>
|
<a href="{% url 'print_agenda' %}" class="btn btn-mini" rel="tooltip" data-original-title="{% trans 'Print agenda as PDF' %}" target="_blank"><i class="icon-print"></i> PDF</a>
|
||||||
|
{% if perms.core.can_see_projector %}
|
||||||
|
<a href="{% url 'agenda_current_list_of_speakers_projector' %}" class="btn btn-mini" rel="tooltip" data-original-title="{% trans 'Current list of speakers' %}">
|
||||||
|
<i class="icon-bell"></i> {% trans 'List of speakers' %}</a>
|
||||||
|
{% endif %}
|
||||||
</small>
|
</small>
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
|
@ -89,6 +89,10 @@ urlpatterns = patterns(
|
|||||||
views.CurrentListOfSpeakersView.as_view(end_speach=True),
|
views.CurrentListOfSpeakersView.as_view(end_speach=True),
|
||||||
name='agenda_end_speach_on_current_list_of_speakers'),
|
name='agenda_end_speach_on_current_list_of_speakers'),
|
||||||
|
|
||||||
|
url(r'^list_of_speakers/projector/$',
|
||||||
|
views.CurrentListOfSpeakersProjectorView.as_view(),
|
||||||
|
name='agenda_current_list_of_speakers_projector'),
|
||||||
|
|
||||||
url(r'^csv_import/$',
|
url(r'^csv_import/$',
|
||||||
views.ItemCSVImportView.as_view(),
|
views.ItemCSVImportView.as_view(),
|
||||||
name='item_csv_import'))
|
name='item_csv_import'))
|
||||||
|
@ -2,21 +2,33 @@
|
|||||||
# TODO: Rename all views and template names
|
# TODO: Rename all views and template names
|
||||||
|
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
from json import dumps
|
||||||
|
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
from django.contrib.staticfiles.templatetags.staticfiles import static
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.db.models import Model
|
from django.db.models import Model
|
||||||
|
from django.template.loader import render_to_string
|
||||||
|
from django.utils.datastructures import SortedDict
|
||||||
|
from django.utils.safestring import mark_safe
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
from django.utils.translation import ugettext_lazy
|
from django.utils.translation import ugettext_lazy
|
||||||
from reportlab.platypus import Paragraph
|
from reportlab.platypus import Paragraph
|
||||||
|
|
||||||
from openslides.config.api import config
|
from openslides.config.api import config
|
||||||
from openslides.projector.api import get_active_slide, update_projector
|
from openslides.projector.api import (
|
||||||
|
get_active_object,
|
||||||
|
get_active_slide,
|
||||||
|
get_projector_overlays_js,
|
||||||
|
get_overlays,
|
||||||
|
update_projector)
|
||||||
from openslides.utils.exceptions import OpenSlidesError
|
from openslides.utils.exceptions import OpenSlidesError
|
||||||
from openslides.utils.pdf import stylesheet
|
from openslides.utils.pdf import stylesheet
|
||||||
from openslides.utils.utils import html_strong
|
from openslides.utils.utils import html_strong
|
||||||
from openslides.utils.views import (
|
from openslides.utils.views import (
|
||||||
|
AjaxMixin,
|
||||||
CreateView,
|
CreateView,
|
||||||
CSVImportView,
|
CSVImportView,
|
||||||
DeleteView,
|
DeleteView,
|
||||||
@ -630,6 +642,108 @@ class CurrentListOfSpeakersView(RedirectView):
|
|||||||
return reverse('item_view', args=[item.pk])
|
return reverse('item_view', args=[item.pk])
|
||||||
|
|
||||||
|
|
||||||
|
class CurrentListOfSpeakersProjectorView(AjaxMixin, TemplateView):
|
||||||
|
"""
|
||||||
|
View with the current list of speakers depending on the active slide.
|
||||||
|
Usefule for the projector.
|
||||||
|
"""
|
||||||
|
template_name = 'agenda/current_list_of_speakers_projector.html'
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Returns response object depending on request type (ajax or normal).
|
||||||
|
"""
|
||||||
|
if request.is_ajax():
|
||||||
|
value = self.ajax_get(request, *args, **kwargs)
|
||||||
|
else:
|
||||||
|
value = super(CurrentListOfSpeakersProjectorView, self).get(request, *args, **kwargs)
|
||||||
|
return value
|
||||||
|
|
||||||
|
def get_item(self):
|
||||||
|
"""
|
||||||
|
Returns the item of the current slide is an agenda item slide or a
|
||||||
|
slide of a related model else returns None.
|
||||||
|
"""
|
||||||
|
slide_object = get_active_object()
|
||||||
|
if slide_object is None or isinstance(slide_object, Item):
|
||||||
|
item = slide_object
|
||||||
|
else:
|
||||||
|
# TODO: If there is more than one item, use the first one in the
|
||||||
|
# mptt tree that is not closed.
|
||||||
|
try:
|
||||||
|
item = Item.objects.filter(
|
||||||
|
content_type=ContentType.objects.get_for_model(slide_object),
|
||||||
|
object_id=slide_object.pk)[0]
|
||||||
|
except IndexError:
|
||||||
|
item = None
|
||||||
|
return item
|
||||||
|
|
||||||
|
def get_content(self):
|
||||||
|
"""
|
||||||
|
Returns the content of this slide.
|
||||||
|
"""
|
||||||
|
item = self.get_item()
|
||||||
|
if item is None:
|
||||||
|
content = mark_safe('<h1>%s</h1><i>%s</i>\n' % (_('List of speakers'), _('Not available.')))
|
||||||
|
else:
|
||||||
|
content_dict = {
|
||||||
|
'title': item.get_title(),
|
||||||
|
'item': item,
|
||||||
|
'list_of_speakers': item.get_list_of_speakers(
|
||||||
|
old_speakers_count=config['agenda_show_last_speakers'])}
|
||||||
|
content = render_to_string('agenda/item_slide_list_of_speaker.html', content_dict)
|
||||||
|
return content
|
||||||
|
|
||||||
|
def get_overlays_and_overlay_js(self):
|
||||||
|
"""
|
||||||
|
Returns the overlays and their JavaScript for this slide as a
|
||||||
|
two-tuple. The overlay 'agenda_speaker' is always excluded.
|
||||||
|
|
||||||
|
The required JavaScript fot this view is inserted.
|
||||||
|
"""
|
||||||
|
overlays = get_overlays(only_active=True)
|
||||||
|
overlays.pop('agenda_speaker', None)
|
||||||
|
overlay_js = get_projector_overlays_js(as_json=True)
|
||||||
|
# Note: The JavaScript content of overlay 'agenda_speaker' is not
|
||||||
|
# excluded because this overlay has no such content at the moment.
|
||||||
|
extra_js = SortedDict()
|
||||||
|
extra_js['load_file'] = static('js/agenda_current_list_of_speakers_projector.js')
|
||||||
|
extra_js['call'] = 'reloadListOfSpeakers();'
|
||||||
|
extra_js = dumps(extra_js)
|
||||||
|
overlay_js.append(extra_js)
|
||||||
|
return overlays, overlay_js
|
||||||
|
|
||||||
|
def get_context_data(self, **context):
|
||||||
|
"""
|
||||||
|
Returns the context for the projector template. Contains the content
|
||||||
|
of this slide.
|
||||||
|
"""
|
||||||
|
overlays, overlay_js = self.get_overlays_and_overlay_js()
|
||||||
|
return super(CurrentListOfSpeakersProjectorView, self).get_context_data(
|
||||||
|
content=self.get_content(),
|
||||||
|
overlays=overlays,
|
||||||
|
overlay_js=overlay_js,
|
||||||
|
**context)
|
||||||
|
|
||||||
|
def get_ajax_context(self, **context):
|
||||||
|
"""
|
||||||
|
Returns the context including the slide content for ajax response. The
|
||||||
|
overlay 'agenda_speaker' is always excluded.
|
||||||
|
"""
|
||||||
|
overlay_dict = {}
|
||||||
|
for overlay in get_overlays().values():
|
||||||
|
if overlay.is_active() and overlay.name != 'agenda_speaker':
|
||||||
|
overlay_dict[overlay.name] = {
|
||||||
|
'html': overlay.get_projector_html(),
|
||||||
|
'javascript': overlay.get_javascript()}
|
||||||
|
else:
|
||||||
|
overlay_dict[overlay.name] = None
|
||||||
|
return super(CurrentListOfSpeakersProjectorView, self).get_ajax_context(
|
||||||
|
content=self.get_content(),
|
||||||
|
overlays=overlay_dict,
|
||||||
|
**context)
|
||||||
|
|
||||||
|
|
||||||
class ItemCSVImportView(CSVImportView):
|
class ItemCSVImportView(CSVImportView):
|
||||||
"""
|
"""
|
||||||
Imports agenda items from an uploaded csv file.
|
Imports agenda items from an uploaded csv file.
|
||||||
|
@ -104,29 +104,22 @@ def default_slide():
|
|||||||
return render_to_string('projector/default_slide.html')
|
return render_to_string('projector/default_slide.html')
|
||||||
|
|
||||||
|
|
||||||
def get_overlays():
|
def get_overlays(only_active=False):
|
||||||
"""
|
"""
|
||||||
Returns all overlay objects.
|
Returns all overlay objects.
|
||||||
|
|
||||||
|
If only_active is True, returns only active overlays.
|
||||||
|
|
||||||
The returned value is a dictonary with the name of the overlay as key, and
|
The returned value is a dictonary with the name of the overlay as key, and
|
||||||
the overlay object as value.
|
the overlay object as value.
|
||||||
"""
|
"""
|
||||||
overlays = {}
|
overlays = {}
|
||||||
for receiver, overlay in projector_overlays.send(sender='get_overlays'):
|
for receiver, overlay in projector_overlays.send(sender='get_overlays'):
|
||||||
|
if not only_active or overlay.is_active():
|
||||||
overlays[overlay.name] = overlay
|
overlays[overlay.name] = overlay
|
||||||
return overlays
|
return overlays
|
||||||
|
|
||||||
|
|
||||||
def get_projector_overlays():
|
|
||||||
"""
|
|
||||||
Returns the HTML code for all active overlays.
|
|
||||||
"""
|
|
||||||
overlays = [{'name': key, 'html': overlay.get_projector_html()}
|
|
||||||
for key, overlay in get_overlays().items()
|
|
||||||
if overlay.is_active()]
|
|
||||||
return render_to_string('projector/all_overlays.html', {'overlays': overlays})
|
|
||||||
|
|
||||||
|
|
||||||
def get_projector_overlays_js(as_json=False):
|
def get_projector_overlays_js(as_json=False):
|
||||||
"""
|
"""
|
||||||
Returns JS-Code for the active overlays.
|
Returns JS-Code for the active overlays.
|
||||||
|
@ -8,8 +8,7 @@
|
|||||||
<link href="{% static 'css/bootstrap.min.css' %}" type="text/css" rel="stylesheet">
|
<link href="{% static 'css/bootstrap.min.css' %}" type="text/css" rel="stylesheet">
|
||||||
<link href="{% static 'css/projector.css' %}" type="text/css" rel="stylesheet">
|
<link href="{% static 'css/projector.css' %}" type="text/css" rel="stylesheet">
|
||||||
<link href="{% static 'img/favicon.png' %}" type="image/png" rel="shortcut icon">
|
<link href="{% static 'img/favicon.png' %}" type="image/png" rel="shortcut icon">
|
||||||
<title>{% trans 'Projector' %} – {{ 'event_name'|get_config }}</title>
|
<title>{% block title %}{% trans 'Projector' %} – {{ 'event_name'|get_config }}{% endblock %}</title>
|
||||||
|
|
||||||
<script type="text/javascript" src="{% static 'js/jquery/jquery.min.js' %}"></script>
|
<script type="text/javascript" src="{% static 'js/jquery/jquery.min.js' %}"></script>
|
||||||
<script type="text/javascript" src="{% static 'js/sockjs-0.3.min.js' %}"></script>
|
<script type="text/javascript" src="{% static 'js/sockjs-0.3.min.js' %}"></script>
|
||||||
<script type="text/javascript" src="{% static 'js/projector.js' %}"></script>
|
<script type="text/javascript" src="{% static 'js/projector.js' %}"></script>
|
||||||
@ -59,7 +58,9 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="overlays">
|
<div id="overlays">
|
||||||
{{ overlays }}
|
{% for overlay in overlays.values %}
|
||||||
|
{{ overlay.get_projector_html|safe }}
|
||||||
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="content" {% if reload %}class="reload"{% endif %}>
|
<div id="content" {% if reload %}class="reload"{% endif %}>
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
{% for overlay in overlays %}
|
|
||||||
{{ overlay.html|safe }}
|
|
||||||
{% endfor %}
|
|
@ -6,7 +6,7 @@ from openslides.utils.tornado_webserver import ProjectorSocketHandler
|
|||||||
from openslides.utils.views import RedirectView, TemplateView
|
from openslides.utils.views import RedirectView, TemplateView
|
||||||
|
|
||||||
from .api import (call_on_projector, get_active_slide,
|
from .api import (call_on_projector, get_active_slide,
|
||||||
get_overlays, get_projector_content, get_projector_overlays,
|
get_overlays, get_projector_content,
|
||||||
get_projector_overlays_js, reset_countdown, set_active_slide,
|
get_projector_overlays_js, reset_countdown, set_active_slide,
|
||||||
start_countdown, stop_countdown, update_projector_overlay)
|
start_countdown, stop_countdown, update_projector_overlay)
|
||||||
|
|
||||||
@ -24,7 +24,7 @@ class ProjectorView(TemplateView):
|
|||||||
if callback is None:
|
if callback is None:
|
||||||
kwargs.update({
|
kwargs.update({
|
||||||
'content': get_projector_content(),
|
'content': get_projector_content(),
|
||||||
'overlays': get_projector_overlays(),
|
'overlays': get_overlays(only_active=True),
|
||||||
'overlay_js': get_projector_overlays_js(as_json=True),
|
'overlay_js': get_projector_overlays_js(as_json=True),
|
||||||
'reload': True,
|
'reload': True,
|
||||||
'calls': config['projector_js_cache']})
|
'calls': config['projector_js_cache']})
|
||||||
|
@ -314,3 +314,23 @@ class TestOverlay(TestCase):
|
|||||||
value = agenda_list_of_speakers(sender='test').get_projector_html()
|
value = agenda_list_of_speakers(sender='test').get_projector_html()
|
||||||
|
|
||||||
self.assertEqual(value, '')
|
self.assertEqual(value, '')
|
||||||
|
|
||||||
|
|
||||||
|
class TestCurrentListOfSpeakersOnProjectorView(SpeakerViewTestCase):
|
||||||
|
"""
|
||||||
|
Test the view with the current list of speakers depending on the actual
|
||||||
|
slide.
|
||||||
|
"""
|
||||||
|
def test_get_none(self):
|
||||||
|
response = self.admin_client.get('/agenda/list_of_speakers/projector/')
|
||||||
|
self.assertContains(response, 'List of speakers</h1><i>Not available')
|
||||||
|
|
||||||
|
def test_get_normal(self):
|
||||||
|
self.item1.title = 'title_gupooDee8ahahnaxoo2a'
|
||||||
|
self.item1.save()
|
||||||
|
Speaker.objects.add(self.speaker1, self.item1)
|
||||||
|
config['projector_active_slide'] = {'callback': 'agenda', 'pk': self.item1.pk}
|
||||||
|
response = self.admin_client.get('/agenda/list_of_speakers/projector/')
|
||||||
|
self.assertContains(response, 'List of speakers')
|
||||||
|
self.assertContains(response, 'title_gupooDee8ahahnaxoo2a')
|
||||||
|
self.assertContains(response, 'speaker1')
|
||||||
|
@ -91,30 +91,17 @@ class ApiFunctions(TestCase):
|
|||||||
mock_overlay = MagicMock()
|
mock_overlay = MagicMock()
|
||||||
mock_overlay.name = 'mock_overlay'
|
mock_overlay.name = 'mock_overlay'
|
||||||
mock_projector_overlays.send.return_value = ((None, mock_overlay), )
|
mock_projector_overlays.send.return_value = ((None, mock_overlay), )
|
||||||
|
|
||||||
value = projector_api.get_overlays()
|
value = projector_api.get_overlays()
|
||||||
self.assertEqual(value, {'mock_overlay': mock_overlay})
|
self.assertEqual(value, {'mock_overlay': mock_overlay})
|
||||||
|
|
||||||
@patch('openslides.projector.api.render_to_string')
|
@patch('openslides.projector.api.projector_overlays')
|
||||||
@patch('openslides.projector.api.get_overlays')
|
def test_get_overlays_inactive(self, mock_projector_overlays):
|
||||||
def test_get_projector_overlays(self, mock_get_overlays, mock_render_to_string):
|
|
||||||
mock_overlay = MagicMock()
|
mock_overlay = MagicMock()
|
||||||
mock_overlay.get_projector_html.return_value = 'some html'
|
mock_overlay.name = 'mock_overlay_2'
|
||||||
mock_get_overlays.return_value = {'overlay_name': mock_overlay}
|
|
||||||
|
|
||||||
# Test with inactive overlay
|
|
||||||
mock_overlay.is_active.return_value = False
|
mock_overlay.is_active.return_value = False
|
||||||
projector_api.get_projector_overlays()
|
mock_projector_overlays.send.return_value = ((None, mock_overlay), )
|
||||||
mock_render_to_string.assert_called_with(
|
value = projector_api.get_overlays(only_active=True)
|
||||||
'projector/all_overlays.html',
|
self.assertNotEqual(value, {'mock_overlay_2': mock_overlay})
|
||||||
{'overlays': []})
|
|
||||||
|
|
||||||
# Test with active overlay
|
|
||||||
mock_overlay.is_active.return_value = True
|
|
||||||
projector_api.get_projector_overlays()
|
|
||||||
mock_render_to_string.assert_Called_with(
|
|
||||||
'projector/all_overlays.html',
|
|
||||||
{'overlays': [{'name': 'overlay_name', 'html': 'some html'}]})
|
|
||||||
|
|
||||||
@patch('openslides.projector.api.get_overlays')
|
@patch('openslides.projector.api.get_overlays')
|
||||||
def test_get_projector_overlays_js(self, mock_get_overlays):
|
def test_get_projector_overlays_js(self, mock_get_overlays):
|
||||||
|
@ -13,9 +13,9 @@ class ProjectorViewTest(TestCase):
|
|||||||
rf = RequestFactory()
|
rf = RequestFactory()
|
||||||
|
|
||||||
@patch('openslides.projector.views.get_projector_overlays_js')
|
@patch('openslides.projector.views.get_projector_overlays_js')
|
||||||
@patch('openslides.projector.views.get_projector_overlays')
|
@patch('openslides.projector.views.get_overlays')
|
||||||
@patch('openslides.projector.views.get_projector_content')
|
@patch('openslides.projector.views.get_projector_content')
|
||||||
def test_get(self, mock_get_projector_content, mock_get_projector_overlays,
|
def test_get(self, mock_get_projector_content, mock_get_overlays,
|
||||||
mock_get_projector_overlays_js):
|
mock_get_projector_overlays_js):
|
||||||
view = views.ProjectorView()
|
view = views.ProjectorView()
|
||||||
view.request = self.rf.get('/')
|
view.request = self.rf.get('/')
|
||||||
@ -34,7 +34,7 @@ class ProjectorViewTest(TestCase):
|
|||||||
with patch('openslides.projector.views.config', mock_config):
|
with patch('openslides.projector.views.config', mock_config):
|
||||||
context = view.get_context_data()
|
context = view.get_context_data()
|
||||||
mock_get_projector_content.assert_called_with()
|
mock_get_projector_content.assert_called_with()
|
||||||
mock_get_projector_overlays.assert_called_with()
|
mock_get_overlays.assert_called_with(only_active=True)
|
||||||
mock_get_projector_overlays_js.assert_called_with(as_json=True)
|
mock_get_projector_overlays_js.assert_called_with(as_json=True)
|
||||||
self.assertTrue(context['reload'])
|
self.assertTrue(context['reload'])
|
||||||
self.assertEqual(context['calls'], 'js_cache')
|
self.assertEqual(context['calls'], 'js_cache')
|
||||||
|
Loading…
Reference in New Issue
Block a user