Merge branch 'stable/1.6.x'

Conflicts:
	CHANGELOG
	README.rst
	openslides/assignment/models.py
	openslides/users/forms.py
	requirements_production.txt
	tests/settings.py
This commit is contained in:
Oskar Hahn 2015-01-17 09:48:46 +01:00
commit 0a24b7267b
27 changed files with 20334 additions and 20034 deletions

View File

@ -24,7 +24,9 @@ Version 1.7 (unreleased)
Core: Core:
- New feature to tag motions, agenda and assignments. - New feature to tag motions, agenda and assignments.
Motion: - Fixed search index problem to index contents of many-to-many table
(e.g. tags of a motion).
Motions:
- New Feature to create amendments, which are related to a parent motion. - New Feature to create amendments, which are related to a parent motion.
- Added possibility to hide motions from non staff users in some states. - Added possibility to hide motions from non staff users in some states.
Other: Other:

View File

@ -105,7 +105,7 @@ portable version you should observe the following install steps.*
To install Virtual Python Environment builder, open command line (cmd) To install Virtual Python Environment builder, open command line (cmd)
and run:: and run::
> easy_install https://pypi.python.org/packages/source/v/virtualenv/virtualenv-1.11.6.tar.gz > easy_install https://pypi.python.org/packages/source/v/virtualenv/virtualenv-12.0.5.tar.gz
Create your OpenSlides directory, change to it, setup and activate the Create your OpenSlides directory, change to it, setup and activate the
virtual environment:: virtual environment::

View File

@ -5,34 +5,21 @@ How to create a new portable Windows distribution of OpenSlides:
Follow the instructions in the README, section III (Windows installation), step 1. Follow the instructions in the README, section III (Windows installation), step 1.
2. Install all required python packages (see requirements_production.txt): 2. Install all required python packages from requirements_production.txt:
(Note: You have to use 'easy_install -Z $PACKAGENAME')
easy_install -Z "django<1.7" ^ python install-requirements.py
backports.ssl_match_hostname ^
"beautifulsoup4<4.4" ^
"bleach<1.5" ^
"django-ckeditor-updated<4.3" ^
"django-haystack<2.2" ^
"django-mptt<0.7" ^
"jsonfield<0.10" ^
"natsort<3.3" ^
"reportlab<2.8" ^
"roman<2.1" ^
"sockjs_tornado<1.1" ^
"tornado<3.3" ^
"whoosh<2.6" ^
"setuptools<3.7"
3. Install pywin32 from binary installer: 3. Install pywin32 from binary installer:
http://sourceforge.net/projects/pywin32/files/pywin32/Build%20218/pywin32-218.win32-py2.7.exe/download http://sourceforge.net/projects/pywin32/files/pywin32/Build%20219/pywin32-219.win32-py2.7.exe/download
Pywin32 is used to update the version resource of the prebuild openslides.exe. Pywin32 is used to update the version resource of the prebuild openslides.exe.
It is not strictly required but at least for published releases it is highly advisable. It is not strictly required but at least for published releases it is highly advisable.
4. Install wxPython from binary installer: 4. Install wxPython from binary installer:
http://downloads.sourceforge.net/wxpython/wxPython2.8-win32-unicode-2.8.12.1-py27.exe http://sourceforge.net/projects/wxpython/files/wxPython/2.8.12.1/wxPython2.8-win32-unicode-2.8.12.1-py27.exe/download
WxPython is required to build the OpenSlides GUI frontend. WxPython is required to build the OpenSlides GUI frontend.

View File

@ -0,0 +1,12 @@
import os
f = open("../../requirements_production.txt")
reqs = f.read().split("\n")
for req in reqs:
# ignore comments or pip options
if req.startswith('--') or req.startswith('#'):
continue
# ignore blank lines
if len(req) > 0:
os.system('easy_install -Z -U "%s"' % req)

View File

@ -83,5 +83,8 @@
{% endwith %} {% endwith %}
<a href="{{ node|absolute_url }}">{% if node.type == node.ORGANIZATIONAL_ITEM %}<i>[{% endif %}{{ node }}{% if node.type == node.ORGANIZATIONAL_ITEM %}]</i>{% endif %}</a> <a href="{{ node|absolute_url }}">{% if node.type == node.ORGANIZATIONAL_ITEM %}<i>[{% endif %}{{ node }}{% if node.type == node.ORGANIZATIONAL_ITEM %}]</i>{% endif %}</a>
{{ node.get_title_supplement|safe }} {{ node.get_title_supplement|safe }}
{% for tag in node.tags.all %}
<span class="label">{{ tag }}</span>
{% endfor %}
</div> </div>
</div> </div>

View File

@ -41,7 +41,7 @@
{% endif %} {% endif %}
{% if perms.core.can_manage_tags %} {% if perms.core.can_manage_tags %}
<a href="{% url 'core_tag_list' %}" class="btn btn-mini" rel="tooltip" data-original-title="{% trans 'Manage tags' %}"> <a href="{% url 'core_tag_list' %}" class="btn btn-mini" rel="tooltip" data-original-title="{% trans 'Manage tags' %}">
<i class="icon-th"></i> <i class="icon-tags"></i>
<span class="optional-small"> {% trans 'Tags' %}</span> <span class="optional-small"> {% trans 'Tags' %}</span>
</a> </a>
{% endif %} {% endif %}

View File

@ -41,6 +41,13 @@
</div> </div>
</small> </small>
</h1> </h1>
<!-- Tags -->
{% for tag in item.tags.all %}
<span class="label">{{ tag }}</span>
{% endfor %}
<!-- Title -->
<p> <p>
{% if not item.content_object %} {% if not item.content_object %}
{{ item.text|safe }} {{ item.text|safe }}
@ -49,6 +56,7 @@
{% endif %} {% endif %}
</p> </p>
<!-- Comment -->
{% if perms.agenda.can_manage_agenda %} {% if perms.agenda.can_manage_agenda %}
{% if item.comment %} {% if item.comment %}
<h3>{% trans "Comment" %}</h3> <h3>{% trans "Comment" %}</h3>
@ -56,7 +64,7 @@
{% endif %} {% endif %}
{% endif %} {% endif %}
{# List of Speakers #} <!-- List of Speakers -->
<h3>{% trans "List of speakers" %} {% if item.speaker_list_closed %}<span class="label label-important">{% trans 'closed' %}</span>{% endif %}</h3> <h3>{% trans "List of speakers" %} {% if item.speaker_list_closed %}<span class="label label-important">{% trans 'closed' %}</span>{% endif %}</h3>
<p> <p>
{% if perms.agenda.can_manage_agenda %} {% if perms.agenda.can_manage_agenda %}

View File

@ -1,2 +1,3 @@
{{ object.title }} {{ object.title }}
{{ object.text }} {{ object.text }}
{{ object.tags.all }}

View File

@ -50,6 +50,11 @@
</small> </small>
</h1> </h1>
<!-- Tags -->
{% for tag in assignment.tags.all %}
<span class="optional label">{{ tag }}</span>
{% endfor %}
<div class="row-fluid"> <div class="row-fluid">
<div class="span9"> <div class="span9">
<!-- Description --> <!-- Description -->

View File

@ -23,7 +23,7 @@
{% endif %} {% endif %}
{% if perms.core.can_manage_tags %} {% if perms.core.can_manage_tags %}
<a href="{% url 'core_tag_list' %}" class="btn btn-mini" rel="tooltip" data-original-title="{% trans 'Manage tags' %}"> <a href="{% url 'core_tag_list' %}" class="btn btn-mini" rel="tooltip" data-original-title="{% trans 'Manage tags' %}">
<i class="icon-th"></i> <i class="icon-tags"></i>
<span class="optional-small"> {% trans 'Tags' %}</span> <span class="optional-small"> {% trans 'Tags' %}</span>
</a> </a>
{% endif %} {% endif %}
@ -46,7 +46,11 @@
</thead> </thead>
{% for object in object_list %} {% for object in object_list %}
<tr class="{% if object.is_active_slide %}activeline{% endif %}"> <tr class="{% if object.is_active_slide %}activeline{% endif %}">
<td><a href="{{ object|absolute_url:'detail' }}">{{ object }}</a></td> <td><a href="{{ object|absolute_url:'detail' }}">{{ object }}</a>
{% for tag in object.tags.all %}
<span class="optional label">{{ tag }}</span>
{% endfor %}
</td>
<td class="optional"> <td class="optional">
<!-- posts --> <!-- posts -->
{% trans "Posts" context "Number of searched candidates for an election" %}: {% trans "Posts" context "Number of searched candidates for an election" %}:

View File

@ -1,3 +1,4 @@
{{ object.name }} {{ object.name }}
{{ object.description }} {{ object.description }}
{{ object.candidates }} {{ object.candidates }}
{{ object.tags.all }}

View File

@ -1,8 +1,9 @@
from datetime import datetime from datetime import datetime
from django.conf import settings from django.conf import settings
from django.contrib.sessions.models import Session
from django.utils.html import urlize from django.utils.html import urlize
from django.utils.importlib import import_module
from sockjs.tornado import SockJSConnection from sockjs.tornado import SockJSConnection
@ -18,22 +19,25 @@ class ChatboxSocketHandler(SockJSConnection):
""" """
from openslides.users.models import User from openslides.users.models import User
# TODO: Use the django way to get the session to be compatible with # get the session (compatible with other auth-backends)
# other auth-backends; see comment in pull request #1220: engine = import_module(settings.SESSION_ENGINE)
# https://github.com/OpenSlides/OpenSlides/pull/1220#discussion_r11565705
session_key = info.get_cookie(settings.SESSION_COOKIE_NAME).value
session = Session.objects.get(session_key=session_key)
try: try:
self.user = User.objects.get(pk=session.get_decoded().get('_auth_user_id')) session_key = info.get_cookie(settings.SESSION_COOKIE_NAME).value
session = engine.SessionStore(session_key)
pk = session.get_decoded().get('_auth_user_id')
except AttributeError:
return False
try:
self.user = User.objects.get(pk)
except User.DoesNotExist: except User.DoesNotExist:
return_value = False return False
if self.user.has_perm('core.can_use_chat'):
self.clients.add(self)
return True
else: else:
if self.user.has_perm('core.can_use_chat'): return False
self.clients.add(self)
return_value = True
else:
return_value = False
return return_value
def on_message(self, message): def on_message(self, message):
""" """

View File

@ -113,7 +113,7 @@
<hr /> <hr />
<footer> <footer>
<small> <small>
&copy; Copyright 20112014 | Powered by <a href="http://openslides.org" target="_blank">OpenSlides</a> | <a href="{% url 'core_version' %}">Version</a> &copy; Copyright 20112015 | Powered by <a href="http://openslides.org" target="_blank">OpenSlides</a> | <a href="{% url 'core_version' %}">Version</a>
</small> </small>
</footer> </footer>
</div><!--/#content--> </div><!--/#content-->
@ -134,7 +134,7 @@
<script type="text/javascript" src="{% static 'js/jquery/jquery.bsmselect.js' %}"></script> <script type="text/javascript" src="{% static 'js/jquery/jquery.bsmselect.js' %}"></script>
<script type="text/javascript"> <script type="text/javascript">
// use jquery-bsmselect for all <select multiple> form elements // use jquery-bsmselect for all <select multiple> form elements
$("select[multiple]").bsmSelect({ $("select[multiple]:not(.dont_use_bsmselect)").bsmSelect({
removeLabel: '<sup><b>X</b></sup>', removeLabel: '<sup><b>X</b></sup>',
containerClass: 'bsmContainer', containerClass: 'bsmContainer',
listClass: 'bsmList-custom', listClass: 'bsmList-custom',

View File

@ -5,17 +5,23 @@
{% block title %}{% trans "Tags" %} {{ block.super }}{% endblock %} {% block title %}{% trans "Tags" %} {{ block.super }}{% endblock %}
{% block content %} {% block content %}
<h1>{% trans "Tags" %}</h1> <h1>{% trans "Tags" %}
<small class="pull-right">
<a href="javascript:window.history.back()" class="btn btn-mini">
<i class="icon-chevron-left"></i><span class="optional-small"> {% trans "Back to overview" %}</span>
</a>
</small>
</h1>
<div class="control-group"> <div class="control-group">
<label for="tag-edit">Name:</label> <label for="tag-edit">{% trans 'Enter new tag name' %}:</label>
<input id="tag-edit" name="new"> <input id="tag-edit" name="new">
<a href="#" id="tag-save" class="btn btn-primary">{% trans 'Save' %}</a> <a href="#" id="tag-save" class="btn btn-primary">{% trans 'Save' %}</a>
</div> </div>
<table id="tag-table" class="table table-striped table-bordered"> <table id="tag-table" class="table table-striped table-bordered">
<tr> <tr>
<th>{% trans "Tag name" %}</th> <th>{% trans "Tag" %}</th>
<th class="mini_width">{% trans "Actions" %}</th> <th class="mini_width">{% trans "Actions" %}</th>
</tr> </tr>
<tr id="dummy-tag" class="tag-row" style="display:none"> <tr id="dummy-tag" class="tag-row" style="display:none">
@ -47,6 +53,7 @@
</tr> </tr>
{% endfor %} {% endfor %}
</table> </table>
<p>{% trans "You can use these tags for agenda items, motions and elections." %}</p>
{% endblock %} {% endblock %}
{% block javascript %} {% block javascript %}

View File

@ -131,7 +131,7 @@ HAYSTACK_CONNECTIONS = {
} }
# Haystack updates search index after each save/delete action by apps # Haystack updates search index after each save/delete action by apps
HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor' HAYSTACK_SIGNAL_PROCESSOR = 'openslides.utils.haystack_processor.OpenSlidesProcessor'
# Adds all automaticly collected plugins # Adds all automaticly collected plugins
INSTALLED_PLUGINS = collect_plugins() INSTALLED_PLUGINS = collect_plugins()

File diff suppressed because it is too large Load Diff

View File

@ -5,7 +5,7 @@
msgid "" msgid ""
msgstr "" msgstr ""
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2014-10-16 23:27+0200\n" "POT-Creation-Date: 2015-01-11 19:03+0100\n"
"Language: en\n" "Language: en\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -72,7 +72,7 @@ def setup_motion_config(sender, **kwargs):
motion_amendments_prefix = ConfigVariable( motion_amendments_prefix = ConfigVariable(
name='motion_amendments_prefix', name='motion_amendments_prefix',
default_value=pgettext('Prefix for amendment', 'A'), default_value=pgettext('Prefix for the identifier for amendments', 'A'),
form_field=forms.CharField( form_field=forms.CharField(
required=False, required=False,
label=ugettext_lazy('Prefix for the identifier for amendments'))) label=ugettext_lazy('Prefix for the identifier for amendments')))

View File

@ -30,7 +30,7 @@
{% endif %} {% endif %}
{% endif %} {% endif %}
{% if motion.is_amendment %} {% if motion.is_amendment %}
(Amendment of <a href="{{ motion.parent|absolute_url }}">{{ motion.parent.identifier|default:motion.parent }}</a>) ({% trans "Amendment of" %} <a href="{{ motion.parent|absolute_url }}">{{ motion.parent.identifier|default:motion.parent }}</a>)
{% endif %} {% endif %}
</small> </small>
<small class="pull-right"> <small class="pull-right">
@ -71,8 +71,6 @@
</small> </small>
</h1> </h1>
{{ motion.tags.all|join:', ' }}
<div class="row-fluid"> <div class="row-fluid">
<div class="span8"> <div class="span8">
{# TODO: show only for workflow with versioning #} {# TODO: show only for workflow with versioning #}
@ -256,7 +254,9 @@
{% block meta_box_poll_extras %}{% endblock %} {% block meta_box_poll_extras %}{% endblock %}
</p> </p>
{% empty %} {% empty %}
{% if not allowed_actions.create_poll %}
{% endif %}
{% endfor %} {% endfor %}
{% if allowed_actions.create_poll %} {% if allowed_actions.create_poll %}
<p> <p>
@ -268,13 +268,19 @@
{% endwith %} {% endwith %}
<!-- Category --> <!-- Category -->
<h5>{% trans "Category" %}:</h5>
{% if motion.category %} {% if motion.category %}
<h5>{% trans "Category" %}:</h5>
{{ motion.category }} {{ motion.category }}
{% else %}
{% endif %} {% endif %}
<!-- Tags -->
{% for tag in motion.tags.all %}
{% if forloop.first %}
<h5>{% trans "Tags" %}:</h5>
{% endif %}
<span class="optional label">{{ tag }}</span>
{% endfor %}
<!-- Creation Time --> <!-- Creation Time -->
<h5> <h5>
{% if motion.versions.count > 1 %} {% if motion.versions.count > 1 %}

View File

@ -38,7 +38,7 @@
{% endif %} {% endif %}
{% if perms.core.can_manage_tags %} {% if perms.core.can_manage_tags %}
<a href="{% url 'core_tag_list' %}" class="btn btn-mini" rel="tooltip" data-original-title="{% trans 'Manage tags' %}"> <a href="{% url 'core_tag_list' %}" class="btn btn-mini" rel="tooltip" data-original-title="{% trans 'Manage tags' %}">
<i class="icon-th"></i> <i class="icon-tags"></i>
<span class="optional-small"> {% trans 'Tags' %}</span> <span class="optional-small"> {% trans 'Tags' %}</span>
</a> </a>
{% endif %} {% endif %}
@ -72,15 +72,19 @@
</thead> </thead>
{% for motion in motion_list %} {% for motion in motion_list %}
<tr class="{% if motion.is_active_slide %}activeline{% endif %}"> <tr class="{% if motion.is_active_slide %}activeline{% endif %}">
<td class="nobr">{{ motion.identifier|default:'' }}</td> <td class="nobr">{{ motion.identifier|default:'' }}
<td>
<a href="{{ motion|absolute_url }}">{{ motion.title }}</a>
{% if motion.is_amendment %} {% if motion.is_amendment %}
<a class="label label-success" data-original-title="Amendment" rel="tooltip"> <a class="badge badge-success" data-original-title="{% trans 'Amendment' %}" rel="tooltip">
{{ 'motion_amendments_prefix'|get_config }} {{ 'motion_amendments_prefix'|get_config }}
</a> </a>
{% endif %} {% endif %}
</td> </td>
<td>
<a href="{{ motion|absolute_url }}">{{ motion.title }}</a>
{% for tag in motion.tags.all %}
<span class="optional label">{{ tag }}</span>
{% endfor %}
</td>
<td class="optional">{% if motion.category %}{{ motion.category }}{% else %}{% endif %}</td> <td class="optional">{% if motion.category %}{{ motion.category }}{% else %}{% endif %}</td>
<td class="optional-small"><span class="label label-info">{% trans motion.state.name %}</span></td> <td class="optional-small"><span class="label label-info">{% trans motion.state.name %}</span></td>
<td class="optional"> <td class="optional">

View File

@ -78,7 +78,7 @@
<small> <small>
{% trans "Motion" %} {{ motion.identifier|default:'' }} {% trans "Motion" %} {{ motion.identifier|default:'' }}
{% if motion.is_amendment %} {% if motion.is_amendment %}
(Amendment of {{ motion.parent.identifier|default:motion.parent }}) ({% trans "Amendment of" %} {{ motion.parent.identifier|default:motion.parent }})
{% endif %} {% endif %}
{% if motion.get_active_version.version_number > 1 %} | {% trans 'Version' %} {{ motion.active_version.version_number }}{% endif %} {% if motion.get_active_version.version_number > 1 %} | {% trans 'Version' %} {{ motion.active_version.version_number }}{% endif %}
</small> </small>

View File

@ -5,3 +5,4 @@
{{ object.submitters }} {{ object.submitters }}
{{ object.supporters }} {{ object.supporters }}
{{ object.category }} {{ object.category }}
{{ object.tags.all }}

View File

@ -59,11 +59,16 @@ class SlideMixin(object):
def delete(self, *args, **kwargs): def delete(self, *args, **kwargs):
""" """
Updates the projector, if the object is on the projector and is deleted. Updates the projector if the object is on the projector and is deleted.
""" """
from openslides.projector.api import update_projector from openslides.projector.api import update_projector
# Checking active slide has to be done before calling super().delete()
# because super().delete() deletes the object and than we have no
# access to the former existing primary key any more. But updating
# projector has to be done after deleting the object of course.
update_required = self.is_active_slide()
value = super(SlideMixin, self).delete(*args, **kwargs) value = super(SlideMixin, self).delete(*args, **kwargs)
if self.is_active_slide(): if update_required:
update_projector() update_projector()
return value return value

View File

@ -75,10 +75,11 @@ class UserUpdateForm(UserCreateForm):
class GroupForm(CssClassMixin, forms.ModelForm): class GroupForm(CssClassMixin, forms.ModelForm):
permissions = LocalizedModelMultipleChoiceField( permissions = LocalizedModelMultipleChoiceField(
queryset=Permission.objects.all(), label=ugettext_lazy('Permissions'), queryset=Permission.objects.all(), label=ugettext_lazy('Permissions'), required=False,
required=False) widget=forms.SelectMultiple(attrs={'class': 'dont_use_bsmselect'}))
users = forms.ModelMultipleChoiceField( users = forms.ModelMultipleChoiceField(
queryset=User.objects.all(), label=ugettext_lazy('Users'), required=False) queryset=User.objects.all(), label=ugettext_lazy('Participants'), required=False,
widget=forms.SelectMultiple(attrs={'class': 'dont_use_bsmselect'}))
class Meta: class Meta:
model = Group model = Group

View File

@ -0,0 +1,23 @@
from django.db import models
from haystack.signals import RealtimeSignalProcessor
class OpenSlidesProcessor(RealtimeSignalProcessor):
def setup(self):
# Naive (listen to all model saves).
super(OpenSlidesProcessor, self).setup()
models.signals.m2m_changed.connect(self.handle_many_to_many)
def teardown(self):
# Naive (listen to all model saves).
super(OpenSlidesProcessor, self).teardown()
models.signals.m2m_changed.disconnect(self.handle_many_to_many)
def handle_many_to_many(self, sender, instance, **kwargs):
"""
Given an individual model instance, determine which backends the
update should be sent to & update the object on those backends.
"""
model_class = type(instance)
if kwargs['action'] == 'post_add' or kwargs['action'] == 'post_clear' or kwargs['action'] == 'post_remove':
self.handle_save(model_class, instance, **kwargs)