Merge remote-tracking branch 'openslides/stable/1.4.x'

Conflicts:
	openslides/__init__.py
This commit is contained in:
Oskar Hahn 2013-07-29 20:48:12 +02:00
commit b8d23164f7
19 changed files with 2984 additions and 29 deletions

View File

@ -10,3 +10,4 @@ Authors of OpenSlides in chronological order of first contribution:
Stefan Frauenknecht <stefan@frauenknecht.net> Stefan Frauenknecht <stefan@frauenknecht.net>
Roland Geider <roland@geider.net> Roland Geider <roland@geider.net>
Tobias Hößl <tobias@hoessl.eu> Tobias Hößl <tobias@hoessl.eu>
Pavel Fric <pavelfric@seznam.cz> (Czech translation)

View File

@ -4,9 +4,24 @@
http://openslides.org http://openslides.org
Version 1.4.2 (unreleased)
Version 1.4.1 (unreleased)
========================== ==========================
Version 1.4.1 (2013-07-29)
==========================
[https://github.com/OpenSlides/OpenSlides/issues?milestone=11]
- Fix tooltip which shows the end of each agenda item.
- Fix duration of agenda with closed agenda items.
- Disable deleting active version of a motion.
- Start browser on custom IP address.
- Fix wrong URLs to polls in motion detail view.
- Add Czech translation.
Version 1.4 (2013-07-10)
========================
[https://github.com/OpenSlides/OpenSlides/issues?milestone=7] [https://github.com/OpenSlides/OpenSlides/issues?milestone=7]
Agenda: Agenda:

View File

@ -1,6 +1,6 @@
============================================== ================================================
Installation instructions for OpenSlides 1.4 Installation instructions for OpenSlides 1.4.1
============================================== ================================================
Content Content
======= =======
@ -90,7 +90,7 @@ II. Installation on GNU/Linux and MacOSX using the package from openslides.org
2. Follow the same steps as in I. but use in step 3. 2. Follow the same steps as in I. but use in step 3.
$ pip install openslides-1.4.tar.gz $ pip install openslides-1.4.1.tar.gz
instead of $ pip install openslides. instead of $ pip install openslides.

View File

@ -2,7 +2,7 @@
English README file for OpenSlides English README file for OpenSlides
==================================== ====================================
This is OpenSlides, version 1.4.1 (unreleased). This is OpenSlides, version 1.4.2 (unreleased).
What is OpenSlides? What is OpenSlides?

View File

@ -45,9 +45,9 @@
<div class="duration"> <div class="duration">
{% if node.duration %} {% if node.duration %}
{{ node.duration }} h {{ node.duration }} h
{% if start and end %} {% if node.tooltip %}
<a class="btn btn-mini" rel="tooltip" data-original-title="{% trans 'End' %}: <a class="btn btn-mini" rel="tooltip" data-original-title="{% trans 'End' %}:
{{ end|date:"DATETIME_FORMAT" }}"><i class="icon-clock"></i> {{ node.tooltip|date:"DATETIME_FORMAT" }}"><i class="icon-clock"></i>
</a> </a>
{% endif %} {% endif %}
{% endif %} {% endif %}

View File

@ -111,7 +111,7 @@
{% if items %} {% if items %}
<ol class="agenda_list {% if perms.agenda.can_manage_agenda %}sortable{% endif %}"> <ol class="agenda_list {% if perms.agenda.can_manage_agenda %}sortable{% endif %}">
{% recursetree items %} {% recursetree items %}
<li class="draggable{% if item.active %} activeline{% endif %}{% if item.closed %} offline{% endif %}"> <li class="draggable">
{% include "agenda/item_row.html" %} {% include "agenda/item_row.html" %}
{% if not node.is_leaf_node %} {% if not node.is_leaf_node %}
<ol> <ol>

View File

@ -50,6 +50,13 @@ class Overview(TemplateView):
else: else:
items = Item.objects.filter(type__exact=Item.AGENDA_ITEM) items = Item.objects.filter(type__exact=Item.AGENDA_ITEM)
# Save the items as a list (not a queryset). This is important,
# because in other case, django-mtpp reloads the items in the
# template. But we add some attributes (in this function), which are
# not in the database and would be lost if the items were reloaded.
# TODO: Try to remove this line in later versions of django-mptt
items = list(items)
start = config['agenda_start_event_date_time'] start = config['agenda_start_event_date_time']
if start is None or len(start) == 0: if start is None or len(start) == 0:
start = None start = None
@ -59,8 +66,8 @@ class Overview(TemplateView):
duration = timedelta() duration = timedelta()
for item in items: for item in items:
if not item.closed and (item.duration is not None if (item.duration is not None and
and len(item.duration) > 0): len(item.duration) > 0):
duration_list = item.duration.split(':') duration_list = item.duration.split(':')
duration += timedelta(hours=int(duration_list[0]), duration += timedelta(hours=int(duration_list[0]),
minutes=int(duration_list[1])) minutes=int(duration_list[1]))

View File

@ -33,6 +33,7 @@ LANGUAGES = (
('de', ugettext('German')), ('de', ugettext('German')),
('en', ugettext('English')), ('en', ugettext('English')),
('fr', ugettext('French')), ('fr', ugettext('French')),
('cs', ugettext('Czech')),
) )
# If you set this to False, Django will make some optimizations so as not # If you set this to False, Django will make some optimizations so as not

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@ -0,0 +1,260 @@
# Language file (for JavaScript) of OpenSlides used by transifex:
# https://www.transifex.com/projects/p/openslides/
# Copyright (C) 20112013 by OpenSlides team, see AUTHORS.
# This file is distributed under the same license as the OpenSlides package.
# Translators:
# emanuel <emanuel@intevation.de>, 2013
# emanuelschuetze <emanuelschuetze@gmail.com>, 2013
# ostcar <mail@oshahn.de>, 2012
# fri <pavelfric@seznam.cz>, 2013
msgid ""
msgstr ""
"Project-Id-Version: OpenSlides\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2013-07-07 23:49+0200\n"
"PO-Revision-Date: 2013-07-15 23:01+0000\n"
"Last-Translator: fri <pavelfric@seznam.cz>\n"
"Language-Team: Czech (http://www.transifex.com/projects/p/openslides/language/cs/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: cs\n"
"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
#: agenda/static/javascript/agenda-config-datepicker.js:9
#: agenda/static/javascript/agenda-config-datepicker.js:39
msgid "en"
msgstr "en"
#: agenda/static/javascript/agenda-config-datepicker.js:10
msgid "previous month"
msgstr "Předchozí měsíc"
#: agenda/static/javascript/agenda-config-datepicker.js:11
msgid "next month"
msgstr "Další měsíc"
#: agenda/static/javascript/agenda-config-datepicker.js:13
msgid "January"
msgstr "Leden"
#: agenda/static/javascript/agenda-config-datepicker.js:13
msgid "February"
msgstr "Únor"
#: agenda/static/javascript/agenda-config-datepicker.js:13
msgid "March"
msgstr "Březen"
#: agenda/static/javascript/agenda-config-datepicker.js:14
msgid "April"
msgstr "Duben"
#: agenda/static/javascript/agenda-config-datepicker.js:14
#: agenda/static/javascript/agenda-config-datepicker.js:20
msgid "May"
msgstr "Květen"
#: agenda/static/javascript/agenda-config-datepicker.js:14
msgid "June"
msgstr "Červen"
#: agenda/static/javascript/agenda-config-datepicker.js:15
msgid "July"
msgstr "Červenec"
#: agenda/static/javascript/agenda-config-datepicker.js:15
msgid "August"
msgstr "Srpen"
#: agenda/static/javascript/agenda-config-datepicker.js:15
msgid "September"
msgstr "Září"
#: agenda/static/javascript/agenda-config-datepicker.js:16
msgid "October"
msgstr "Říjen"
#: agenda/static/javascript/agenda-config-datepicker.js:16
msgid "November"
msgstr "Listopad"
#: agenda/static/javascript/agenda-config-datepicker.js:16
msgid "December"
msgstr "Prosinec"
#: agenda/static/javascript/agenda-config-datepicker.js:19
msgid "Jan"
msgstr "Led"
#: agenda/static/javascript/agenda-config-datepicker.js:19
msgid "Feb"
msgstr "Úno"
#: agenda/static/javascript/agenda-config-datepicker.js:19
msgid "Mar"
msgstr "Bře"
#: agenda/static/javascript/agenda-config-datepicker.js:20
msgid "Apr"
msgstr "Dub"
#: agenda/static/javascript/agenda-config-datepicker.js:20
msgid "Jun"
msgstr "Čer"
#: agenda/static/javascript/agenda-config-datepicker.js:21
msgid "Jul"
msgstr "Čec"
#: agenda/static/javascript/agenda-config-datepicker.js:21
msgid "Aug"
msgstr "Srp"
#: agenda/static/javascript/agenda-config-datepicker.js:21
msgid "Sep"
msgstr "Zář"
#: agenda/static/javascript/agenda-config-datepicker.js:22
msgid "Oct"
msgstr "Říj"
#: agenda/static/javascript/agenda-config-datepicker.js:22
msgid "Nov"
msgstr "Lis"
#: agenda/static/javascript/agenda-config-datepicker.js:22
msgid "Dec"
msgstr "Pro"
#: agenda/static/javascript/agenda-config-datepicker.js:25
msgid "Sunday"
msgstr "Neděle"
#: agenda/static/javascript/agenda-config-datepicker.js:25
msgid "Monday"
msgstr "Pondělí"
#: agenda/static/javascript/agenda-config-datepicker.js:25
msgid "Tuesdey"
msgstr "Úterý"
#: agenda/static/javascript/agenda-config-datepicker.js:25
msgid "Wednesday"
msgstr "Středa"
#: agenda/static/javascript/agenda-config-datepicker.js:26
msgid "Thursday"
msgstr "Čtvrtek"
#: agenda/static/javascript/agenda-config-datepicker.js:26
msgid "Friday"
msgstr "Pátek"
#: agenda/static/javascript/agenda-config-datepicker.js:26
msgid "Saturday"
msgstr "Sobota"
#: agenda/static/javascript/agenda-config-datepicker.js:29
#: agenda/static/javascript/agenda-config-datepicker.js:33
msgid "Su"
msgstr "Ne"
#: agenda/static/javascript/agenda-config-datepicker.js:29
#: agenda/static/javascript/agenda-config-datepicker.js:33
msgid "Mo"
msgstr "Po"
#: agenda/static/javascript/agenda-config-datepicker.js:29
#: agenda/static/javascript/agenda-config-datepicker.js:33
msgid "Tu"
msgstr "Út"
#: agenda/static/javascript/agenda-config-datepicker.js:29
#: agenda/static/javascript/agenda-config-datepicker.js:33
msgid "We"
msgstr "St"
#: agenda/static/javascript/agenda-config-datepicker.js:30
#: agenda/static/javascript/agenda-config-datepicker.js:34
msgid "Th"
msgstr "Čt"
#: agenda/static/javascript/agenda-config-datepicker.js:30
#: agenda/static/javascript/agenda-config-datepicker.js:34
msgid "Fr"
msgstr "Pá"
#: agenda/static/javascript/agenda-config-datepicker.js:30
#: agenda/static/javascript/agenda-config-datepicker.js:34
msgid "Sa"
msgstr "So"
#: agenda/static/javascript/agenda-config-datepicker.js:45
msgid "Time"
msgstr "Čas"
#: agenda/static/javascript/agenda-config-datepicker.js:46
msgid "Hour"
msgstr "Hodina"
#: agenda/static/javascript/agenda-config-datepicker.js:47
msgid "Minute"
msgstr "Minuta"
#: agenda/static/javascript/agenda-config-datepicker.js:48
msgid "Current time"
msgstr "Nynější čas"
#: agenda/static/javascript/agenda-config-datepicker.js:49
msgid "Close"
msgstr "Zavřít"
#: agenda/static/javascript/agenda.js:17
#, c-format
msgid ", of which %s are hidden."
msgstr ", z toho %s skryto."
#: static/javascript/dataTables.bootstrap.js:14
msgid "All"
msgstr "Vše"
#: static/javascript/dataTables.bootstrap.js:19
msgid "_MENU_ entries per page"
msgstr "_MENU_ záznamů na stranu"
#: static/javascript/dataTables.bootstrap.js:20
msgid "Search:"
msgstr "Hledání:"
#: static/javascript/dataTables.bootstrap.js:21
msgid "Showing _START_ to _END_ of _TOTAL_ entries"
msgstr "_START_ až _END_ z _TOTAL_ záznamů"
#: static/javascript/dataTables.bootstrap.js:22
msgid "Showing 0 entries"
msgstr "0 záznamů"
#: static/javascript/dataTables.bootstrap.js:23
msgid "(filtered from _MAX_ total entries)"
msgstr "(filtrováno z _MAX_ záznamů)"
#: static/javascript/dataTables.bootstrap.js:24
msgid "No matching records found"
msgstr "Nebyly nalezeny žádné odpovídající záznamy"
#: static/javascript/dataTables.bootstrap.js:26
msgid "First"
msgstr "První"
#: static/javascript/dataTables.bootstrap.js:27
msgid "Last"
msgstr "Poslední"
#: static/javascript/dataTables.bootstrap.js:28
msgid "Next"
msgstr "Další"
#: static/javascript/dataTables.bootstrap.js:29
msgid "Previous"
msgstr "Předchozí"

View File

@ -203,11 +203,15 @@ def _main(opts, database_path=None):
reload = False reload = False
if opts.start_browser: if opts.start_browser:
if opts.address:
prefix = opts.address
else:
prefix = 'localhost'
if port == 80: if port == 80:
suffix = "" suffix = ""
else: else:
suffix = ":%d" % port suffix = ":%d" % port
start_browser("http://localhost%s" % suffix) start_browser("http://%s%s" % (prefix, suffix))
# Start the server # Start the server
run_tornado(addr, port, reload) run_tornado(addr, port, reload)

View File

@ -100,7 +100,7 @@ class Motion(SlideMixin, models.Model):
""" """
Return a human readable name of this motion. Return a human readable name of this motion.
""" """
return self.active_version.title return self.get_active_version().title
# TODO: Use transaction # TODO: Use transaction
def save(self, use_version=None, *args, **kwargs): def save(self, use_version=None, *args, **kwargs):

View File

@ -69,18 +69,18 @@
{# TODO: show only for workflow with versioning #} {# TODO: show only for workflow with versioning #}
{% with last_version=motion.get_last_version active_version=motion.get_active_version %} {% with last_version=motion.get_last_version active_version=motion.get_active_version %}
{% if version.version_number != last_version.version_number %} {% if version.version_number != last_version.version_number %}
<span class="label label-warning"> <p><span class="label label-warning">
<i class="icon-warning-sign icon-white"></i> {% trans "This is not the newest version." %} <i class="icon-warning-sign icon-white"></i> {% trans "This is not the newest version." %}
</span> </span>
<a href="{% model_url last_version %}" class="btn btn-small">{% trans "Go to the newest version" %} <a href="{% model_url last_version %}" class="btn btn-small">{% trans "Go to the newest version" %}
(# {{ last_version.version_number }})</a> (# {{ last_version.version_number }})</a></p>
{% endif %} {% endif %}
{% if version.version_number != active_version.version_number %} {% if version.version_number != active_version.version_number %}
<span class="label label-warning"> <p><span class="label label-warning">
<i class="icon-warning-sign icon-white"></i> {% trans "This version is not authorized." %} <i class="icon-warning-sign icon-white"></i> {% trans "This version is not authorized." %}
</span> </span>
<a href="{% model_url active_version %}" class="btn btn-small">{% trans "Go to the authorized version" %} <a href="{% model_url active_version %}" class="btn btn-small">{% trans "Go to the authorized version" %}
(# {{ active_version.version_number }})</a> (# {{ active_version.version_number }})</a></p>
{% endif %} {% endif %}
{% endwith %} {% endwith %}
@ -130,9 +130,11 @@
<a href="{% model_url version %}" title="{% trans 'Show' %}" class="btn btn-mini"> <a href="{% model_url version %}" title="{% trans 'Show' %}" class="btn btn-mini">
<i class="icon-search"></i> <i class="icon-search"></i>
</a> </a>
{% if version != motion.active_version %}
<a href="{% model_url version 'delete' %}" title="{% trans 'Delete' %}" class="btn btn-mini"> <a href="{% model_url version 'delete' %}" title="{% trans 'Delete' %}" class="btn btn-mini">
<i class="icon-remove"></i> <i class="icon-remove"></i>
</a> </a>
{% endif %}
</td> </td>
</tr> </tr>
{% if forloop.last %} {% if forloop.last %}
@ -198,8 +200,8 @@
{% if perms.motion.can_manage_motion or poll.has_votes %} {% if perms.motion.can_manage_motion or poll.has_votes %}
<li><b>{{ poll.poll_number|ordinal|safe }} {% trans "vote" %}</b> <li><b>{{ poll.poll_number|ordinal|safe }} {% trans "vote" %}</b>
{% if perms.motion.can_manage_motion %} {% if perms.motion.can_manage_motion %}
<a class="btn btn-mini" href="{% url 'motion_poll_edit' motion.id poll.id %}" title="{% trans 'Edit Vote' %}"><i class="icon-pencil"></i></a> <a class="btn btn-mini" href="{% model_url poll 'edit' %}" title="{% trans 'Edit Vote' %}"><i class="icon-pencil"></i></a>
<a class="btn btn-mini" href="{% url 'motion_poll_delete' motion.id poll.id %}" title="{% trans 'Delete Vote' %}"><i class="icon-remove"></i></a> <a class="btn btn-mini" href="{% model_url poll 'delete' %}" title="{% trans 'Delete Vote' %}"><i class="icon-remove"></i></a>
{% endif %} {% endif %}
<br> <br>
{% if poll.has_votes %} {% if poll.has_votes %}

View File

@ -303,10 +303,19 @@ class VersionDeleteView(DeleteView):
success_url_name = 'motion_detail' success_url_name = 'motion_detail'
def get_object(self): def get_object(self):
motion_id = int(self.kwargs.get('pk')) try:
version_number = int(self.kwargs.get('version_number')) motion = Motion.objects.get(pk=int(self.kwargs.get('pk')))
return MotionVersion.objects.get(motion=motion_id, except Motion.DoesNotExist:
version_number=version_number) raise Http404('Motion %s not found.' % self.kwargs.get('pk'))
try:
version = MotionVersion.objects.get(
motion=motion,
version_number=int(self.kwargs.get('version_number')))
except MotionVersion.DoesNotExist:
raise Http404('Version %s not found.' % self.kwargs.get('version_number'))
if version == motion.active_version:
raise Http404('You can not delete the active version of a motion.')
return version
def get_success_url_name_args(self): def get_success_url_name_args(self):
return (self.object.motion_id, ) return (self.object.motion_id, )
@ -333,7 +342,7 @@ class VersionPermitView(SingleObjectMixin, QuestionMixin, RedirectView):
try: try:
self.version = self.object.versions.get(version_number=int(version_number)) self.version = self.object.versions.get(version_number=int(version_number))
except MotionVersion.DoesNotExist: except MotionVersion.DoesNotExist:
raise Http404('Version %s not found' % version_number) raise Http404('Version %s not found.' % version_number)
return super(VersionPermitView, self).get(*args, **kwargs) return super(VersionPermitView, self).get(*args, **kwargs)
def get_url_name_args(self): def get_url_name_args(self):

View File

@ -146,6 +146,12 @@ class ModelTest(TestCase):
motion.save(use_version=False) motion.save(use_version=False)
self.assertEqual(motion.versions.count(), 2) self.assertEqual(motion.versions.count(), 2)
def test_unicode_with_no_active_version(self):
motion = Motion.objects.create(title='foo', text='bar', identifier='')
motion.active_version = None
motion.save(update_fields=['active_version'])
self.assertEqual(str(motion), 'foo') # motion.__unicode__() raised an AttributeError
class ConfigTest(TestCase): class ConfigTest(TestCase):
def test_stop_submitting(self): def test_stop_submitting(self):

View File

@ -412,6 +412,7 @@ class TestVersionPermitView(MotionViewTestCase):
class TestVersionDeleteView(MotionViewTestCase): class TestVersionDeleteView(MotionViewTestCase):
def test_get(self): def test_get(self):
self.motion1.save(use_version=self.motion1.get_new_version(title='new', text='new'))
response = self.check_url('/motion/1/version/1/del/', self.admin_client, 302) response = self.check_url('/motion/1/version/1/del/', self.admin_client, 302)
self.assertRedirects(response, '/motion/1/version/1/') self.assertRedirects(response, '/motion/1/version/1/')
@ -424,3 +425,10 @@ class TestVersionDeleteView(MotionViewTestCase):
response = self.admin_client.post('/motion/1/version/2/del/', {'yes': 1}) response = self.admin_client.post('/motion/1/version/2/del/', {'yes': 1})
self.assertRedirects(response, '/motion/1/') self.assertRedirects(response, '/motion/1/')
self.assertEqual(self.motion1.versions.count(), 2) self.assertEqual(self.motion1.versions.count(), 2)
def test_delete_active_version(self):
self.motion1.save(use_version=self.motion1.get_new_version(title='new_title_yae6Aequaiw5saeb8suG', text='new'))
motion = Motion.objects.all()[0]
self.assertEqual(motion.get_active_version().title, 'new_title_yae6Aequaiw5saeb8suG')
response = self.admin_client.post('/motion/1/version/2/del/', {'yes': 1})
self.assertEqual(response.status_code, 404)