Insert new app to upload files via the frontend. Let tornado server media files. Insert icon-mediafile css class. Insert extra_stylfiles context variable.

This commit is contained in:
Norman Jäckel 2013-02-16 16:19:20 +01:00
parent 571649d5a6
commit ad0e157bd1
22 changed files with 651 additions and 57 deletions

9
.gitignore vendored
View File

@ -1,7 +1,12 @@
.venv/*
# General
*.pyc
*.swp
*~
# Virtual Environment
.venv/*
# Development settings and database
settings.py
database.sqlite
!tests/settings.py
@ -14,6 +19,6 @@ dist/*
.DS_Store
versiontools*
# Unit test / coverage reports
# Unit test and coverage reports
.coverage
htmlcov

View File

@ -1,12 +1,12 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
openslides.openslides_settings
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
openslides.global_settings
~~~~~~~~~~~~~~~~~~~~~~~~~~
OpenSlides default settings.
:copyright: 2011, 2012 by OpenSlides team, see AUTHORS.
:copyright: 20112013 by OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details.
"""
@ -15,6 +15,7 @@ import sys
from openslides.main import fs2unicode
SITE_ROOT = os.path.realpath(os.path.dirname(__file__))
AUTHENTICATION_BACKENDS = (
@ -34,7 +35,6 @@ LANGUAGES = (
('fr', ugettext('French')),
)
# If you set this to False, Django will make some optimizations so as not
# to load the internationalization machinery.
USE_I18N = True
@ -47,18 +47,14 @@ LOCALE_PATHS = (
fs2unicode(os.path.join(SITE_ROOT, 'locale')),
)
# Absolute path to the directory that holds media.
# Example: "/home/media/media.lawrence.com/"
MEDIA_ROOT = fs2unicode(os.path.join(SITE_ROOT, './static/'))
# URL that handles the media served from MEDIA_ROOT. Make sure to use a
# trailing slash if there is a path component (optional in other cases).
# Examples: "http://media.lawrence.com", "http://example.com/media/"
MEDIA_URL = ''
MEDIA_URL = '/media/'
# Absolute path to the directory that holds static media from ``collectstatic``
# Example: "/home/media/static.lawrence.com/"
STATIC_ROOT = fs2unicode(os.path.join(SITE_ROOT, '../site-static'))
STATIC_ROOT = fs2unicode(os.path.join(SITE_ROOT, '../collected-site-static'))
# URL that handles the media served from STATIC_ROOT. Make sure to use a
# trailing slash if there is a path component (optional in other cases).
@ -119,6 +115,7 @@ INSTALLED_APPS = (
'openslides.motion',
'openslides.assignment',
'openslides.participant',
'openslides.mediafile',
'openslides.config',
)

View File

@ -6,7 +6,7 @@
Main script to start and set up OpenSlides.
:copyright: 2011, 2012 by OpenSlides team, see AUTHORS.
:copyright: 20112013 by OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details.
"""
@ -64,6 +64,11 @@ INSTALLED_PLUGINS = (
)
INSTALLED_APPS += INSTALLED_PLUGINS
# Absolute path to the directory that holds media.
# Example: "/home/media/media.lawrence.com/"
MEDIA_ROOT = %(media_root_path)s
"""
KEY_LENGTH = 30
@ -195,14 +200,17 @@ def create_settings(settings_path, database_path=None):
if database_path is _portable_db_path:
database_path = get_portable_db_path()
dbpath_value = 'openslides.main.get_portable_db_path()'
media_root_path_value = 'openslides.main.get_portable_media_root_path()'
else:
if database_path is None:
database_path = get_user_data_path('openslides', 'database.sqlite')
dbpath_value = repr(fs2unicode(database_path))
media_root_path_value = repr(fs2unicode(get_user_data_path('openslides', 'media', '')))
settings_content = CONFIG_TEMPLATE % dict(
default_key=base64.b64encode(os.urandom(KEY_LENGTH)),
dbpath=dbpath_value)
dbpath=dbpath_value,
media_root_path=media_root_path_value)
if not os.path.exists(settings_module):
os.makedirs(settings_module)
@ -370,6 +378,10 @@ def get_portable_db_path():
return get_portable_path('openslides', 'database.sqlite')
def get_portable_media_root_path():
return get_portable_path('openslides', 'media', '')
def win32_get_app_data_path(*args):
shell32 = ctypes.WinDLL("shell32.dll")
SHGetFolderPath = shell32.SHGetFolderPathW

View File

View File

@ -0,0 +1,50 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
openslides.mediafile.forms
~~~~~~~~~~~~~~~~~~~~~~~~~~
Forms for the mediafile app.
:copyright: 20112013 by OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details.
"""
from django.forms import ModelForm
from openslides.utils.forms import CssClassMixin
from .models import Mediafile
class MediafileNormalUserCreateForm(CssClassMixin, ModelForm):
"""Form to create a media file.
This form is only used by normal users, not by managers.
"""
class Meta:
model = Mediafile
exclude = ('uploader',)
class MediafileUpdateForm(CssClassMixin, ModelForm):
"""Form to edit mediafile entries.
This form is only for managers to update the mediafile entry.
"""
class Meta:
model = Mediafile
def save(self, *args, **kwargs):
"""Method to save the form.
Here the overwrite is to delete old files.
"""
if not self.instance.pk is None:
old_file = Mediafile.objects.get(pk=self.instance.pk).mediafile
if not old_file == self.instance.mediafile:
old_file.delete()
return super(MediafileUpdateForm, self).save(*args, **kwargs)

View File

@ -0,0 +1,85 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
openslides.mediafile.models
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Models for the mediafile app.
:copyright: 20112013 by OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details.
"""
import mimetypes
from django.db import models
from django.utils.translation import ugettext_noop
from openslides.utils.person.models import PersonField
class Mediafile(models.Model):
"""The Mediafile class
Class for uploaded files which can be delivered under a certain url.
"""
mediafile = models.FileField(upload_to='file')
"""A FileField
See https://docs.djangoproject.com/en/dev/ref/models/fields/#filefield
for more information.
"""
title = models.CharField(max_length=255, unique=True)
"""A string representing the title of the file."""
uploader = PersonField(blank=True)
"""A person the uploader of a file."""
timestamp = models.DateTimeField(auto_now_add=True)
"""A DateTimeField to save the upload date and time."""
filetype = models.CharField(max_length=255, editable=False)
"""A string used to show the type of the file."""
class Meta:
"""Meta class for the mediafile model."""
ordering = ['title']
permissions = (
('can_see', ugettext_noop('Can see the list of files')),
('can_upload', ugettext_noop('Can upload files')),
('can_manage', ugettext_noop('Can manage files')),)
def __unicode__(self):
"""Method for representation."""
return self.title
def save(self, *args, **kwargs):
"""Method to read filetype and then save to the database."""
if self.mediafile:
self.filetype = mimetypes.guess_type(self.mediafile.path)[0] or ugettext_noop('unknown')
else:
self.filetype = ugettext_noop('unknown')
return super(Mediafile, self).save(*args, **kwargs)
@models.permalink
def get_absolute_url(self, link='update'):
"""Returns the URL to a mediafile. The link can be 'update' or 'delete'."""
if link == 'update' or link == 'edit': # 'edit' ist only used until utils/views.py is fixed
return ('mediafile_update', [str(self.id)])
if link == 'delete':
return ('mediafile_delete', [str(self.id)])
def get_filesize(self):
"""Transforms Bytes to Kilobytes or Megabytes. Returns the size as string."""
size = self.mediafile.size
if size < 1024:
return '< 1 kB'
if size >= 1024 * 1024:
mB = size / 1024 / 1024
return '%d MB' % mB
else:
kB = size / 1024
return '%d kB' % kB
# TODO: Read http://stackoverflow.com/a/1094933 and think about it.

View File

@ -0,0 +1,12 @@
/*
* OpenSlides mediafile style
*
* :copyright: 20112013 by OpenSlides team, see AUTHORS.
* :license: GNU GPL, see LICENSE for more details.
*/
/** Navigation icons (mapping to glyphicons-halflings) **/
.icon-mediafile {
background-position: -96px -24px;
}

View File

@ -0,0 +1,37 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}
{{ block.super }}
{% if mediafile %}
{% trans "Edit media" %}
{% else %}
{% trans "New media" %}
{% endif %}
{% endblock %}
{% block content %}
<h1>
{% if mediafile %}
{% trans "Edit media" %}
{% else %}
{% trans "New media" %}
{% endif %}
<small class="pull-right">
<a href="{% url 'mediafile_list' %}" class="btn btn-mini"><i class="icon-chevron-left"></i> {% trans "Back to overview" %}</a>
</small>
</h1>
<form action="" method="post" enctype="multipart/form-data">{% csrf_token %}
{% include "form.html" %}
<p>
{% if perms.mediafile.can_manage %}
{% include "formbuttons_saveapply.html" %}
{% else %}
{% include "formbuttons_save.html" %}
{% endif %}
<a href="{% url 'mediafile_list' %}" class="btn">{% trans 'Cancel' %}</a>
</p>
<small>* {% trans "required" %}</small>
</form>
{% endblock %}

View File

@ -0,0 +1,49 @@
{% extends 'base.html' %}
{% load i18n %}
{% load tags %}
{% block title %}{{ block.super }} {% trans 'Media' %}{% endblock %}
{% block content %}
<h1>{% trans 'Media' %}
<small class="pull-right">
{% if perms.mediafile.can_upload %}
<a href="{% url 'mediafile_create' %}" class="btn btn-mini" rel="tooltip" data-original-title="{% trans 'New media' %}"><i class="icon-plus"></i> {% trans "New" %}</a>
{% endif %}
</small>
</h1>
<table class="table table-striped table-bordered">
<tr>
<th>{% trans 'Title' %}</th>
<th>{% trans 'Type' %}</th>
<th>{% trans 'Size' %}</th>
<th>{% trans 'Upload time' %}</th>
<th>{% trans 'Uploader' %}</th>
{% if perms.mediafile.can_manage %}
<th class="mini_width">{% trans "Actions" %}</th>
{% endif %}
</tr>
{% for mediafile in mediafile_list %}
<tr>
<td><a href="{{ mediafile.mediafile.url }}">{{ mediafile }}</a></td>
<td>{{ mediafile.filetype }}</td>
<td>{{ mediafile.get_filesize }}</td>
<td>{{ mediafile.timestamp }}</td>
<td>{{ mediafile.uploader }}</td>
{% if perms.mediafile.can_manage %}
<td>
<span style="width: 1px; white-space: nowrap;">
<a href="{% model_url mediafile 'update' %}" title="{% trans 'Edit' %}" class="btn btn-mini"><i class="icon-pencil"></i></a>
<a href="{% model_url mediafile 'delete' %}" title="{% trans 'Delete' %}" class="btn btn-mini"><i class="icon-remove"></i></a>
</span>
</td>
{% endif %}
</tr>
{% empty %}
<tr>
<td colspan="6"><i>{% trans 'No media available.' %}</i></td>
</tr>
{% endfor %}
</table>
{% endblock %}

View File

@ -0,0 +1,40 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
openslides.mediafile.urls
~~~~~~~~~~~~~~~~~~~~~~~~~~~
URL patterns for the mediafile app.
:copyright: 20112013 by OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details.
"""
from django.conf.urls import url, patterns
from .models import Mediafile
from .views import (MediafileListView, MediafileCreateView,
MediafileUpdateView, MediafileDeleteView)
urlpatterns = patterns('',
url(r'^$',
MediafileListView.as_view(),
name='mediafile_list',
),
url(r'^new/$',
MediafileCreateView.as_view(),
name='mediafile_create',
),
url(r'^(?P<pk>\d+)/edit/$',
MediafileUpdateView.as_view(),
name='mediafile_update',
),
url(r'^(?P<pk>\d+)/del/$',
MediafileDeleteView.as_view(),
name='mediafile_delete',
),
)

View File

@ -0,0 +1,96 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
openslides.mediafile.views
~~~~~~~~~~~~~~~~~~~~~~~~~~
Views for the mediafile app.
:copyright: 20112013 by OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details.
"""
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext as _
from openslides.utils.template import Tab
from openslides.utils.views import ListView, CreateView, UpdateView, DeleteView
from .models import Mediafile
from .forms import MediafileNormalUserCreateForm, MediafileUpdateForm
class MediafileListView(ListView):
"""View to see a table of all uploaded files."""
model = Mediafile
def has_permission(self, request, *args, **kwargs):
return (request.user.has_perm('mediafile.can_see') or
request.user.has_perm('mediafile.can_upload') or
request.user.has_perm('mediafile.can_manage'))
class MediafileCreateView(CreateView):
"""View to upload a new file
A manager can also set the uploader, else the request user is set as uploader.
"""
model = Mediafile
permission_required = 'mediafile.can_upload'
success_url_name = 'mediafile_list'
def get_form(self, form_class):
form_kwargs = self.get_form_kwargs()
if self.request.method == 'GET':
form_kwargs['initial'].update({'uploader': self.request.user.person_id}) # TODO: Check this.
if not self.request.user.has_perm('mediafile.can_manage'):
# Return our own ModelForm
return MediafileNormalUserCreateForm(**form_kwargs)
else:
# Return a ModelForm created by Django.
return form_class(**form_kwargs)
def manipulate_object(self, *args, **kwargs):
if not self.request.user.has_perm('mediafile.can_manage'):
self.object.uploader = self.request.user
return super(MediafileCreateView, self).manipulate_object(*args, **kwargs)
class MediafileUpdateView(UpdateView):
"""View to edit the entry of an uploaded file."""
model = Mediafile
permission_required = 'mediafile.can_manage'
form_class = MediafileUpdateForm
success_url_name = 'mediafile_list'
def get_form_kwargs(self, *args, **kwargs):
form_kwargs = super(MediafileUpdateView, self).get_form_kwargs(*args, **kwargs)
form_kwargs['initial'].update({'uploader': self.object.uploader.person_id})
return form_kwargs
class MediafileDeleteView(DeleteView):
"""View to delete the entry of an uploaded file and the file itself."""
model = Mediafile
permission_required = 'mediafile.can_manage'
success_url_name = 'mediafile_list'
def case_yes(self, *args, **kwargs):
"""Deletes the file in the filesystem, if user clicks "Yes"."""
self.object.mediafile.delete()
return super(MediafileDeleteView, self).case_yes(*args, **kwargs)
def register_tab(request):
"""Inserts a new Tab to the views for files."""
selected = request.path.startswith('/mediafile/')
return Tab(
title=_('Media'),
app='mediafile', # TODO: Rename this to icon='mediafile' later
stylefile='styles/mediafile.css',
url=reverse('mediafile_list'),
permission=(request.user.has_perm('mediafile.can_see') or
request.user.has_perm('mediafile.can_upload') or
request.user.has_perm('mediafile.can_manage')),
selected=selected)

View File

@ -6,14 +6,13 @@
Useful functions for the participant app.
:copyright: 2011, 2012 by OpenSlides team, see AUTHORS.
:copyright: 20112013 by OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details.
"""
from random import choice
import csv
from django.contrib.auth.models import Permission
from django.db import transaction
from django.utils.translation import ugettext as _
@ -22,11 +21,6 @@ from openslides.utils import csv_ext
from openslides.participant.models import User, Group
DEFAULT_PERMS = ['can_see_agenda', 'can_see_projector',
'can_see_motion', 'can_see_assignment',
'can_see_dashboard']
def gen_password():
"""
generates a random passwort.

View File

@ -26,7 +26,7 @@ from openslides.projector.api import register_slidemodel
from openslides.projector.projector import SlideMixin
class User(DjangoUser, PersonMixin, Person, SlideMixin):
class User(PersonMixin, DjangoUser, Person, SlideMixin):
prefix = 'user' # This is for the slides
person_prefix = 'user'
GENDER_CHOICES = (

View File

@ -42,19 +42,23 @@ def create_builtin_groups(sender, **kwargs):
ct_participant = ContentType.objects.get(app_label='participant', model='user')
perm_6 = Permission.objects.get(content_type=ct_participant, codename='can_see_participant')
ct_mediafile = ContentType.objects.get(app_label='mediafile', model='mediafile')
perm_6a = Permission.objects.get(content_type=ct_mediafile, codename='can_see')
group_anonymous = Group.objects.create(name=ugettext_noop('Anonymous'))
group_anonymous.permissions.add(perm_1, perm_2, perm_3, perm_4, perm_5, perm_6)
group_anonymous.permissions.add(perm_1, perm_2, perm_3, perm_4, perm_5, perm_6, perm_6a)
group_registered = Group.objects.create(name=ugettext_noop('Registered'))
group_registered.permissions.add(perm_1, perm_2, perm_3, perm_4, perm_5, perm_6)
group_registered.permissions.add(perm_1, perm_2, perm_3, perm_4, perm_5, perm_6, perm_6a)
# Delegates
perm_7 = Permission.objects.get(content_type=ct_motion, codename='can_create_motion')
perm_8 = Permission.objects.get(content_type=ct_motion, codename='can_support_motion')
perm_9 = Permission.objects.get(content_type=ct_assignment, codename='can_nominate_other')
perm_10 = Permission.objects.get(content_type=ct_assignment, codename='can_nominate_self')
perm_10a = Permission.objects.get(content_type=ct_mediafile, codename='can_upload')
group_delegates = Group.objects.create(name=ugettext_noop('Delegates'))
group_delegates.permissions.add(perm_7, perm_8, perm_9, perm_10)
group_delegates.permissions.add(perm_7, perm_8, perm_9, perm_10, perm_10a)
# Staff
perm_11 = Permission.objects.get(content_type=ct_agenda, codename='can_manage_agenda')
@ -62,9 +66,10 @@ def create_builtin_groups(sender, **kwargs):
perm_13 = Permission.objects.get(content_type=ct_assignment, codename='can_manage_assignment')
perm_14 = Permission.objects.get(content_type=ct_participant, codename='can_manage_participant')
perm_15 = Permission.objects.get(content_type=ct_projector, codename='can_manage_projector')
perm_15a = Permission.objects.get(content_type=ct_mediafile, codename='can_manage')
ct_config = ContentType.objects.get(app_label='config', model='configstore')
perm_16 = Permission.objects.get(content_type=ct_config, codename='can_manage_config')
group_staff = Group.objects.create(name=ugettext_noop('Staff'))
group_staff.permissions.add(perm_7, perm_9, perm_10, perm_11, perm_12, perm_13, perm_14, perm_15, perm_16)
group_staff.permissions.add(perm_7, perm_9, perm_10, perm_11, perm_12, perm_13, perm_14, perm_15, perm_15a, perm_16)

View File

@ -12,13 +12,14 @@
<title>{% block title %}{% get_config 'event_name' %}{% endblock %}</title>
<!-- styles -->
<link href="{% static 'styles/bootstrap.min.css' %}" type="text/css" rel="stylesheet">
<link href="{% static 'styles/bootstrap-responsive.min.css' %}" type="text/css" rel="stylesheet">
<link href="{% static 'styles/base.css' %}" type="text/css" rel="stylesheet">
<link href="{% static 'styles/bootstrap.min.css' %}" type="text/css" rel="stylesheet" />
<link href="{% static 'styles/bootstrap-responsive.min.css' %}" type="text/css" rel="stylesheet" />
<link href="{% static 'styles/base.css' %}" type="text/css" rel="stylesheet" />
<link href="{% static 'img/favicon.png' %}" type="image/png" rel="shortcut icon" />
{% block header %}
{% endblock %}
{% for stylefile in extra_stylefiles %}
<link href="{% static stylefile %}" type="text/css" rel="stylesheet" />
{% endfor %}
{% block header %}{% endblock %}
</head>
<body>
<!-- Navbar -->
@ -26,7 +27,7 @@
<div class="row-fluid">
<div class="span12">
<div id="header">
<a href="/" class="logo" title="{% trans 'Home' %}"><img src="{% static 'img/logo.png' %}"></a>
<a href="/" class="logo" title="{% trans 'Home' %}"><img src="{% static 'img/logo.png' %}" /></a>
<!--<a class="title" href="#">{% get_config 'event_name' %}</a>-->
{% block loginbutton %}
<div class="btn-group pull-right">
@ -65,7 +66,7 @@
{% if tab.permission %}
<li{% if tab.selected %} class="active"{% endif %}>
<a href="{{ tab.url }}">
<span class="ico"><i class="icon-{{tab.app}}"></i></span>
<span class="ico"><i class="icon-{{ tab.app }}"></i></span>
<span class="text">{{ tab.title }}</span>
</a>
</li>
@ -94,7 +95,7 @@
{% endblock %}
</div>
</div>
<hr>
<hr />
<footer>
<small>
&copy; Copyright 2011-2013 | Powered by <a href="http://openslides.org" target="_blank">OpenSlides</a>
@ -111,7 +112,6 @@
<script src="{% static 'javascript/bootstrap.min.js' %}" type="text/javascript"></script>
<script src="{% static 'javascript/utils.js' %}" type="text/javascript"></script>
<script src="{% url 'django.views.i18n.javascript_catalog' %}" type="text/javascript"></script>
{% block javascript %}
{% endblock %}
{% block javascript %}{% endblock %}
</body>
</html>

View File

@ -6,7 +6,7 @@
Global URL list for OpenSlides.
:copyright: 2011, 2012 by OpenSlides team, see AUTHORS.
:copyright: 20112013 by OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details.
"""
@ -16,6 +16,7 @@ from django.utils.importlib import import_module
from openslides.utils.views import RedirectView
handler500 = 'openslides.utils.views.server_error'
urlpatterns = patterns('',
@ -26,18 +27,13 @@ urlpatterns = patterns('',
(r'^motion/', include('openslides.motion.urls')),
(r'^assignment/', include('openslides.assignment.urls')),
(r'^participant/', include('openslides.participant.urls')),
(r'^mediafile/', include('openslides.mediafile.urls')),
(r'^config/', include('openslides.config.urls')),
(r'^projector/', include('openslides.projector.urls')),
(r'^i18n/', include('django.conf.urls.i18n')),
)
urlpatterns += patterns('django.contrib.staticfiles.views',
url(r'^static/(?P<path>.*)$', 'serve', {'insecure': True}),
)
js_info_dict = {
'packages': [],
}
js_info_dict = {'packages': [],}
for plugin in settings.INSTALLED_PLUGINS:
try:
@ -50,7 +46,6 @@ for plugin in settings.INSTALLED_PLUGINS:
% plugin_name)))
js_info_dict['packages'].append(plugin_name)
urlpatterns += patterns('',
(r'^jsi18n/$', 'django.views.i18n.javascript_catalog', js_info_dict),

View File

@ -21,7 +21,7 @@ class PersonChoices(object):
def __iter__(self):
if self.field.empty_label is not None:
yield (u"", self.field.empty_label)
yield (u'', self.field.empty_label)
for person in sorted(Persons(), key=lambda person: person.sort_name):
yield (person.person_id, person)
@ -51,6 +51,8 @@ class PersonFormField(forms.fields.ChoiceField):
choices = property(_get_choices, forms.fields.ChoiceField._set_choices)
def to_python(self, value):
if value == u'':
return u''
return get_person(value)
def valid_value(self, value):

View File

@ -1,12 +1,12 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
openslides.template
~~~~~~~~~~~~~~~~~~~
openslides.utils.template
~~~~~~~~~~~~~~~~~~~~~~~~~
Useful template functions for OpenSlides.
:copyright: 2011, 2012 by OpenSlides team, see AUTHORS.
:copyright: 20112013 by OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details.
"""
@ -15,13 +15,13 @@ from django.template.loader_tags import BlockNode, ExtendsNode
class Tab(object):
def __init__(self, title='', app='', url='', permission=True, selected=False):
self.selected = False
def __init__(self, title='', app='', stylefile='', url='', permission=True, selected=False):
self.title = title
self.app = app
self.stylefile = stylefile
self.url = url
self.permission = permission
self.selected = selected
self.url = url
## All following function are only needed to render a block from a template

View File

@ -21,7 +21,7 @@ from django.core.handlers.wsgi import WSGIHandler as Django_WSGIHandler
from django.conf import settings
class StaticFileHandler(StaticFileHandler):
class DjangoStaticFileHandler(StaticFileHandler):
"""Handels static data by using the django finders."""
def initialize(self):
@ -33,14 +33,15 @@ class StaticFileHandler(StaticFileHandler):
from django.contrib.staticfiles import finders
normalized_path = posixpath.normpath(unquote(path)).lstrip('/')
absolute_path = finders.find(normalized_path)
return super(StaticFileHandler, self).get(absolute_path, include_body)
return super(DjangoStaticFileHandler, self).get(absolute_path, include_body)
def run_tornado(addr, port):
parse_command_line()
app = WSGIContainer(Django_WSGIHandler())
tornado_app = Application([
(r"%s(.*)" % settings.STATIC_URL, StaticFileHandler),
(r"%s(.*)" % settings.STATIC_URL, DjangoStaticFileHandler),
(r'%s(.*)' % settings.MEDIA_URL, StaticFileHandler, {'path': settings.MEDIA_ROOT}),
('.*', FallbackHandler, dict(fallback=app))])
server = HTTPServer(tornado_app)

View File

@ -385,14 +385,24 @@ def server_error(request, template_name='500.html'):
@receiver(template_manipulation, dispatch_uid="send_register_tab")
def send_register_tab(sender, request, context, **kwargs):
"""
Receiver to the template_manipulation signal. Collects from the file
views.py in all apps the tabs setup by the function register_tab.
Inserts the tab objects and also the extra_stylefiles to the context.
"""
tabs = []
extra_stylefiles = []
for app in settings.INSTALLED_APPS:
try:
mod = import_module(app + '.views')
tabs.append(mod.register_tab(request))
tab = mod.register_tab(request)
tabs.append(tab)
if tab.stylefile:
extra_stylefiles.append(tab.stylefile)
except (ImportError, AttributeError):
continue
context.update({
'tabs': tabs,
'extra_stylefiles': extra_stylefiles,
})

View File

204
tests/mediafile/tests.py Normal file
View File

@ -0,0 +1,204 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
openslides.mediafile.tests
~~~~~~~~~~~~~~~~~~~~~~~~~~
Unit test for the mediafile app.
:copyright: 20112013 by OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details.
"""
import os
import tempfile
from django.test.client import Client
from django.core.files.uploadedfile import SimpleUploadedFile
from django.conf import settings
from django.contrib.contenttypes.models import ContentType
from django.contrib.auth.models import Permission
from openslides.utils.test import TestCase
from openslides.mediafile.models import Mediafile
from openslides.participant.models import User
class MediafileTest(TestCase):
"""
Unit test for the mediafile model.
"""
def setUp(self):
# Setup the three permissions
ct = ContentType.objects.get(app_label='mediafile', model='mediafile')
perm_1 = Permission.objects.get(content_type=ct, codename='can_see')
perm_2 = Permission.objects.get(content_type=ct, codename='can_upload')
perm_3 = Permission.objects.get(content_type=ct, codename='can_manage')
# Setup three different users
self.manager = User.objects.create(username='mediafile_test_manager')
self.manager.reset_password('default')
self.manager.user_permissions.add(perm_1, perm_2, perm_3)
self.vip_user = User.objects.create(username='mediafile_test_vip_user')
self.vip_user.reset_password('default')
self.vip_user.user_permissions.add(perm_1, perm_2)
self.normal_user = User.objects.create(username='mediafile_test_normal_user')
self.normal_user.reset_password('default')
# Setup a mediafile object
self.tmp_dir = os.path.realpath(os.path.dirname(__file__))
settings.MEDIA_ROOT = self.tmp_dir
tmpfile_no, mediafile_path = tempfile.mkstemp(prefix='tmp_openslides_test', dir=self.tmp_dir)
self.object = Mediafile.objects.create(title='Title File 1', mediafile=mediafile_path, uploader=self.vip_user)
os.close(tmpfile_no)
def tearDown(self):
self.object.mediafile.delete()
def test_unicode(self):
self.assertEqual(self.object.__unicode__(), 'Title File 1')
def test_absolute_url(self):
self.assertEqual(self.object.get_absolute_url(), '/mediafile/1/edit/')
self.assertEqual(self.object.get_absolute_url('edit'), '/mediafile/1/edit/')
self.assertEqual(self.object.get_absolute_url('update'), '/mediafile/1/edit/')
self.assertEqual(self.object.get_absolute_url(link='delete'), '/mediafile/1/del/')
def login_clients(self):
"""
Helper function to login all three test users.
"""
client_manager = Client()
client_manager.login(username='mediafile_test_manager', password='default')
client_vip_user = Client()
client_vip_user.login(username='mediafile_test_vip_user', password='default')
client_normal_user = Client()
client_normal_user.login(username='mediafile_test_normal_user', password='default')
return {'client_manager': client_manager,
'client_vip_user': client_vip_user,
'client_normal_user': client_normal_user}
def test_see_mediafilelist(self):
for client in self.login_clients().itervalues():
response = client.get('/mediafile/')
self.assertEqual(response.status_code, 200)
bad_client = Client()
response = bad_client.get('/mediafile/')
self.assertEqual(response.status_code, 302)
def test_upload_mediafile_get_request(self):
clients = self.login_clients()
response = clients['client_manager'].get('/mediafile/new/')
self.assertContains(response, '---------', status_code=200)
self.assertContains(response, '<option value="user:1" selected="selected">mediafile_test_manager</option>', status_code=200)
response = clients['client_vip_user'].get('/mediafile/new/')
self.assertEqual(response.status_code, 200)
response = clients['client_normal_user'].get('/mediafile/new/')
self.assertEqual(response.status_code, 403)
bad_client = Client()
response = bad_client.get('/mediafile/new/')
self.assertEqual(response.status_code, 302)
def test_upload_mediafile_post_request(self):
# Test first user
client_1 = self.login_clients()['client_manager']
new_file_1 = SimpleUploadedFile(name='new_test_file.txt', content='test content hello manager')
response_1 = client_1.post('/mediafile/new/',
{'title': 'new_test_file_title_1',
'mediafile': new_file_1,
'uploader': self.manager.person_id})
self.assertEqual(response_1.status_code, 302)
object_1 = Mediafile.objects.latest('timestamp')
self.assertEqual(object_1.mediafile.url, '/media/file/new_test_file.txt')
path_1 = object_1.mediafile.path
object_1.mediafile.delete()
self.assertFalse(os.path.exists(path_1))
# Test second user
client_2 = self.login_clients()['client_vip_user']
new_file_2 = SimpleUploadedFile(name='new_test_file.txt', content='test content hello vip_user')
response_2 = client_2.post('/mediafile/new/',
{'title': 'new_test_file_title_2',
'mediafile': new_file_2})
self.assertEqual(response_2.status_code, 302)
object_2 = Mediafile.objects.latest('timestamp')
self.assertEqual(object_2.mediafile.url, '/media/file/new_test_file.txt')
path_2 = object_2.mediafile.path
object_2.mediafile.delete()
self.assertFalse(os.path.exists(path_2))
# Test third user
client_3 = self.login_clients()['client_normal_user']
new_file_3 = SimpleUploadedFile(name='new_test_file.txt', content='test content hello vip_user')
response_3 = client_3.post('/mediafile/new/',
{'title': 'new_test_file_title_2',
'mediafile': new_file_3})
self.assertEqual(response_3.status_code, 403)
def test_edit_mediafile_get_request(self):
clients = self.login_clients()
response = clients['client_manager'].get('/mediafile/1/edit/')
self.assertContains(response, '---------', status_code=200)
self.assertContains(response, '<option value="user:2" selected="selected">mediafile_test_vip_user</option>', status_code=200)
response = clients['client_vip_user'].get('/mediafile/1/edit/')
self.assertEqual(response.status_code, 403)
response = clients['client_normal_user'].get('/mediafile/1/edit/')
self.assertEqual(response.status_code, 403)
bad_client = Client()
response = bad_client.get('/mediafile/1/edit/')
self.assertEqual(response.status_code, 302)
def test_edit_mediafile_post_request(self):
# Test only one user
tmpfile_no, mediafile_2_path = tempfile.mkstemp(prefix='tmp_openslides_test', dir=self.tmp_dir)
os.close(tmpfile_no)
object_2 = Mediafile.objects.create(title='Title File 2', mediafile=mediafile_2_path, uploader=self.vip_user)
client_1 = self.login_clients()['client_manager']
new_file_1 = SimpleUploadedFile(name='new_test_file.txt', content='test content hello manager')
response_1 = client_1.post('/mediafile/2/edit/',
{'title': 'new_test_file_title_1',
'mediafile': new_file_1,
'uploader': self.manager.person_id})
self.assertEqual(response_1.status_code, 302)
object_2 = Mediafile.objects.get(pk=2)
self.assertEqual(object_2.mediafile.url, '/media/file/new_test_file.txt')
self.assertEqual(object_2.uploader, self.manager)
path_2 = object_2.mediafile.path
object_2.mediafile.delete()
self.assertFalse(os.path.exists(path_2))
def test_delete_mediafile_get_request(self):
clients = self.login_clients()
response = clients['client_manager'].get('/mediafile/1/del/')
self.assertEqual(response.status_code, 302)
response = clients['client_vip_user'].get('/mediafile/1/del/')
self.assertEqual(response.status_code, 403)
response = clients['client_normal_user'].get('/mediafile/1/del/')
self.assertEqual(response.status_code, 403)
bad_client = Client()
response = bad_client.get('/mediafile/2/del/')
self.assertEqual(response.status_code, 302)
def test_delete_mediafile_post_request(self):
tmpfile_no, mediafile_3_path = tempfile.mkstemp(prefix='tmp_openslides_test', dir=self.tmp_dir)
os.close(tmpfile_no)
object_3 = Mediafile.objects.create(title='Title File 3', mediafile=mediafile_3_path)
client_1 = self.login_clients()['client_manager']
response_1 = client_1.post('/mediafile/2/del/', {'yes': 'foo'})
self.assertEqual(response_1.status_code, 302)
self.assertFalse(os.path.exists(object_3.mediafile.path))
def test_filesize(self):
tmpfile_no, mediafile_4_path = tempfile.mkstemp(prefix='tmp_openslides_test', dir=self.tmp_dir)
os.close(tmpfile_no)
object_4 = Mediafile.objects.create(title='Title File 4', mediafile=mediafile_4_path)
self.assertEqual(object_4.get_filesize(), '< 1 kB')
with open(object_4.mediafile.path, 'wb') as bigfile:
bigfile.seek(2047)
bigfile.write('0')
self.assertEqual(object_4.get_filesize(), '2 kB')
with open(object_4.mediafile.path, 'wb') as bigfile:
bigfile.seek(1048575)
bigfile.write('0')
self.assertEqual(object_4.get_filesize(), '1 MB')
object_4.mediafile.delete()