Merge pull request #1343 from ostcar/user

New users app.
This commit is contained in:
Norman Jäckel 2014-10-12 10:32:41 +02:00
commit 0a2923f7e3
87 changed files with 881 additions and 1382 deletions

View File

@ -263,7 +263,7 @@ def createsuperuser(settings, args):
ensure_settings(settings, args)
# can't be imported in global scope as it already requires
# the settings module during import
from openslides.participant.api import create_or_reset_admin_user
from openslides.users.api import create_or_reset_admin_user
if create_or_reset_admin_user():
print('Admin user successfully created.')
else:

View File

@ -6,7 +6,7 @@ from django.utils.translation import ugettext_lazy
from mptt.forms import TreeNodeChoiceField
from openslides.utils.forms import CssClassMixin, CleanHtmlFormMixin
from openslides.utils.person.forms import PersonFormField
from openslides.users.models import User
from .models import Item, Speaker
@ -59,7 +59,8 @@ class AppendSpeakerForm(CssClassMixin, forms.Form):
"""
Form to set an user to a list of speakers.
"""
speaker = PersonFormField(
speaker = forms.ModelChoiceField(
User.objects.all(),
widget=forms.Select(attrs={'class': 'medium-input'}),
label=ugettext_lazy("Add participant"))
@ -72,7 +73,7 @@ class AppendSpeakerForm(CssClassMixin, forms.Form):
Checks, that the user is not already on the list.
"""
speaker = self.cleaned_data['speaker']
if Speaker.objects.filter(person=speaker, item=self.item, begin_time=None).exists():
if Speaker.objects.filter(user=speaker, item=self.item, begin_time=None).exists():
raise forms.ValidationError(ugettext_lazy(
'%s is already on the list of speakers.'
% str(speaker)))

View File

@ -17,8 +17,8 @@ from openslides.projector.api import (get_active_slide, reset_countdown,
from openslides.projector.models import SlideMixin
from openslides.utils.exceptions import OpenSlidesError
from openslides.utils.models import AbsoluteUrlMixin
from openslides.utils.person.models import PersonField
from openslides.utils.utils import to_roman
from openslides.users.models import User
class Item(SlideMixin, AbsoluteUrlMixin, MPTTModel):
@ -279,7 +279,7 @@ class Item(SlideMixin, AbsoluteUrlMixin, MPTTModel):
def get_next_speaker(self):
"""
Returns the speaker object of the person who is next.
Returns the speaker object of the user who is next.
"""
try:
return self.speaker_set.filter(begin_time=None).order_by('weight')[0]
@ -337,17 +337,17 @@ class Item(SlideMixin, AbsoluteUrlMixin, MPTTModel):
class SpeakerManager(models.Manager):
def add(self, person, item):
if self.filter(person=person, item=item, begin_time=None).exists():
def add(self, user, item):
if self.filter(user=user, item=item, begin_time=None).exists():
raise OpenSlidesError(_(
'%(person)s is already on the list of speakers of item %(id)s.')
% {'person': person, 'id': item.id})
if isinstance(person, AnonymousUser):
'%(user)s is already on the list of speakers of item %(id)s.')
% {'user': user, 'id': item.id})
if isinstance(user, AnonymousUser):
raise OpenSlidesError(
_('An anonymous user can not be on lists of speakers.'))
weight = (self.filter(item=item).aggregate(
models.Max('weight'))['weight__max'] or 0)
return self.create(item=item, person=person, weight=weight + 1)
return self.create(item=item, user=user, weight=weight + 1)
class Speaker(AbsoluteUrlMixin, models.Model):
@ -357,14 +357,14 @@ class Speaker(AbsoluteUrlMixin, models.Model):
objects = SpeakerManager()
person = PersonField()
user = models.ForeignKey(User)
"""
ForeinKey to the person who speaks.
ForeinKey to the user who speaks.
"""
item = models.ForeignKey(Item)
"""
ForeinKey to the AgendaItem to which the person want to speak.
ForeinKey to the AgendaItem to which the user want to speak.
"""
begin_time = models.DateTimeField(null=True)
@ -396,11 +396,11 @@ class Speaker(AbsoluteUrlMixin, models.Model):
self.check_and_update_projector()
def __str__(self):
return str(self.person)
return str(self.user)
def get_absolute_url(self, link='detail'):
if link == 'detail':
url = self.person.get_absolute_url('detail')
url = self.user.get_absolute_url('detail')
elif link == 'delete':
url = reverse('agenda_speaker_delete',
args=[self.item.pk, self.pk])
@ -421,7 +421,7 @@ class Speaker(AbsoluteUrlMixin, models.Model):
def begin_speach(self):
"""
Let the person speak.
Let the user speak.
Set the weight to None and the time to now. If anyone is still
speaking, end his speach.

View File

@ -7,12 +7,12 @@ from .models import Item
class AgendaPersonalInfo(PersonalInfo):
"""
Class for personal info block for the agenda app.
Class for user info block for the agenda app.
"""
headline = ugettext_lazy('I am on the list of speakers of the following items')
default_weight = 10
def get_queryset(self):
return Item.objects.filter(
speaker__person=self.request.user,
speaker__user=self.request.user,
speaker__begin_time=None)

View File

@ -121,7 +121,7 @@
{% if speaker_dict.type == 'actual_speaker' %}
<a href="{% url 'agenda_speaker_end_speach' item.pk %}" class="btn btn-mini btn-danger"><i class="icon-bell icon-white"></i> {% trans 'End speach' %}</a>
{% elif speaker_dict.type == 'coming_speaker' %}
<a href="{% url 'agenda_speaker_speak' item.pk speaker_dict.speaker.person.person_id %}"
<a href="{% url 'agenda_speaker_speak' item.pk speaker_dict.speaker.user.pk %}"
class="btn btn-mini"><i class="icon-bell"></i> {% trans "Begin speach" %}</a>
{% endif %}
<a href="{{ speaker_dict.speaker|absolute_url:'delete' }}"
@ -150,8 +150,8 @@
<div class="control-group input-append {% if field.errors %}error{% endif %}">
{{ field }}
<button class="btn btn-primary tooltip-bottom" type="submit" data-original-title="{% trans 'Apply' %}"><i class="icon-ok icon-white"></i></button>
{% if perms.participant.can_see_participant and perms.participant.can_manage_participant %}
<a href="{% url 'user_new' %}" class="btn" rel="tooltip" data-original-title="{% trans 'Add new participant' %}"><i class="icon-add-user"></i></a>
{% if perms.users.can_see and perms.users.can_manage %}
<a href="{% url 'user_new' %}" class="btn" rel="tooltip" data-original-title="{% trans 'Add new user' %}"><i class="icon-add-user"></i></a>
{% endif %}
{% if field.errors %}
<span class="help-inline">{{ field.errors }}</span>

View File

@ -63,7 +63,7 @@ urlpatterns = patterns(
views.SpeakerDeleteView.as_view(),
name='agenda_speaker_delete'),
url(r'^(?P<pk>\d+)/speaker/(?P<person_id>[^/]+)/speak/$',
url(r'^(?P<pk>\d+)/speaker/(?P<user_id>[^/]+)/speak/$',
views.SpeakerSpeakView.as_view(),
name='agenda_speaker_speak'),

View File

@ -190,13 +190,13 @@ class AgendaItemView(SingleObjectMixin, FormView):
'object': self.object,
'list_of_speakers': list_of_speakers,
'is_on_the_list_of_speakers': Speaker.objects.filter(
item=self.object, begin_time=None, person=self.request.user).exists(),
item=self.object, begin_time=None, user=self.request.user).exists(),
'active_type': active_type,
})
return super(AgendaItemView, self).get_context_data(**kwargs)
def form_valid(self, form):
Speaker.objects.add(person=form.cleaned_data['speaker'], item=self.get_object())
Speaker.objects.add(user=form.cleaned_data['speaker'], item=self.get_object())
return self.render_to_response(self.get_context_data(form=form))
def get_form_kwargs(self):
@ -392,7 +392,7 @@ class SpeakerAppendView(SingleObjectMixin, RedirectView):
messages.error(request, _('The list of speakers is closed.'))
else:
try:
Speaker.objects.add(item=self.object, person=request.user)
Speaker.objects.add(item=self.object, user=request.user)
except OpenSlidesError as e:
messages.error(request, e)
else:
@ -413,7 +413,7 @@ class SpeakerDeleteView(DeleteView):
if 'speaker' in kwargs:
return request.user.has_perm('agenda.can_manage_agenda')
else:
# Any person who is on the list of speakers can delete himself from the list.
# Any user who is on the list of speakers can delete himself from the list.
return True
def get(self, *args, **kwargs):
@ -434,7 +434,7 @@ class SpeakerDeleteView(DeleteView):
return Speaker.objects.get(pk=self.kwargs['speaker'])
except KeyError:
return Speaker.objects.filter(
item=self.kwargs['pk'], person=self.request.user).exclude(weight=None).get()
item=self.kwargs['pk'], user=self.request.user).exclude(weight=None).get()
def get_url_name_args(self):
return [self.kwargs['pk']]
@ -448,7 +448,7 @@ class SpeakerDeleteView(DeleteView):
class SpeakerSpeakView(SingleObjectMixin, RedirectView):
"""
Mark the speaking person.
Mark the speaking user.
"""
required_permission = 'agenda.can_manage_agenda'
url_name = 'item_view'
@ -458,14 +458,14 @@ class SpeakerSpeakView(SingleObjectMixin, RedirectView):
self.object = self.get_object()
try:
speaker = Speaker.objects.filter(
person=kwargs['person_id'],
user=kwargs['user_id'],
item=self.object,
begin_time=None).get()
except Speaker.DoesNotExist: # TODO: Check the MultipleObjectsReturned error here?
messages.error(
self.request,
_('%(person)s is not on the list of %(item)s.')
% {'person': kwargs['person_id'], 'item': self.object})
_('%(user)s is not on the list of %(item)s.')
% {'user': kwargs['user_id'], 'item': self.object})
else:
speaker.begin_speach()

View File

@ -1 +1 @@
from . import main_menu, personal_info, signals, slides, template, widgets # noqa
# TODO: apploader

View File

@ -1,8 +1,8 @@
from django import forms
from django.utils.translation import ugettext_lazy
from openslides.users.models import User
from openslides.utils.forms import CssClassMixin
from openslides.utils.person import PersonFormField
from .models import Assignment
@ -17,6 +17,7 @@ class AssignmentForm(CssClassMixin, forms.ModelForm):
class AssignmentRunForm(CssClassMixin, forms.Form):
candidate = PersonFormField(
candidate = forms.ModelChoiceField(
queryset=User.objects.all(),
widget=forms.Select(attrs={'class': 'medium-input'}),
label=ugettext_lazy("Nominate a participant"))

View File

@ -14,8 +14,8 @@ from openslides.projector.api import get_active_object, update_projector
from openslides.projector.models import RelatedModelMixin, SlideMixin
from openslides.utils.exceptions import OpenSlidesError
from openslides.utils.models import AbsoluteUrlMixin
from openslides.utils.person import PersonField
from openslides.utils.utils import html_strong
from openslides.users.models import User
class AssignmentCandidate(RelatedModelMixin, models.Model):
@ -23,7 +23,7 @@ class AssignmentCandidate(RelatedModelMixin, models.Model):
Many2Many table between an assignment and the candidates.
"""
assignment = models.ForeignKey("Assignment")
person = PersonField(db_index=True)
person = models.ForeignKey(User, db_index=True)
elected = models.BooleanField(default=False)
blocked = models.BooleanField(default=False)
@ -178,12 +178,11 @@ class Assignment(SlideMixin, AbsoluteUrlMixin, models.Model):
if only_candidate:
candidates = candidates.filter(elected=False)
# TODO: rewrite this with a queryset
participants = []
for candidate in candidates.all():
participants.append(candidate.person)
participants.sort(key=lambda person: person.sort_name)
return participants
# return candidates.values_list('person', flat=True)
def set_elected(self, person, value=True):
candidate = self.assignment_candidates.get(person=person)
@ -251,8 +250,6 @@ class Assignment(SlideMixin, AbsoluteUrlMixin, models.Model):
for poll in polls:
options += poll.get_options()
options.sort(key=lambda option: option.candidate.sort_name)
for option in options:
candidate = option.candidate
if candidate in vote_results_dict:
@ -284,7 +281,7 @@ class AssignmentVote(BaseVote):
class AssignmentOption(BaseOption):
poll = models.ForeignKey('AssignmentPoll')
candidate = PersonField()
candidate = models.ForeignKey(User)
vote_class = AssignmentVote
def __str__(self):
@ -345,3 +342,7 @@ class AssignmentPoll(SlideMixin, RelatedModelMixin, CollectDefaultVotesMixin,
def get_slide_context(self, **context):
return super(AssignmentPoll, self).get_slide_context(poll=self)
# TODO: use the app framework
from . import main_menu, personal_info, signals, slides, template, widgets # noqa

View File

@ -69,14 +69,14 @@
<a href="{{ person|absolute_url }}">{{ person }}</a>
{% if perms.assignment.can_manage_assignment %}
{% if assignment.status == "sea" or assignment.status == "vot" %}
<a href="{% url 'assignment_delother' assignment.id person.person_id %}" class="btn btn-mini" rel="tooltip" data-original-title="{% trans 'Remove candidate' %}"><i class="icon-remove"></i></a>
<a href="{% url 'assignment_delother' assignment.id person.pk %}" class="btn btn-mini" rel="tooltip" data-original-title="{% trans 'Remove candidate' %}"><i class="icon-remove"></i></a>
{% endif %}
{% endif %}
{% if person in assignment.elected %}
| <b>{% trans "elected" %}</b>
{% if perms.assignment.can_manage_assignment %}
{% if assignment.status == "sea" or assignment.status == "vot" %}
<a class="btn btn-mini" href="{% url 'assignment_user_not_elected' assignment.id person.person_id %}" rel="tooltip" data-original-title="{% trans 'Mark candidate as not elected' %}">
<a class="btn btn-mini" href="{% url 'assignment_user_not_elected' assignment.id person.pk %}" rel="tooltip" data-original-title="{% trans 'Mark candidate as not elected' %}">
<i class="icon-ban-circle"></i>
</a>
{% endif %}
@ -128,7 +128,7 @@
{% for person in blocked_candidates %}
<li>
<a href="{{ person|absolute_url }}">{{ person }}</a>
<a class="btn btn-mini" href="{% url 'assignment_delother' assignment.id person.person_id %}"
<a class="btn btn-mini" href="{% url 'assignment_delother' assignment.id person.pk %}"
rel="tooltip" data-original-title="{% trans 'Remove candidate' %}">
<i class="icon-ban-circle"></i>
</a>
@ -190,7 +190,7 @@
<td>
{% if candidate in assignment.elected %}
{% if perms.assignment.can_manage_assignment %}
<a class="election_link elected tooltip-bottom" href="{% url 'assignment_user_not_elected' assignment.id candidate.person_id %}"
<a class="election_link elected tooltip-bottom" href="{% url 'assignment_user_not_elected' assignment.id candidate.pk %}"
data-original-title="{% trans 'Mark candidate as elected' %}"></a>
{% else %}
<a class="elected">
@ -199,7 +199,7 @@
{% endif %}
{% else %}
{% if perms.assignment.can_manage_assignment %}
<a class="election_link tooltip-bottom" href="{% url 'assignment_user_elected' assignment.id candidate.person_id %}"
<a class="election_link tooltip-bottom" href="{% url 'assignment_user_elected' assignment.id candidate.pk %}"
data-original-title="{% trans 'Mark candidate as elected' %}"></a>
{% endif %}
{% endif %}

View File

@ -10,10 +10,9 @@ from reportlab.platypus import (PageBreak, Paragraph, SimpleDocTemplate, Spacer,
from openslides.agenda.views import CreateRelatedAgendaItemView as _CreateRelatedAgendaItemView
from openslides.config.api import config
from openslides.participant.models import Group, User
from openslides.users.models import Group, User # TODO: remove this
from openslides.poll.views import PollFormView
from openslides.utils.pdf import stylesheet
from openslides.utils.person import get_person
from openslides.utils.utils import html_strong
from openslides.utils.views import (CreateView, DeleteView, DetailView,
ListView, PDFView, PermissionMixin,
@ -187,7 +186,7 @@ class AssignmentRunOtherDeleteView(SingleObjectMixin, QuestionView):
def _get_person_information(self):
self.object = self.get_object()
self.person = get_person(self.kwargs.get('user_id'))
self.person = User.objects.get(pk=self.kwargs.get('user_id'))
self.is_blocked = self.object.is_blocked(self.person)
@ -252,7 +251,7 @@ class SetElectedView(SingleObjectMixin, RedirectView):
def pre_redirect(self, *args, **kwargs):
self.object = self.get_object()
self.person = get_person(kwargs['user_id'])
self.person = User.objects.get(pk=kwargs['user_id'])
self.elected = kwargs['elected']
self.object.set_elected(self.person, self.elected)
@ -406,8 +405,8 @@ class AssignmentPDF(PDFView):
candidate_string = candidate.clean_name
if candidate in elected_candidates:
candidate_string = "* " + candidate_string
if candidate.name_suffix:
candidate_string += "\n(%s)" % candidate.name_suffix
if candidate.structure_level:
candidate_string += "\n(%s)" % candidate.structure_level
row.append(candidate_string)
for vote in poll_list:
if vote is None:
@ -505,7 +504,7 @@ class AssignmentPollPDF(PDFView):
def get(self, request, *args, **kwargs):
self.poll = AssignmentPoll.objects.get(id=self.kwargs['poll_id'])
return super(AssignmentPollPDF, self).get(request, *args, **kwargs)
return super().get(request, *args, **kwargs)
def get_filename(self):
filename = u'%s-%s_%s' % (
@ -570,10 +569,10 @@ class AssignmentPollPDF(PDFView):
counter += 1
candidate = option.candidate
cell.append(Paragraph(
candidate.clean_name, stylesheet['Ballot_option_name_YNA']))
if candidate.name_suffix:
candidate.get_short_name(), stylesheet['Ballot_option_name_YNA']))
if candidate.structure_level:
cell.append(Paragraph(
"(%s)" % candidate.name_suffix,
"(%s)" % candidate.structure_level,
stylesheet['Ballot_option_suffix_YNA']))
else:
cell.append(Paragraph(
@ -616,9 +615,9 @@ class AssignmentPollPDF(PDFView):
cell.append(Paragraph("<font name='circlefont' size='15'>%s</font> \
<font name='Ubuntu'>%s</font>" %
(circle, candidate.clean_name), stylesheet['Ballot_option_name']))
if candidate.name_suffix:
if candidate.structure_level:
cell.append(Paragraph(
"(%s)" % candidate.name_suffix,
"(%s)" % candidate.structure_level,
stylesheet['Ballot_option_suffix']))
else:
cell.append(Paragraph(

View File

@ -1 +1 @@
from . import main_menu, signals, slides, widgets # noqa
# TODO: apploading

View File

@ -16,7 +16,7 @@ class ChatboxSocketHandler(SockJSConnection):
"""
Checks connecting user and adds his client to the clients list.
"""
from openslides.participant.models import User
from openslides.users.models import User
# TODO: Use the django way to get the session to be compatible with
# other auth-backends; see comment in pull request #1220:

View File

@ -1,10 +1,14 @@
from django.core.urlresolvers import reverse
from django.db import models
from django.utils.translation import ugettext_lazy, ugettext_noop
from django.contrib.auth import get_user_model
from openslides.utils.models import AbsoluteUrlMixin
from openslides.projector.models import SlideMixin
# Imports the default user so that other apps can import it from here.
User = get_user_model()
class CustomSlide(SlideMixin, AbsoluteUrlMixin, models.Model):
"""
@ -38,3 +42,7 @@ class CustomSlide(SlideMixin, AbsoluteUrlMixin, models.Model):
else:
url = super(CustomSlide, self).get_absolute_url(link)
return url
# TODO: apploader
from . import main_menu, signals, slides, widgets # noqa

View File

@ -11,6 +11,8 @@ AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend',
'openslides.utils.auth.AnonymousAuth',)
AUTH_USER_MODEL = 'users.User'
LOGIN_URL = '/login/'
LOGIN_REDIRECT_URL = '/'
@ -66,7 +68,7 @@ MIDDLEWARE_CLASSES = (
'django.middleware.locale.LocaleMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'openslides.participant.middleware.AuthenticationMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'openslides.config.middleware.ConfigCacheMiddleware',
)
@ -90,7 +92,7 @@ INSTALLED_APPS = (
'openslides.agenda',
'openslides.motion',
'openslides.assignment',
'openslides.participant',
'openslides.users',
'openslides.mediafile',
'openslides.config',
)

View File

@ -7,7 +7,7 @@ from django.utils.translation import ugettext_lazy, ugettext_noop
from openslides.projector.models import SlideMixin
from openslides.utils.models import AbsoluteUrlMixin
from openslides.utils.person.models import PersonField
from openslides.users.models import User
class Mediafile(SlideMixin, AbsoluteUrlMixin, models.Model):
@ -17,17 +17,17 @@ class Mediafile(SlideMixin, AbsoluteUrlMixin, models.Model):
slide_callback_name = 'mediafile'
PRESENTABLE_FILE_TYPES = ['application/pdf']
mediafile = models.FileField(upload_to='file', verbose_name=ugettext_lazy("File"))
mediafile = models.FileField(upload_to='file', verbose_name=ugettext_lazy('File'))
"""
See https://docs.djangoproject.com/en/dev/ref/models/fields/#filefield
for more information.
"""
title = models.CharField(max_length=255, unique=True, verbose_name=ugettext_lazy("Title"))
title = models.CharField(max_length=255, unique=True, verbose_name=ugettext_lazy('Title'))
"""A string representing the title of the file."""
uploader = PersonField(blank=True, verbose_name=ugettext_lazy("Uploaded by"))
"""A person the uploader of a file."""
uploader = models.ForeignKey(User, null=True, blank=True, verbose_name=ugettext_lazy('Uploaded by'))
"""A user the uploader of a file."""
timestamp = models.DateTimeField(auto_now_add=True)
"""A DateTimeField to save the upload date and time."""

View File

@ -70,7 +70,7 @@ class MediafileCreateView(MediafileViewMixin, CreateView):
def get_form_kwargs(self, *args, **kwargs):
form_kwargs = super(MediafileCreateView, self).get_form_kwargs(*args, **kwargs)
if self.request.method == 'GET':
form_kwargs['initial'].update({'uploader': self.request.user.person_id})
form_kwargs['initial'].update({'uploader': self.request.user})
return form_kwargs
@ -80,11 +80,12 @@ class MediafileUpdateView(MediafileViewMixin, UpdateView):
"""
def check_permission(self, request, *args, **kwargs):
return (request.user.has_perm('mediafile.can_manage') or
(request.user.has_perm('mediafile.can_upload') and self.get_object().uploader == self.request.user))
(request.user.has_perm('mediafile.can_upload') and
self.get_object().uploader == self.request.user))
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})
form_kwargs['initial'].update({'uploader': self.object.uploader.pk})
return form_kwargs

View File

@ -1 +1 @@
from . import main_menu, personal_info, signals, slides, widgets # noqa
# TODO: Apploading

View File

@ -9,7 +9,7 @@ from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_noop
from openslides.utils import csv_ext
from openslides.utils.person.api import Persons
from openslides.users.models import User
from openslides.utils.utils import html_strong
from .models import Category, Motion
@ -90,8 +90,8 @@ def import_motions(csvfile, default_submitter, override, importing_person=None):
# Add submitter
person_found = False
if submitter:
for person in Persons():
if person.clean_name == submitter:
for person in User.objects.all():
if person.get_short_name() == submitter:
if person_found:
warning.append(_('Several suitable submitters found.'))
person_found = False

View File

@ -5,7 +5,7 @@ from openslides.config.api import config
from openslides.mediafile.models import Mediafile
from openslides.utils.forms import (CleanHtmlFormMixin, CssClassMixin,
CSVImportForm, LocalizedModelChoiceField)
from openslides.utils.person import MultiplePersonFormField, PersonFormField
from openslides.users.models import User
from ckeditor.widgets import CKEditorWidget
@ -72,14 +72,14 @@ class BaseMotionForm(CleanHtmlFormMixin, CssClassMixin, forms.ModelForm):
class MotionSubmitterMixin(forms.ModelForm):
"""Mixin to append the submitter field to a MotionForm."""
submitter = MultiplePersonFormField(label=ugettext_lazy("Submitter"),
required=False)
submitter = forms.ModelMultipleChoiceField(
User.objects, label=ugettext_lazy("Submitter"), required=False)
"""Submitter of the motion. Can be one or more persons."""
def __init__(self, *args, **kwargs):
"""Fill in the submitter of the motion as default value."""
if self.motion is not None:
submitter = [submitter.person.person_id for submitter in self.motion.submitter.all()]
submitter = [submitter.person.id for submitter in self.motion.submitter.all()]
self.initial['submitter'] = submitter
super(MotionSubmitterMixin, self).__init__(*args, **kwargs)
@ -87,13 +87,14 @@ class MotionSubmitterMixin(forms.ModelForm):
class MotionSupporterMixin(forms.ModelForm):
"""Mixin to append the supporter field to a Motionform."""
supporter = MultiplePersonFormField(required=False, label=ugettext_lazy("Supporters"))
supporter = forms.ModelMultipleChoiceField(
User.objects, required=False, label=ugettext_lazy("Supporters"))
"""Supporter of the motion. Can be one or more persons."""
def __init__(self, *args, **kwargs):
"""Fill in the supporter of the motions as default value."""
if self.motion is not None:
supporter = [supporter.person.person_id for supporter in self.motion.supporter.all()]
supporter = [supporter.person.id for supporter in self.motion.supporter.all()]
self.initial['supporter'] = supporter
super(MotionSupporterMixin, self).__init__(*args, **kwargs)
@ -169,7 +170,8 @@ class MotionCSVImportForm(CSVImportForm):
should be overridden.
"""
default_submitter = PersonFormField(
default_submitter = forms.ModelChoiceField(
User.objects.all(),
required=True,
label=ugettext_lazy('Default submitter'),
help_text=ugettext_lazy('This person is used as submitter for any line of your csv file which does not contain valid submitter data.'))

View File

@ -11,7 +11,7 @@ from openslides.poll.models import (BaseOption, BasePoll, BaseVote, CollectDefau
from openslides.projector.models import RelatedModelMixin, SlideMixin
from jsonfield import JSONField
from openslides.utils.models import AbsoluteUrlMixin
from openslides.utils.person import PersonField
from openslides.users.models import User
from .exceptions import WorkflowError
@ -366,8 +366,7 @@ class Motion(SlideMixin, AbsoluteUrlMixin, models.Model):
@property
def supporters(self):
return sorted([object.person for object in self.supporter.all()],
key=lambda person: person.sort_name)
return [supporter.person for supporter in self.supporter.all()]
def add_submitter(self, person):
MotionSubmitter.objects.create(motion=self, person=person)
@ -582,8 +581,8 @@ class MotionSubmitter(RelatedModelMixin, models.Model):
motion = models.ForeignKey('Motion', related_name="submitter")
"""The motion to witch the object belongs."""
person = PersonField()
"""The person, who is the submitter."""
person = models.ForeignKey(User)
"""The user, who is the submitter."""
def __str__(self):
"""Return the name of the submitter as string."""
@ -599,7 +598,7 @@ class MotionSupporter(models.Model):
motion = models.ForeignKey('Motion', related_name="supporter")
"""The motion to witch the object belongs."""
person = PersonField()
person = models.ForeignKey(User)
"""The person, who is the supporter."""
def __str__(self):
@ -632,12 +631,6 @@ class Category(AbsoluteUrlMixin, models.Model):
class Meta:
ordering = ['prefix']
# class Comment(models.Model):
# motion_version = models.ForeignKey(MotionVersion)
# text = models.TextField()
# author = PersonField()
# creation_time = models.DateTimeField(auto_now=True)
class MotionLog(models.Model):
"""Save a logmessage for a motion."""
@ -650,7 +643,7 @@ class MotionLog(models.Model):
The log message. It should be a list of strings in english.
"""
person = PersonField(null=True)
person = models.ForeignKey(User, null=True)
"""A person object, who created the log message. Optional."""
time = models.DateTimeField(auto_now=True)
@ -856,4 +849,10 @@ class Workflow(models.Model):
def check_first_state(self):
"""Checks whether the first_state itself belongs to the workflow."""
if self.first_state and not self.first_state.workflow == self:
raise WorkflowError('%s can not be first state of %s because it does not belong to it.' % (self.first_state, self))
raise WorkflowError(
'%s can not be first state of %s because it '
'does not belong to it.' % (self.first_state, self))
# TODO: Apploading
from . import main_menu, personal_info, signals, slides, widgets # noqa

View File

@ -9,7 +9,7 @@ from reportlab.lib.units import cm
from reportlab.platypus import PageBreak, Paragraph, Spacer, Table, TableStyle
from openslides.config.api import config
from openslides.participant.models import Group, User
from openslides.users.models import Group, User # TODO: remove this line
from openslides.utils.pdf import stylesheet
from .models import Category, Motion
@ -281,9 +281,9 @@ def motion_poll_to_pdf(pdf, poll):
# set number of ballot papers
if ballot_papers_selection == "NUMBER_OF_DELEGATES":
# TODO: get this number from persons
# TODO: get this number from users
try:
if Group.objects.get(pk=3):
if Group.objects.get(pk=3): # TODO: Find a better way
number = User.objects.filter(groups__pk=3).count()
except Group.DoesNotExist:
number = 0

View File

@ -1,10 +0,0 @@
from django.contrib.auth.middleware import AuthenticationMiddleware as _AuthenticationMiddleware
from django.contrib.auth.models import AnonymousUser
class AuthenticationMiddleware(_AuthenticationMiddleware):
def process_request(self, request):
super(AuthenticationMiddleware, self).process_request(request)
if not isinstance(request.user, AnonymousUser):
request.user = request.user.user

View File

@ -1,225 +0,0 @@
from django.contrib.auth.models import Group as DjangoGroup
from django.contrib.auth.models import User as DjangoUser
from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType
from django.core.urlresolvers import reverse
from django.db import models
from django.db.models import signals
from django.dispatch import receiver
from django.utils.translation import ugettext_lazy, ugettext_noop
from openslides.config.api import config
from openslides.projector.models import SlideMixin
from openslides.utils.models import AbsoluteUrlMixin
from openslides.utils.person import Person, PersonMixin
from openslides.utils.person.signals import receive_persons
class User(SlideMixin, PersonMixin, Person, AbsoluteUrlMixin, DjangoUser):
slide_callback_name = 'user'
person_prefix = 'user'
GENDER_CHOICES = (
('male', ugettext_lazy('Male')),
('female', ugettext_lazy('Female')),
)
django_user = models.OneToOneField(DjangoUser, editable=False, parent_link=True)
structure_level = models.CharField(
max_length=255, blank=True, default='', verbose_name=ugettext_lazy("Structure level"),
help_text=ugettext_lazy('Will be shown after the name.'))
title = models.CharField(
max_length=50, blank=True, default='', verbose_name=ugettext_lazy("Title"),
help_text=ugettext_lazy('Will be shown before the name.'))
gender = models.CharField(
max_length=50, choices=GENDER_CHOICES, blank=True,
verbose_name=ugettext_lazy("Gender"), help_text=ugettext_lazy('Only for filtering the participant list.'))
committee = models.CharField(
max_length=255, blank=True, default='', verbose_name=ugettext_lazy("Committee"),
help_text=ugettext_lazy('Only for filtering the participant list.'))
about_me = models.TextField(
blank=True, default='', verbose_name=ugettext_lazy('About me'),
help_text=ugettext_lazy('Your profile text'))
comment = models.TextField(
blank=True, default='', verbose_name=ugettext_lazy('Comment'),
help_text=ugettext_lazy('Only for notes.'))
default_password = models.CharField(
max_length=100, blank=True, default='',
verbose_name=ugettext_lazy("Default password"))
class Meta:
permissions = (
('can_see_participant', ugettext_noop('Can see participants')),
('can_manage_participant', ugettext_noop('Can manage participants')),
)
ordering = ('last_name',)
def __str__(self):
if self.name_suffix:
return u"%s (%s)" % (self.clean_name, self.name_suffix)
return u"%s" % self.clean_name
def get_absolute_url(self, link='detail'):
"""
Return the URL to the user.
"""
if link == 'detail':
url = reverse('user_view', args=[str(self.pk)])
elif link == 'update':
url = reverse('user_edit', args=[str(self.pk)])
elif link == 'delete':
url = reverse('user_delete', args=[str(self.pk)])
else:
url = super(User, self).get_absolute_url(link)
return url
def get_slide_context(self, **context):
# Does not call super. In this case the context would override the name
# 'user'.
return {'shown_user': self}
@property
def clean_name(self):
if self.title:
name = "%s %s" % (self.title, self.get_full_name())
else:
name = self.get_full_name()
return name or self.username
def get_name_suffix(self):
return self.structure_level
def set_name_suffix(self, value):
self.structure_level = value
name_suffix = property(get_name_suffix, set_name_suffix)
def reset_password(self, password=None):
"""
Reset the password for the user to his default-password.
"""
if password is None:
password = self.default_password
self.set_password(password)
self.save()
@property
def sort_name(self):
if config['participant_sort_users_by_first_name']:
return self.first_name.lower()
return self.last_name.lower()
class Group(SlideMixin, PersonMixin, Person, AbsoluteUrlMixin, DjangoGroup):
slide_callback_name = 'group'
person_prefix = 'group'
django_group = models.OneToOneField(DjangoGroup, editable=False, parent_link=True)
group_as_person = models.BooleanField(
default=False, verbose_name=ugettext_lazy("Use this group as participant"),
help_text=ugettext_lazy('For example as submitter of a motion.'))
description = models.TextField(blank=True, verbose_name=ugettext_lazy("Description"))
class Meta:
ordering = ('name',)
def __str__(self):
return str(self.name)
def get_absolute_url(self, link='detail'):
"""
Return the URL to the user group.
"""
if link == 'detail':
url = reverse('user_group_view', args=[str(self.pk)])
elif link == 'update':
url = reverse('user_group_edit', args=[str(self.pk)])
elif link == 'delete':
url = reverse('user_group_delete', args=[str(self.pk)])
else:
url = super(Group, self).get_absolute_url(link)
return url
class UsersAndGroupsToPersons(object):
"""
Object to send all Users and Groups or a special User or Group to
the Person-API via receice_persons()
"""
def __init__(self, person_prefix_filter=None, id_filter=None):
self.person_prefix_filter = person_prefix_filter
self.id_filter = id_filter
if config['participant_sort_users_by_first_name']:
self.users = User.objects.all().order_by('first_name')
else:
self.users = User.objects.all().order_by('last_name')
self.groups = Group.objects.filter(group_as_person=True)
def __iter__(self):
if (not self.person_prefix_filter or
self.person_prefix_filter == User.person_prefix):
if self.id_filter:
try:
yield self.users.get(pk=self.id_filter)
except User.DoesNotExist:
pass
else:
for user in self.users:
yield user
if (not self.person_prefix_filter or
self.person_prefix_filter == Group.person_prefix):
if self.id_filter:
try:
yield self.groups.get(pk=self.id_filter)
except Group.DoesNotExist:
pass
else:
for group in self.groups:
yield group
@receiver(receive_persons, dispatch_uid="participant")
def receive_persons(sender, **kwargs):
"""
Answers to the Person-API
"""
return UsersAndGroupsToPersons(
person_prefix_filter=kwargs['person_prefix_filter'],
id_filter=kwargs['id_filter'])
@receiver(signals.post_save, sender=DjangoUser)
def djangouser_post_save(sender, instance, signal, *args, **kwargs):
try:
instance.user
except User.DoesNotExist:
User(django_user=instance).save_base(raw=True)
@receiver(signals.post_save, sender=DjangoGroup)
def djangogroup_post_save(sender, instance, signal, *args, **kwargs):
try:
instance.group
except Group.DoesNotExist:
Group(django_group=instance).save_base(raw=True)
@receiver(signals.post_save, sender=User)
def user_post_save(sender, instance, *args, **kwargs):
if not kwargs['created']:
return
from openslides.participant.api import get_registered_group # TODO: Test, if global import is possible
registered = get_registered_group()
instance.groups.add(registered)
instance.save()
def get_protected_perm():
"""
Returns the permission to manage participants. This function is a helper
function used to protect manager users from locking out themselves.
"""
return Permission.objects.get(
content_type=ContentType.objects.get(app_label='participant', model='user'),
codename='can_manage_participant')

View File

@ -1,47 +0,0 @@
{% extends "base.html" %}
{% load i18n %}
{% load tags %}
{% block title %}{% trans group.name %} {{ block.super }}{% endblock %}
{% block content %}
<h1>{% trans group.name %}
<small class="pull-right">
<a href="{% url 'user_group_overview' %}" class="btn btn-mini"><i class="icon-chevron-left"></i><span class="optional-small"> {% trans "Back to overview" %}</span></a>
<!-- activate projector -->
{% if perms.core.can_manage_projector %}
<a href="{% url 'projector_activate_slide' group.sid %}" class="activate_link btn {% if group.active %}btn-primary{% endif %} btn-mini" rel="tooltip" data-original-title="{% trans 'Show group' %}">
<i class="icon-facetime-video {% if group.active %}icon-white{% endif %}"></i>
</a>
{% endif %}
{% if perms.participant.can_manage_participant and group.pk != 1 and group.pk != 2 %}
<div class="btn-group">
<a data-toggle="dropdown" class="btn btn-mini dropdown-toggle">
<span class="optional-small">{% trans 'More actions' %} </span><span class="caret"></span>
</a>
<ul class="dropdown-menu pull-right">
<!-- edit -->
<li><a href="{% url 'user_group_edit' group.id %}"><i class="icon-pencil"></i> {% trans 'Edit group' %}</a></li>
<!-- delete -->
<li><a href="{% url 'user_group_delete' group.id %}"><i class="icon-remove"></i> {% trans 'Delete group' %}</a></li>
</ul>
</div>
{% endif %}
</small>
</h1>
<p>{{ group.description }}</p>
<h4>{% trans "Members" %}</h4>
<ol>
{% for member in group_members %}
<li><a href="{{ member|absolute_url }}">{{ member }}</a></li>
{% empty %}
<p>{% trans "No members available." %}</p>
{% endfor %}
</ol>
{% endblock %}

View File

@ -1,14 +0,0 @@
{% load i18n %}
<h1>
{% trans group.name %}
<small>{% trans "Group" %}</small>
</h1>
<p><i>{{ group.user_set.all.count }} {% trans "participants" %}</i></p>
<ol>
{% for member in group.user_set.all %}
<li>{{ member }}</li>
{% endfor %}
</ol>

View File

@ -1,28 +0,0 @@
{% extends 'core/widget.html' %}
{% load i18n %}
{% load tags %}
{% block content %}
<ul style="line-height: 180%">
{% for group in groups %}
<li class="{% if group.is_active_slide %}activeline{% endif %}">
<a href="{{ group|absolute_url:'projector' }}" class="activate_link btn {% if group.is_active_slide %}btn-primary{% endif %} btn-mini"
rel="tooltip" data-original-title="{% trans 'Show' %}">
<i class="icon-facetime-video {% if group.is_active_slide %}icon-white{% endif %}"></i>
</a>&nbsp;
{% if perms.participant.can_manage_participant %}
<a href="{{ group|absolute_url:'update' }}"
rel="tooltip" data-original-title="{% trans 'Edit' %}" class="btn btn-mini right">
<i class="icon-pencil"></i>
</a>
{% endif %}
<a href="{{ group|absolute_url:'projector_preview' }}"
rel="tooltip" data-original-title="{% trans 'Preview' %}" class="btn btn-mini right">
<i class="icon-search"></i>
</a>
<a href="{{ group|absolute_url }}">{% trans group.name %}</a>
</li>
{% endfor %}
</ul>
{% endblock %}

View File

@ -1,45 +0,0 @@
from django.utils.translation import ugettext_lazy
from openslides.utils.widgets import Widget
from .models import Group, User
class UserWidget(Widget):
"""
Provides a widget with all users. This is for short activation of
user slides.
"""
name = 'user'
verbose_name = ugettext_lazy('Participants')
required_permission = 'core.can_manage_projector'
default_column = 1
default_weight = 60
default_active = False
template_name = 'participant/widget_user.html'
more_link_pattern_name = 'user_overview'
def get_context_data(self, **context):
return super(UserWidget, self).get_context_data(
users=User.objects.all(),
**context)
class GroupWidget(Widget):
"""
Provides a widget with all groups. This is for short activation of
group slides.
"""
name = 'group'
verbose_name = ugettext_lazy('Groups')
required_permission = 'core.can_manage_projector'
default_column = 1
default_weight = 70
default_active = False
template_name = 'participant/widget_group.html'
more_link_pattern_name = 'user_group_overview'
def get_context_data(self, **context):
return super(GroupWidget, self).get_context_data(
groups=Group.objects.all(),
**context)

View File

@ -23,7 +23,7 @@ urlpatterns += patterns(
(r'^agenda/', include('openslides.agenda.urls')),
(r'^motion/', include('openslides.motion.urls')),
(r'^assignment/', include('openslides.assignment.urls')),
(r'^participant/', include('openslides.participant.urls')),
(r'^user/', include('openslides.users.urls')),
(r'^mediafile/', include('openslides.mediafile.urls')),
(r'^config/', include('openslides.config.urls')),
(r'^projector/', include('openslides.projector.urls')),
@ -32,12 +32,13 @@ urlpatterns += patterns(
)
# TODO: move this patterns into core or the participant app
from openslides.users.views import UserSettingsView, UserPasswordSettingsView
urlpatterns += patterns(
'',
(r'^jsi18n/$', 'django.views.i18n.javascript_catalog', js_info_dict),
url(r'^login/$',
'openslides.participant.views.login',
'openslides.users.views.login',
name='user_login'),
url(r'^logout/$',
@ -45,11 +46,11 @@ urlpatterns += patterns(
name='user_logout'),
url(r'^usersettings/$',
'openslides.participant.views.user_settings',
UserSettingsView.as_view(),
name='user_settings'),
url(r'^usersettings/changepassword/$',
'openslides.participant.views.user_settings_password',
UserPasswordSettingsView.as_view(),
name='password_change'),
)

View File

@ -1,6 +1,9 @@
from random import choice
from .models import Group, User
from django.contrib.auth.models import Permission, Group
from django.contrib.contenttypes.models import ContentType
from .models import User
def gen_password():
@ -62,3 +65,13 @@ def create_or_reset_admin_user():
admin.save()
admin.groups.add(group_staff)
return created
def get_protected_perm():
"""
Returns the permission to manage users. This function is a helper
function used to protect manager users from locking out themselves.
"""
return Permission.objects.get(
content_type=ContentType.objects.get(app_label='users', model='user'),
codename='can_manage')

View File

@ -63,6 +63,7 @@ def import_users(csvfile):
error_messages.append(_('Group id %(id)s does not exists (line %(line)d).') % {'id': groupid, 'line': line_no + 1})
continue
user.reset_password()
user.save()
count_success += 1
except csv.Error:
error_messages.append(_('Import aborted because of severe errors in the input file.'))
@ -71,7 +72,7 @@ def import_users(csvfile):
# Build final success message
if count_success:
success_message = _('%d new participants were successfully imported.') % count_success
success_message = _('%d new users were successfully imported.') % count_success
else:
success_message = ''

View File

@ -8,76 +8,70 @@ from openslides.config.api import config
from openslides.utils.forms import (CssClassMixin,
LocalizedModelMultipleChoiceField)
from .models import get_protected_perm, Group, User
from .models import Group, User
from .api import get_protected_perm
class UserCreateForm(CssClassMixin, forms.ModelForm):
groups = LocalizedModelMultipleChoiceField(
# Hide the built-in groups 'Anonymous' (pk=1) and 'Registered' (pk=2)
queryset=Group.objects.exclude(pk=1).exclude(pk=2),
queryset=Group.objects.exclude(pk__in=[1, 2]),
label=ugettext_lazy('Groups'), required=False)
class Meta:
model = User
fields = ('title', 'first_name', 'last_name', 'gender', 'email',
'groups', 'structure_level', 'committee', 'about_me', 'comment',
'is_active', 'default_password')
fields = ('title', 'first_name', 'last_name', 'groups',
'structure_level', 'about_me', 'comment', 'is_active',
'default_password')
def clean(self, *args, **kwargs):
"""
Ensures that a user has either a first name or a last name.
"""
cleaned_data = super(UserCreateForm, self).clean(*args, **kwargs)
if not cleaned_data['first_name'] and not cleaned_data['last_name']:
if not (cleaned_data['first_name'] or cleaned_data['last_name']):
error_msg = _('First name and last name can not both be empty.')
raise forms.ValidationError(error_msg)
return cleaned_data
class UserMultipleCreateForm(forms.Form):
participants_block = forms.CharField(
users_block = forms.CharField(
widget=forms.Textarea,
label=ugettext_lazy('Participants'),
help_text=ugettext_lazy('Use one line per participant for its name (first name and last name).'))
label=ugettext_lazy('Users'),
help_text=ugettext_lazy('Use one line per user for its name '
'(first name and last name).'))
class UserUpdateForm(UserCreateForm):
"""
Form to update an user. It raises a validation error, if a non-superuser
user edits himself and removes the last group containing the permission
to manage participants.
"""
user_name = forms.CharField(label=ugettext_lazy('Username'))
"""
Field to save the username.
The field username (without the underscore) from the UserModel does not
allow whitespaces and umlauts.
to manage users.
"""
class Meta:
model = User
fields = ('user_name', 'title', 'first_name', 'last_name', 'gender', 'email',
'groups', 'structure_level', 'committee', 'about_me', 'comment',
fields = ('username', 'title', 'first_name', 'last_name',
'groups', 'structure_level', 'about_me', 'comment',
'is_active', 'default_password')
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request')
kwargs['initial']['user_name'] = kwargs['instance'].username
return super(UserUpdateForm, self).__init__(*args, **kwargs)
def clean(self, *args, **kwargs):
"""
Raises a validation error if a non-superuser user edits himself
and removes the last group containing the permission to manage participants.
and removes the last group containing the permission to manage users.
"""
# TODO: Check this in clean_groups
if (self.request.user == self.instance and
not self.instance.is_superuser and
not self.cleaned_data['groups'].filter(permissions__in=[get_protected_perm()]).exists()):
error_msg = _('You can not remove the last group containing the permission to manage participants.')
error_msg = _('You can not remove the last group containing the permission to manage users.')
raise forms.ValidationError(error_msg)
return super(UserUpdateForm, self).clean(*args, **kwargs)
return super().clean(*args, **kwargs)
class GroupForm(forms.ModelForm, CssClassMixin):
@ -85,7 +79,7 @@ class GroupForm(forms.ModelForm, CssClassMixin):
queryset=Permission.objects.all(), label=ugettext_lazy('Permissions'),
required=False)
users = forms.ModelMultipleChoiceField(
queryset=User.objects.all(), label=ugettext_lazy('Participants'), required=False)
queryset=User.objects.all(), label=ugettext_lazy('Users'), required=False)
class Meta:
model = Group
@ -96,10 +90,10 @@ class GroupForm(forms.ModelForm, CssClassMixin):
# Initial users
if kwargs.get('instance', None) is not None:
initial = kwargs.setdefault('initial', {})
initial['users'] = [django_user.user.pk for django_user in kwargs['instance'].user_set.all()]
initial['users'] = kwargs['instance'].user_set.all()
super(GroupForm, self).__init__(*args, **kwargs)
if config['participant_sort_users_by_first_name']:
super().__init__(*args, **kwargs)
if config['users_sort_users_by_first_name']:
self.fields['users'].queryset = self.fields['users'].queryset.order_by('first_name')
def save(self, commit=True):
@ -124,10 +118,10 @@ class GroupForm(forms.ModelForm, CssClassMixin):
def clean(self, *args, **kwargs):
"""
Raises a validation error if a non-superuser user removes himself
from the last group containing the permission to manage participants.
from the last group containing the permission to manage users.
Raises also a validation error if a non-superuser removes his last
permission to manage participants from the (last) group.
permission to manage users from the (last) group.
"""
# TODO: Check this in clean_users or clean_permissions
if (self.request and
@ -136,37 +130,24 @@ class GroupForm(forms.ModelForm, CssClassMixin):
not Group.objects.exclude(pk=self.instance.pk).filter(
permissions__in=[get_protected_perm()],
user__pk=self.request.user.pk).exists()):
error_msg = _('You can not remove yourself from the last group containing the permission to manage participants.')
error_msg = _('You can not remove yourself from the last group containing the permission to manage users.')
raise forms.ValidationError(error_msg)
if (self.request and
not self.request.user.is_superuser and
not get_protected_perm() in self.cleaned_data['permissions'] and
not Group.objects.exclude(pk=self.instance.pk).filter(
permissions__in=[get_protected_perm()],
user__pk=self.request.user.pk).exists()):
error_msg = _('You can not remove the permission to manage participants from the last group you are in.')
error_msg = _('You can not remove the permission to manage users from the last group you are in.')
raise forms.ValidationError(error_msg)
return super(GroupForm, self).clean(*args, **kwargs)
class UsersettingsForm(CssClassMixin, forms.ModelForm):
user_name = forms.CharField(label=ugettext_lazy('Username'))
"""
Field to save the username.
The field username (without the underscore) from the UserModel does not
allow whitespaces and umlauts.
"""
class Meta:
model = User
fields = ('username', 'title', 'first_name', 'last_name', 'about_me')
language = forms.ChoiceField(
choices=settings.LANGUAGES, label=ugettext_lazy('Language'))
def __init__(self, *args, **kwargs):
kwargs['initial'] = kwargs.get('initial', {})
kwargs['initial']['user_name'] = kwargs['instance'].username
return super(UsersettingsForm, self).__init__(*args, **kwargs)
class Meta:
model = User
fields = ('user_name', 'title', 'first_name', 'last_name', 'gender', 'email',
'committee', 'about_me')

View File

@ -3,12 +3,12 @@ from django.utils.translation import ugettext_lazy
from openslides.utils.main_menu import MainMenuEntry
class ParticipantMainMenuEntry(MainMenuEntry):
class UserMainMenuEntry(MainMenuEntry):
"""
Main menu entry for the participant app.
"""
verbose_name = ugettext_lazy('Participants')
required_permission = 'participant.can_see_participant'
verbose_name = ugettext_lazy('Users')
required_permission = 'users.can_see'
default_weight = 50
pattern_name = 'user_overview'
pattern_name = 'user_list'
icon_css_class = 'icon-user'

136
openslides/users/models.py Normal file
View File

@ -0,0 +1,136 @@
# TODO: Check every app, that they do not import Group or User from here.
from django.contrib.auth.models import (PermissionsMixin, AbstractBaseUser,
BaseUserManager)
# TODO: Do not import the Group in here, but in core.models (if necessary)
from django.contrib.auth.models import Group # noqa
from django.core.urlresolvers import reverse
from django.db import models
from django.utils.translation import ugettext_lazy, ugettext_noop
from openslides.projector.models import SlideMixin
from openslides.utils.models import AbsoluteUrlMixin
class UserManager(BaseUserManager):
def create_user(self, username, password, **kwargs):
user = self.model(username=username, **kwargs)
user.set_password(password)
user.save(using=self._db)
return user
class User(SlideMixin, AbsoluteUrlMixin, PermissionsMixin, AbstractBaseUser):
USERNAME_FIELD = 'username'
slide_callback_name = 'user'
username = models.CharField(
ugettext_lazy('Username'), max_length=255, unique=True)
first_name = models.CharField(
ugettext_lazy('First name'), max_length=255, blank=True)
last_name = models.CharField(
ugettext_lazy('Last name'), max_length=255, blank=True)
# TODO: try to remove the default argument in the following fields
structure_level = models.CharField(
max_length=255, blank=True, default='',
verbose_name=ugettext_lazy('Structure level'),
help_text=ugettext_lazy('Will be shown after the name.'))
title = models.CharField(
max_length=50, blank=True, default='',
verbose_name=ugettext_lazy('Title'),
help_text=ugettext_lazy('Will be shown before the name.'))
about_me = models.TextField(
blank=True, default='', verbose_name=ugettext_lazy('About me'),
help_text=ugettext_lazy('Your profile text'))
comment = models.TextField(
blank=True, default='', verbose_name=ugettext_lazy('Comment'),
help_text=ugettext_lazy('Only for notes.'))
default_password = models.CharField(
max_length=100, blank=True, default='',
verbose_name=ugettext_lazy('Default password'))
is_active = models.BooleanField(
ugettext_lazy('active'), default=True,
help_text=ugettext_lazy(
'Designates whether this user should be treated as '
'active. Unselect this instead of deleting accounts.'))
is_present = models.BooleanField(
ugettext_lazy('present'), default=False,
help_text=ugettext_lazy('Designates whether this user is in the room '
'or not.'))
objects = UserManager()
class Meta:
permissions = (
('can_see', ugettext_noop('Can see users')),
('can_manage', ugettext_noop('Can manage users')),
)
ordering = ('last_name',)
def __str__(self):
return self.get_full_name()
def get_absolute_url(self, link='detail'):
"""
Returns the URL to the user.
"""
if link == 'detail':
url = reverse('user_view', args=[str(self.pk)])
elif link == 'update':
url = reverse('user_edit', args=[str(self.pk)])
elif link == 'delete':
url = reverse('user_delete', args=[str(self.pk)])
else:
url = super().get_absolute_url(link)
return url
def get_slide_context(self, **context):
"""
Returns the context for the user slide.
"""
# Does not call super. In this case the context would override the name
# 'user'.
return {'shown_user': self}
def get_full_name(self):
"""
Returns a long form of the name.
E. g.: * Dr. Max Mustermann (Villingen)
* Professor Dr. Enders, Christoph (Leipzig)
"""
if self.structure_level:
structure = '(%s)' % self.structure_level
else:
structure = ''
return ' '.join((self.title, self.get_short_name(), structure)).strip()
def get_short_name(self):
"""
Returns only the name of the user.
E. g.: * Max Mustermann
* Enders, Christoph
"""
# TODO: Order of name. See config.
name = ('%s %s' % (self.first_name, self.last_name)).strip()
return name or self.username
def reset_password(self, password=None):
"""
Reset the password for the user to his default-password.
"""
if password is None:
password = self.default_password
self.set_password(password)

View File

@ -12,13 +12,13 @@ from openslides.utils.pdf import stylesheet
from .models import User
def participants_to_pdf(pdf):
def users_to_pdf(pdf):
"""
Create a list of all participants as PDF
Create a list of all users as PDF
"""
data = [['#', _('Title'), _('Last Name'), _('First Name'),
_('Structure level'), _('Group')]]
if config['participant_sort_users_by_first_name']:
if config['users_sort_users_by_first_name']:
sort = 'first_name'
else:
sort = 'last_name'
@ -48,30 +48,30 @@ def participants_to_pdf(pdf):
return pdf
def participants_passwords_to_pdf(pdf):
def users_passwords_to_pdf(pdf):
"""
Create access data sheets for all participants as PDF
Create access data sheets for all users as PDF
"""
participant_pdf_wlan_ssid = config["participant_pdf_wlan_ssid"] or "-"
participant_pdf_wlan_password = config["participant_pdf_wlan_password"] or "-"
participant_pdf_wlan_encryption = config["participant_pdf_wlan_encryption"] or "-"
participant_pdf_url = config["participant_pdf_url"] or "-"
participant_pdf_welcometitle = config["participant_pdf_welcometitle"]
participant_pdf_welcometext = config["participant_pdf_welcometext"]
if config['participant_sort_users_by_first_name']:
users_pdf_wlan_ssid = config["users_pdf_wlan_ssid"] or "-"
users_pdf_wlan_password = config["users_pdf_wlan_password"] or "-"
users_pdf_wlan_encryption = config["users_pdf_wlan_encryption"] or "-"
users_pdf_url = config["users_pdf_url"] or "-"
users_pdf_welcometitle = config["users_pdf_welcometitle"]
users_pdf_welcometext = config["users_pdf_welcometext"]
if config['users_sort_users_by_first_name']:
sort = 'first_name'
else:
sort = 'last_name'
qrcode_size = 2 * cm
# qrcode for system url
qrcode_url = QrCodeWidget(participant_pdf_url)
qrcode_url = QrCodeWidget(users_pdf_url)
qrcode_url.barHeight = qrcode_size
qrcode_url.barWidth = qrcode_size
qrcode_url.barBorder = 0
qrcode_url_draw = Drawing(45, 45)
qrcode_url_draw.add(qrcode_url)
# qrcode for wlan
text = "WIFI:S:%s;T:%s;P:%s;;" % (participant_pdf_wlan_ssid, participant_pdf_wlan_encryption, participant_pdf_wlan_password)
text = "WIFI:S:%s;T:%s;P:%s;;" % (users_pdf_wlan_ssid, users_pdf_wlan_encryption, users_pdf_wlan_password)
qrcode_wlan = QrCodeWidget(text)
qrcode_wlan.barHeight = qrcode_size
qrcode_wlan.barWidth = qrcode_size
@ -89,15 +89,15 @@ def participants_passwords_to_pdf(pdf):
stylesheet['h2']))
cell.append(Paragraph("%s:" % _("WLAN name (SSID)"),
stylesheet['formfield']))
cell.append(Paragraph(participant_pdf_wlan_ssid,
cell.append(Paragraph(users_pdf_wlan_ssid,
stylesheet['formfield_value']))
cell.append(Paragraph("%s:" % _("WLAN password"),
stylesheet['formfield']))
cell.append(Paragraph(participant_pdf_wlan_password,
cell.append(Paragraph(users_pdf_wlan_password,
stylesheet['formfield_value']))
cell.append(Paragraph("%s:" % _("WLAN encryption"),
stylesheet['formfield']))
cell.append(Paragraph(participant_pdf_wlan_encryption,
cell.append(Paragraph(users_pdf_wlan_encryption,
stylesheet['formfield_value']))
cell.append(Spacer(0, 0.5 * cm))
# OpenSlides access data
@ -114,17 +114,17 @@ def participants_passwords_to_pdf(pdf):
stylesheet['formfield_value']))
cell2.append(Paragraph("URL:",
stylesheet['formfield']))
cell2.append(Paragraph(participant_pdf_url,
cell2.append(Paragraph(users_pdf_url,
stylesheet['formfield_value']))
data.append([cell, cell2])
# QRCodes
cell = []
if participant_pdf_wlan_ssid != "-" and participant_pdf_wlan_encryption != "-":
if users_pdf_wlan_ssid != "-" and users_pdf_wlan_encryption != "-":
cell.append(qrcode_wlan_draw)
cell.append(Paragraph(_("Scan this QRCode to connect WLAN."),
stylesheet['qrcode_comment']))
cell2 = []
if participant_pdf_url != "-":
if users_pdf_url != "-":
cell2.append(qrcode_url_draw)
cell2.append(Paragraph(_("Scan this QRCode to open URL."),
stylesheet['qrcode_comment']))
@ -138,8 +138,8 @@ def participants_passwords_to_pdf(pdf):
pdf.append(Spacer(0, 2 * cm))
# welcome title and text
pdf.append(Paragraph(participant_pdf_welcometitle, stylesheet['h2']))
pdf.append(Paragraph(participant_pdf_welcometext.replace('\r\n', '<br/>'),
pdf.append(Paragraph(users_pdf_welcometitle, stylesheet['h2']))
pdf.append(Paragraph(users_pdf_welcometext.replace('\r\n', '<br/>'),
stylesheet['Paragraph12']))
pdf.append(PageBreak())
return pdf

View File

@ -5,8 +5,8 @@ from .models import User
class Index(indexes.SearchIndex, indexes.Indexable):
text = indexes.EdgeNgramField(document=True, use_template=True)
text = indexes.EdgeNgramField(document=True, use_template=True)
modelfilter_name = "Participants" # verbose_name of model
modelfilter_value = "participant.user" # 'app_name.model_name'
modelfilter_name = "Users" # verbose_name of model
modelfilter_value = "users.user" # 'app_name.model_name'
def get_model(self):
return User

View File

@ -4,36 +4,37 @@ from django.contrib.contenttypes.models import ContentType
from django.dispatch import receiver
from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_lazy, ugettext_noop
from django.db.models import signals
from openslides.config.api import ConfigGroup, ConfigGroupedCollection, ConfigVariable
from openslides.config.signals import config_signal
from openslides.core.signals import post_database_setup
from .api import create_or_reset_admin_user
from .models import Group
from .models import Group, User
@receiver(config_signal, dispatch_uid='setup_participant_config')
def setup_participant_config(sender, **kwargs):
@receiver(config_signal, dispatch_uid='setup_users_config')
def setup_users_config(sender, **kwargs):
"""
Participant config variables.
"""
# General
participant_sort_users_by_first_name = ConfigVariable(
name='participant_sort_users_by_first_name',
users_sort_users_by_first_name = ConfigVariable(
name='users_sort_users_by_first_name',
default_value=False,
form_field=forms.BooleanField(
required=False,
label=ugettext_lazy('Sort participants by first name'),
label=ugettext_lazy('Sort users by first name'),
help_text=ugettext_lazy('Disable for sorting by last name')))
group_general = ConfigGroup(
title=ugettext_lazy('Sorting'),
variables=(participant_sort_users_by_first_name,))
variables=(users_sort_users_by_first_name,))
# PDF
participant_pdf_welcometitle = ConfigVariable(
name='participant_pdf_welcometitle',
users_pdf_welcometitle = ConfigVariable(
name='users_pdf_welcometitle',
default_value=_('Welcome to OpenSlides!'),
translatable=True,
form_field=forms.CharField(
@ -41,8 +42,8 @@ def setup_participant_config(sender, **kwargs):
required=False,
label=ugettext_lazy('Title for access data and welcome PDF')))
participant_pdf_welcometext = ConfigVariable(
name='participant_pdf_welcometext',
users_pdf_welcometext = ConfigVariable(
name='users_pdf_welcometext',
default_value=_('[Place for your welcome and help text.]'),
translatable=True,
form_field=forms.CharField(
@ -50,8 +51,8 @@ def setup_participant_config(sender, **kwargs):
required=False,
label=ugettext_lazy('Help text for access data and welcome PDF')))
participant_pdf_url = ConfigVariable(
name='participant_pdf_url',
users_pdf_url = ConfigVariable(
name='users_pdf_url',
default_value='http://example.com:8000',
form_field=forms.CharField(
widget=forms.TextInput(),
@ -59,8 +60,8 @@ def setup_participant_config(sender, **kwargs):
label=ugettext_lazy('System URL'),
help_text=ugettext_lazy('Used for QRCode in PDF of access data.')))
participant_pdf_wlan_ssid = ConfigVariable(
name='participant_pdf_wlan_ssid',
users_pdf_wlan_ssid = ConfigVariable(
name='users_pdf_wlan_ssid',
default_value='',
form_field=forms.CharField(
widget=forms.TextInput(),
@ -68,8 +69,8 @@ def setup_participant_config(sender, **kwargs):
label=ugettext_lazy('WLAN name (SSID)'),
help_text=ugettext_lazy('Used for WLAN QRCode in PDF of access data.')))
participant_pdf_wlan_password = ConfigVariable(
name='participant_pdf_wlan_password',
users_pdf_wlan_password = ConfigVariable(
name='users_pdf_wlan_password',
default_value='',
form_field=forms.CharField(
widget=forms.TextInput(),
@ -77,8 +78,8 @@ def setup_participant_config(sender, **kwargs):
label=ugettext_lazy('WLAN password'),
help_text=ugettext_lazy('Used for WLAN QRCode in PDF of access data.')))
participant_pdf_wlan_encryption = ConfigVariable(
name='participant_pdf_wlan_encryption',
users_pdf_wlan_encryption = ConfigVariable(
name='users_pdf_wlan_encryption',
default_value='',
form_field=forms.ChoiceField(
widget=forms.Select(),
@ -93,21 +94,21 @@ def setup_participant_config(sender, **kwargs):
group_pdf = ConfigGroup(
title=ugettext_lazy('PDF'),
variables=(participant_pdf_welcometitle,
participant_pdf_welcometext,
participant_pdf_url,
participant_pdf_wlan_ssid,
participant_pdf_wlan_password,
participant_pdf_wlan_encryption))
variables=(users_pdf_welcometitle,
users_pdf_welcometext,
users_pdf_url,
users_pdf_wlan_ssid,
users_pdf_wlan_password,
users_pdf_wlan_encryption))
return ConfigGroupedCollection(
title=ugettext_noop('Participant'),
url='participant',
title=ugettext_noop('Users'),
url='users',
weight=50,
groups=(group_general, group_pdf))
@receiver(post_database_setup, dispatch_uid='participant_create_builtin_groups_and_admin')
@receiver(post_database_setup, dispatch_uid='users_create_builtin_groups_and_admin')
def create_builtin_groups_and_admin(sender, **kwargs):
"""
Creates the buildin groups and the admin user.
@ -138,8 +139,8 @@ def create_builtin_groups_and_admin(sender, **kwargs):
ct_assignment = ContentType.objects.get(app_label='assignment', model='assignment')
perm_16 = Permission.objects.get(content_type=ct_assignment, codename='can_see_assignment')
ct_participant = ContentType.objects.get(app_label='participant', model='user')
perm_17 = Permission.objects.get(content_type=ct_participant, codename='can_see_participant')
ct_users = ContentType.objects.get(app_label='users', model='user')
perm_17 = Permission.objects.get(content_type=ct_users, codename='can_see')
ct_mediafile = ContentType.objects.get(app_label='mediafile', model='mediafile')
perm_18 = Permission.objects.get(content_type=ct_mediafile, codename='can_see')
@ -163,7 +164,7 @@ def create_builtin_groups_and_admin(sender, **kwargs):
perm_41 = Permission.objects.get(content_type=ct_agenda, codename='can_manage_agenda')
perm_42 = Permission.objects.get(content_type=ct_motion, codename='can_manage_motion')
perm_43 = Permission.objects.get(content_type=ct_assignment, codename='can_manage_assignment')
perm_44 = Permission.objects.get(content_type=ct_participant, codename='can_manage_participant')
perm_44 = Permission.objects.get(content_type=ct_users, codename='can_manage')
perm_45 = Permission.objects.get(content_type=ct_core, codename='can_manage_projector')
perm_46 = Permission.objects.get(content_type=ct_core, codename='can_use_chat')
perm_47 = Permission.objects.get(content_type=ct_mediafile, codename='can_manage')
@ -176,8 +177,19 @@ def create_builtin_groups_and_admin(sender, **kwargs):
group_staff.permissions.add(perm_31, perm_33, perm_34, perm_35)
# add staff permissions
group_staff.permissions.add(perm_41, perm_42, perm_43, perm_44, perm_45, perm_46, perm_47, perm_48)
# add can_see_participant permission
# add can_see_user permission
group_staff.permissions.add(perm_17) # TODO: Remove this redundancy after cleanup of the permission system
# Admin user
create_or_reset_admin_user()
@receiver(signals.post_save, sender=User)
def user_post_save(sender, instance, *args, **kwargs):
if not kwargs['created']:
return
from openslides.users.api import get_registered_group # TODO: Test, if global import is possible
registered = get_registered_group()
instance.groups.add(registered)
instance.save()

View File

@ -1,6 +1,5 @@
from openslides.projector.api import register_slide_model
from .models import Group, User
from .models import User
register_slide_model(User, 'participant/user_slide.html')
register_slide_model(Group, 'participant/group_slide.html')

View File

@ -1,10 +1,10 @@
{% load i18n %}
{% load highlight %}
{% if perms.participant.can_see_participant %}
{% if perms.users.can_see %}
<li>
<a href="{{ result.object.get_absolute_url }}">{{ result.object }}</a><br>
<span class="app">{% trans "Participant" %}</a></span><br>
<span class="app">{% trans "User" %}</a></span><br>
{% highlight result.text with request.GET.q %}
</li>
{% endif %}

View File

@ -0,0 +1,55 @@
{% extends "base.html" %}
{% load i18n %}
{% load tags %}
{% block title %}{% trans group.name %} {{ block.super }}{% endblock %}
{% block content %}
<h1>{% trans group.name %}
<small class="pull-right">
<a href="{% url 'group_list' %}" class="btn btn-mini">
<i class="icon-chevron-left"></i>
<span class="optional-small">{% trans "Back to overview" %}</span>
</a>
{% if perms.users.can_manage and group.pk != 1 and group.pk != 2 %}
<div class="btn-group">
<a data-toggle="dropdown" class="btn btn-mini dropdown-toggle">
<span class="optional-small">{% trans 'More actions' %} </span>
<span class="caret"></span>
</a>
<ul class="dropdown-menu pull-right">
<!-- edit -->
<li>
<a href="{% url 'group_update' group.id %}">
<i class="icon-pencil"></i>
{% trans 'Edit group' %}
</a>
</li>
<!-- delete -->
<li>
<a href="{% url 'group_delete' group.id %}">
<i class="icon-remove"></i>
{% trans 'Delete group' %}
</a>
</li>
</ul>
</div>
{% endif %}
</small>
</h1>
<p>{{ group.description }}</p>
<h4>{% trans "Members" %}</h4>
<ol>
{% for member in group_members %}
<li><a href="{{ member|absolute_url }}">{{ member }}</a></li>
{% empty %}
<p>{% trans "No members available." %}</p>
{% endfor %}
</ol>
{% endblock %}

View File

@ -19,7 +19,10 @@
{% trans "New group" %}
{% endif %}
<small class="pull-right">
<a href="{% url 'user_group_overview' %}" class="btn btn-mini"><i class="icon-chevron-left"></i> {% trans "Back to overview" %}</a>
<a href="{% url 'group_list' %}" class="btn btn-mini">
<i class="icon-chevron-left"></i>
{% trans "Back to overview" %}
</a>
</small>
</h1>
@ -27,7 +30,7 @@
{% include "form.html" %}
<p>
{% include "formbuttons_saveapply.html" %}
<a href="{% url 'user_group_overview' %}" class="btn">
<a href="{% url 'group_list' %}" class="btn">
{% trans 'Cancel' %}
</a>
</p>

View File

@ -19,8 +19,16 @@
<h1>
{% trans "Groups" %}
<small class="pull-right">
<a href="{% url 'user_group_new' %}" class="btn btn-mini btn-primary" rel="tooltip" data-original-title="{% trans 'New group' %}"><i class="icon-plus icon-white"></i> {% trans "New" %}</a>
<a href="{% url 'user_overview' %}" class="btn btn-mini"><i class="icon-chevron-left"></i><span class="optional-small"> {% trans "Back to participants overview" %}</span></a>
<a href="{% url 'group_create' %}" class="btn btn-mini btn-primary" rel="tooltip" data-original-title="{% trans 'New group' %}">
<i class="icon-plus icon-white"></i>
{% trans "New" %}
</a>
<a href="{% url 'user_list' %}" class="btn btn-mini">
<i class="icon-chevron-left"></i>
<span class="optional-small">
{% trans "Back to users overview" %}
</span>
</a>
</small>
</h1>
@ -37,29 +45,22 @@
<tr class="{% if group.is_active_slide %}activeline{% endif %}">
<td class="nobr">{{ group.pk }}
{% if group.pk == 1 or group.pk == 2 %}
<a title="{% blocktrans %}The groups 1 ('Anonymous') and 2 ('Registered') are fixed default groups which can not be deleted. Each created or imported participant is a member of group 2. Use custom groups to set additional permissions for a subset of participants.{% endblocktrans %}"><i class="icon-info-sign"></i></a>
<a title="{% blocktrans %}The groups 1 ('Anonymous') and 2 ('Registered') are fixed default groups which can not be deleted. Each created or imported user is a member of group 2. Use custom groups to set additional permissions for a subset of users.{% endblocktrans %}"><i class="icon-info-sign"></i></a>
{% endif %}
</td>
<td>
<a href="{{ group|absolute_url }}">{% trans group.name %}</a>
<a href="{% url 'group_detail' group.pk %}">{% trans group.name %}</a>
</td>
<td>
<span class="badge badge-info">{{ group.user_set.all.count }}</span>
</td>
<td>
<span style="width: 1px; white-space: nowrap;">
{% if perms.core.can_manage_projector %}
<a href="{{ group|absolute_url:'projector' }}"
class="activate_link btn {% if group.is_active_slide %}btn-primary{% endif %} btn-mini"
rel="tooltip" data-original-title="{% trans 'Show group' %}">
<i class="icon-facetime-video {% if group.is_active_slide %}icon-white{% endif %}"></i>
</a>
{% endif %}
<a href="{% url 'user_group_edit' group.id %}" title="{% trans 'Edit' %}" class="btn btn-mini">
<a href="{% url 'group_update' group.id %}" title="{% trans 'Edit' %}" class="btn btn-mini">
<i class="icon-pencil"></i>
</a>
{% if group.pk != 1 and group.pk != 2 %}
<a href="{% url 'user_group_delete' group.id %}" title="{% trans 'Delete' %}" class="btn btn-mini">
<a href="{% url 'group_delete' group.id %}" title="{% trans 'Delete' %}" class="btn btn-mini">
<i class="icon-remove"></i>
</a>
{% endif %}

View File

@ -9,23 +9,33 @@
<h1>
{{ shown_user.clean_name }}
<small class="pull-right">
<a href="{% url 'user_overview' %}" class="btn btn-mini"><i class="icon-chevron-left"></i><span class="optional-small"> {% trans "Back to overview" %}</span></a>
<a href="{% url 'user_list' %}" class="btn btn-mini">
<i class="icon-chevron-left"></i>
<span class="optional-small">
{% trans "Back to overview" %}
</span>
</a>
<!-- activate projector -->
{% if perms.core.can_manage_projector %}
<a href="{% url 'projector_activate_slide' shown_user.sid %}" class="activate_link btn {% if shown_user.active %}btn-primary{% endif %} btn-mini" rel="tooltip" data-original-title="{% trans 'Show participant' %}">
<a href="{% url 'projector_activate_slide' shown_user.sid %}" class="activate_link btn {% if shown_user.active %}btn-primary{% endif %} btn-mini" rel="tooltip" data-original-title="{% trans 'Show users' %}">
<i class="icon-facetime-video {% if shown_user.active %}icon-white{% endif %}"></i>
</a>
{% endif %}
{% if perms.participant.can_manage_participant %}
{% if perms.users.can_manage %}
<div class="btn-group">
<a data-toggle="dropdown" class="btn btn-mini dropdown-toggle">
<span class="optional-small">{% trans 'More actions' %} </span><span class="caret"></span>
</a>
<ul class="dropdown-menu pull-right">
<!-- edit -->
<li><a href="{% url 'user_edit' shown_user.id %}"><i class="icon-pencil"></i> {% trans 'Edit participant' %}</a></li>
<li>
<a href="{% url 'user_edit' shown_user.id %}">
<i class="icon-pencil"></i>
{% trans 'Edit user' %}
</a>
</li>
<!-- delete -->
<li><a href="{% url 'user_delete' shown_user.id %}"><i class="icon-remove"></i> {% trans 'Delete participant' %}</a></li>
<li><a href="{% url 'user_delete' shown_user.id %}"><i class="icon-remove"></i> {% trans 'Delete user' %}</a></li>
</ul>
</div>
{% endif %}
@ -59,11 +69,11 @@
{% endif %}
{% endfor %}
{% else %}
{% trans "The participant is not member of any group." %}
{% trans "The user is not member of any group." %}
{% endif %}
</fieldset>
{% if perms.participant.can_manage_participant %}
{% if perms.users.can_manage %}
<fieldset>
<legend>{% trans "Administrative data" %}</legend>
<label>{% trans "Username" %}</label>
@ -74,7 +84,7 @@
{% if shown_user.last_login > shown_user.date_joined %}
{{ shown_user.last_login }}
{% else %}
{% trans "The participant has not logged in yet." %}
{% trans "The user has not logged in yet." %}
{% endif %}
{% endif %}
</div>

View File

@ -24,9 +24,9 @@
{% block title %}
{% if edit_user %}
{% trans "Edit participant" %}
{% trans "Edit user" %}
{% else %}
{% trans "New participant" %}
{% trans "New user" %}
{% endif %}
{{ block.super }}
{% endblock %}
@ -34,12 +34,15 @@
{% block content %}
<h1>
{% if edit_user %}
{% trans "Edit participant" %}
{% trans "Edit user" %}
{% else %}
{% trans "New participant" %}
{% trans "New user" %}
{% endif %}
<small class="pull-right">
<a href="{% url 'user_overview' %}" class="btn btn-mini"><i class="icon-chevron-left"></i> {% trans "Back to overview" %}</a>
<a href="{% url 'user_list' %}" class="btn btn-mini">
<i class="icon-chevron-left"></i>
{% trans "Back to overview" %}
</a>
</small>
</h1>
@ -47,12 +50,15 @@
{% include "form.html" %}
{% if edit_user %}
<p style="margin: -15px 0 25px 0;">
<a class="btn btn-mini" href="{% url 'user_reset_password' edit_user.id %}"><i class="icon-exclamation-sign"></i> {% trans 'Reset to First Password' %}</a>
<a class="btn btn-mini" href="{% url 'user_reset_password' edit_user.id %}">
<i class="icon-exclamation-sign"></i>
{% trans 'Reset to First Password' %}
</a>
</p>
{% endif %}
<p>
{% include "formbuttons_saveapply.html" %}
<a href="{% url 'user_overview' %}" class="btn">
<a href="{% url 'user_list' %}" class="btn">
{% trans 'Cancel' %}
</a>
</p>

View File

@ -2,18 +2,21 @@
{% load i18n %}
{% block title %}{% trans "Import participants" %} {{ block.super }}{% endblock %}
{% block title %}{% trans "Import users" %} {{ block.super }}{% endblock %}
{% block content %}
<h1>
{% trans 'Import participants' %}
{% trans 'Import users' %}
<small class="pull-right">
<a href="{% url 'user_overview' %}" class="btn btn-mini"><i class="icon-chevron-left"></i> {% trans "Back to overview" %}</a>
<a href="{% url 'user_list' %}" class="btn btn-mini">
<i class="icon-chevron-left"></i>
{% trans "Back to overview" %}
</a>
</small>
</h1>
<p>{% trans 'Select a CSV file to import participants' %}.</p>
<p>{% trans 'Select a CSV file to import users' %}.</p>
<p>{% trans 'Please note' %}:</p>
<ul>
<li>
@ -40,7 +43,7 @@
<button class="btn btn-primary" type="submit">
<span class="icon import">{% trans 'Import' %}</span>
</button>
<a href="{% url 'user_overview' %}" class="btn">
<a href="{% url 'user_list' %}" class="btn">
{% trans 'Cancel' %}
</a>
</p>

View File

@ -3,14 +3,14 @@
{% load i18n %}
{% block title %}
{% trans 'New multiple participants' %} {{ block.super }}
{% trans 'New multiple users' %} {{ block.super }}
{% endblock %}
{% block content %}
<h1>
{% trans 'New multiple participants' %}
{% trans 'New multiple users' %}
<small class="pull-right">
<a href="{% url 'user_overview' %}" class="btn btn-mini">
<a href="{% url 'user_list' %}" class="btn btn-mini">
<i class="icon-chevron-left"></i> {% trans 'Back to overview' %}
</a>
</small>
@ -19,7 +19,7 @@
{% include 'form.html' %}
<p>
{% include 'formbuttons_save.html' %}
<a href="{% url 'user_overview' %}" class="btn">
<a href="{% url 'user_list' %}" class="btn">
{% trans 'Cancel' %}
</a>
</p>

View File

@ -4,7 +4,7 @@
{% load staticfiles %}
{% load tags %}
{% block title %}{% trans "Participants" %} {{ block.super }}{% endblock %}
{% block title %}{% trans "Users" %} {{ block.super }}{% endblock %}
{% block header %}
<link href="{% static 'css/dataTables/dataTables.bootstrap.css' %}" type="text/css" rel="stylesheet">
@ -17,27 +17,51 @@
{% block content %}
<h1>
{% trans "Participants" %}
{% trans "Users" %}
<small class="pull-right">
{% if perms.participant.can_manage_participant %}
<a href="{% url 'user_new' %}" class="btn btn-mini btn-primary" rel="tooltip" data-original-title="{% trans 'New participant' %}"><i class="icon-plus icon-white"></i> {% trans "New" %}</a>
<a href="{% url 'user_new_multiple' %}" class="btn btn-mini" rel="tooltip" data-original-title="{% trans 'New multiple participants' %}"><i class="icon-plus"></i> {% trans 'New multiple' %}</a>
<a href="{% url 'user_group_overview' %}" class="btn btn-mini" rel="tooltip" data-original-title="{% trans 'All groups' %}"><i class="icon-group"></i><span class="optional-small"> {% trans "Groups" %}</span></a>
<a href="{% url 'user_csv_import' %}" class="btn btn-mini" rel="tooltip" data-original-title="{% trans 'Import participants' %}"><i class="icon-import"></i><span class="optional-small"> {% trans 'Import' %}</span></a>
{% if perms.users.can_manage %}
<a href="{% url 'user_new' %}" class="btn btn-mini btn-primary" rel="tooltip" data-original-title="{% trans 'New user' %}">
<i class="icon-plus icon-white"></i>
{% trans "New" %}
</a>
<a href="{% url 'user_new_multiple' %}" class="btn btn-mini" rel="tooltip" data-original-title="{% trans 'New multiple users' %}">
<i class="icon-plus"></i>
{% trans 'New multiple' %}
</a>
<a href="{% url 'group_list' %}" class="btn btn-mini" rel="tooltip" data-original-title="{% trans 'All groups' %}">
<i class="icon-group"></i>
<span class="optional-small">{% trans "Groups" %}</span>
</a>
<a href="{% url 'user_csv_import' %}" class="btn btn-mini" rel="tooltip" data-original-title="{% trans 'Import users' %}">
<i class="icon-import"></i>
<span class="optional-small"> {% trans 'Import' %}</span>
</a>
{% endif %}
{% if perms.participant.can_see_participant and perms.participant.can_manage_participant %}
{% if perms.users.can_see and perms.users.can_manage %}
<div class="btn-group">
<a data-toggle="dropdown" class="btn btn-mini dropdown-toggle">
<i class="icon-print"></i><span class="optional-small"> PDF</span> <span class="caret"></span>
<i class="icon-print"></i>
<span class="optional-small"> PDF</span>
<span class="caret"></span>
</a>
<ul class="dropdown-menu pull-right">
{% url 'user_settings' as url_usersettings %}
<li><a href="{% url 'user_print' %}" target="_blank"><i class="icon-list"></i> {% trans 'List of participants' %}</a></li>
<li><a href="{% url 'print_passwords' %}" target="_blank"><i class="icon-th-large"></i> {% trans 'List of access data' %}</a></li>
<li>
<a href="{% url 'user_print' %}" target="_blank">
<i class="icon-list"></i>
{% trans 'List of users' %}
</a>
</li>
<li>
<a href="{% url 'print_passwords' %}" target="_blank">
<i class="icon-th-large"></i>
{% trans 'List of access data' %}
</a>
</li>
</ul>
</div>
{% else %}
<a href="{% url 'user_print' %}" class="btn btn-mini" rel="tooltip" data-original-title="{% trans 'Print list of participants as PDF' %}" target="_blank"><i class="icon-print"></i> PDF</a>
<a href="{% url 'user_print' %}" class="btn btn-mini" rel="tooltip" data-original-title="{% trans 'Print list of users as PDF' %}" target="_blank"><i class="icon-print"></i> PDF</a>
{% endif %}
</small>
</h1>
@ -51,7 +75,7 @@
<th class="optional">{% trans "Structure level" %}</th>
<th class="optional">{% trans "Group" %}</th>
<th class="optional">{% trans "Committee" %}</th>
{% if perms.participant.can_manage_participant %}
{% if perms.users.can_manage %}
<th class="optional">{% trans "Comment" %}</th>
<th class="optional">{% trans "Last Login" %}</th>
<th class="mini_width">{% trans "Actions" %}</th>
@ -61,7 +85,7 @@
<tbody>
{% for user in users %}
<tr class="{% if user.is_active_slide %}activeline{% endif %}">
<td>{% if perms.participant.can_manage_participant %}
<td>{% if perms.users.can_manage %}
{% if user != request_user %}
<a href="{% url 'user_status_toggle' user.id %}"
class="status_link btn btn-mini {% if user.is_active %}btn-success{% endif %}"
@ -77,7 +101,7 @@
</td>
<td class="optional">{{ user.title }}</td>
<td>
{% if 'participant_sort_users_by_first_name'|get_config %}
{% if 'users_sort_users_by_first_name'|get_config %}
<a href="{% url 'user_view' user.id %}">{{ user.first_name }} {{ user.last_name }}</a>
{% else %}
<a href="{% url 'user_view' user.id %}">{{ user.last_name }}{% if user.last_name and user.first_name %},{% endif %} {{ user.first_name }}</a>
@ -93,7 +117,7 @@
{% endfor %}
</td>
<td class="optional">{{ user.committee }}</td>
{% if perms.participant.can_manage_participant %}
{% if perms.users.can_manage %}
<td class="optional">
{% if user.comment %}
<a class="btn btn-mini" rel="popover" data-content="{{ user.comment|linebreaks }}">
@ -110,7 +134,7 @@
<span style="width: 1px; white-space: nowrap;">
{% if perms.core.can_manage_projector %}
<a href="{{ user|absolute_url:'projector' }}" class="activate_link btn {% if user.is_active_slide %}btn-primary{% endif %} btn-mini"
rel="tooltip" data-original-title="{% trans 'Show participant' %}">
rel="tooltip" data-original-title="{% trans 'Show user' %}">
<i class="icon-facetime-video {% if user.is_active_slide %}icon-white{% endif %}"></i>
</a>
{% endif %}

View File

@ -11,7 +11,7 @@
rel="tooltip" data-original-title="{% trans 'Show' %}">
<i class="icon-facetime-video {% if user.is_active_slide %}icon-white{% endif %}"></i>
</a>&nbsp;
{% if perms.participant.can_manage_participant %}
{% if perms.users.can_manage %}
<a href="{{ user|absolute_url:'update' }}"
rel="tooltip" data-original-title="{% trans 'Edit' %}" class="btn btn-mini right">
<i class="icon-pencil"></i>
@ -24,7 +24,7 @@
<a href="{{ user|absolute_url }}">{{ user }}</a>
</li>
{% empty %}
<li>{% trans 'No participants available.' %}</li>
<li>{% trans 'No users available.' %}</li>
{% endfor %}
</ul>
{% endblock %}

View File

@ -6,8 +6,8 @@ urlpatterns = patterns(
'',
# User
url(r'^$',
views.UserOverview.as_view(),
name='user_overview'), # TODO: Rename this to user_list
views.UserListView.as_view(),
name='user_list'),
url(r'^new/$',
views.UserCreateView.as_view(),
@ -54,31 +54,31 @@ urlpatterns = patterns(
# Group
url(r'^group/$',
views.GroupOverview.as_view(),
name='user_group_overview'), # TODO: Rename this to group_list
views.GroupListView.as_view(),
name='group_list'),
url(r'^group/new/$',
views.GroupCreateView.as_view(),
name='user_group_new'),
name='group_create'),
url(r'^group/(?P<pk>\d+)/$',
views.GroupDetailView.as_view(),
name='user_group_view'),
name='group_detail'),
url(r'^group/(?P<pk>\d+)/edit/$',
views.GroupUpdateView.as_view(),
name='user_group_edit'),
name='group_update'),
url(r'^group/(?P<pk>\d+)/del/$',
views.GroupDeleteView.as_view(),
name='user_group_delete'),
name='group_delete'),
# PDF
url(r'^print/$',
views.ParticipantsListPDF.as_view(),
views.UsersListPDF.as_view(),
name='user_print'),
url(r'^passwords/print/$',
views.ParticipantsPasswordsPDF.as_view(),
views.UsersPasswordsPDF.as_view(),
name='print_passwords'),
)

View File

@ -1,47 +1,43 @@
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.contrib.auth.forms import PasswordChangeForm
from django.contrib.auth.hashers import make_password
from django.contrib.auth.views import login as django_login
from django.core.urlresolvers import reverse
from django.shortcuts import redirect
from django.utils.translation import ugettext as _
from django.utils.translation import activate, ugettext_lazy
from django.utils.translation import ugettext as _, ugettext_lazy, activate
from openslides.config.api import config
from openslides.utils.utils import (delete_default_permissions, html_strong,
template)
from openslides.utils.views import (CreateView, CSVImportView, DeleteView, DetailView,
FormView, ListView, PDFView,
PermissionMixin, QuestionView,
RedirectView, SingleObjectMixin, UpdateView)
from openslides.utils.utils import delete_default_permissions, html_strong
from openslides.utils.views import (
CreateView, CSVImportView, DeleteView, DetailView, FormView, ListView,
PDFView, PermissionMixin, QuestionView, RedirectView, SingleObjectMixin,
UpdateView, LoginMixin)
from openslides.utils.exceptions import OpenSlidesError
from .api import gen_password, gen_username
from .api import gen_password, gen_username, get_protected_perm
from .csv_import import import_users
from .forms import (GroupForm, UserCreateForm, UserMultipleCreateForm,
UsersettingsForm, UserUpdateForm)
from .models import get_protected_perm, Group, User
from .pdf import participants_to_pdf, participants_passwords_to_pdf
from .models import Group, User
from .pdf import users_to_pdf, users_passwords_to_pdf
class UserOverview(ListView):
class UserListView(ListView):
"""
Show all participants (users).
Show all users.
"""
required_permission = 'participant.can_see_participant'
template_name = 'participant/overview.html'
required_permission = 'users.can_see'
context_object_name = 'users'
def get_queryset(self):
query = User.objects
if config['participant_sort_users_by_first_name']:
if config['users_sort_users_by_first_name']:
query = query.order_by('first_name')
else:
query = query.order_by('last_name')
return query.all()
def get_context_data(self, **kwargs):
context = super(UserOverview, self).get_context_data(**kwargs)
context = super().get_context_data(**kwargs)
all_users = User.objects.count()
# context vars
context.update({
@ -54,29 +50,29 @@ class UserDetailView(DetailView, PermissionMixin):
"""
Classed based view to show a specific user in the interface.
"""
required_permission = 'participant.can_see_participant'
required_permission = 'users.can_see'
model = User
template_name = 'participant/user_detail.html'
context_object_name = 'shown_user'
class UserCreateView(CreateView):
"""
Create a new participant.
Create a new user.
"""
required_permission = 'participant.can_manage_participant'
template_name = 'participant/edit.html'
required_permission = 'users.can_manage'
model = User
context_object_name = 'edit_user'
form_class = UserCreateForm
success_url_name = 'user_overview'
success_url_name = 'user_list'
url_name_args = []
def manipulate_object(self, form):
self.object.username = gen_username(
form.cleaned_data['first_name'], form.cleaned_data['last_name'])
if not self.object.default_password:
self.object.default_password = gen_password()
self.object.set_password(self.object.default_password)
def post_save(self, form):
@ -86,7 +82,7 @@ class UserCreateView(CreateView):
# to new user but super(..).post_save(form) removes it and sets only the
# groups selected in the form (without 'registered')
# workaround: add registered group again manually
from openslides.participant.api import get_registered_group # TODO: Test, if global import is possible
from openslides.users.api import get_registered_group # TODO: Test, if global import is possible
registered = get_registered_group()
self.object.groups.add(registered)
@ -94,15 +90,19 @@ class UserCreateView(CreateView):
class UserMultipleCreateView(FormView):
"""
View to create multiple users at once using a big text field.
Sets the password with md5. It is the same password as in the
default_password field in cleartext. A stronger password hasher is used,
when the password is changed by the user.
"""
required_permission = 'participant.can_manage_participant'
template_name = 'participant/user_form_multiple.html'
required_permission = 'users.can_manage'
template_name = 'users/user_form_multiple.html'
form_class = UserMultipleCreateForm
success_url_name = 'user_overview'
success_url_name = 'user_list'
def form_valid(self, form):
# TODO: Use bulk_create after rework of participant.models.User
for number, line in enumerate(form.cleaned_data['participants_block'].splitlines()):
# TODO: Use bulk_create
for number, line in enumerate(form.cleaned_data['users_block'].splitlines()):
names_list = line.split()
first_name = ' '.join(names_list[:-1])
last_name = names_list[-1]
@ -114,20 +114,19 @@ class UserMultipleCreateView(FormView):
last_name=last_name,
default_password=default_password,
password=make_password(default_password, '', 'md5'))
messages.success(self.request, _('%(number)d participants successfully created.') % {'number': number + 1})
messages.success(self.request, _('%(number)d users successfully created.') % {'number': number + 1})
return super(UserMultipleCreateView, self).form_valid(form)
class UserUpdateView(UpdateView):
"""
Update an existing participant.
Update an existing users.
"""
required_permission = 'participant.can_manage_participant'
template_name = 'participant/edit.html'
required_permission = 'users.can_manage'
model = User
context_object_name = 'edit_user'
form_class = UserUpdateForm
success_url_name = 'user_overview'
success_url_name = 'user_list'
url_name_args = []
def get_form_kwargs(self, *args, **kwargs):
@ -135,50 +134,47 @@ class UserUpdateView(UpdateView):
form_kwargs.update({'request': self.request})
return form_kwargs
def manipulate_object(self, form):
self.object.username = form.cleaned_data['user_name']
def post_save(self, form):
super(UserUpdateView, self).post_save(form)
# TODO: find a better solution that makes the following lines obsolete
# TODO: Find a better solution that makes the following lines obsolete
# Background: motion.models.use_post_save adds already the registerd group
# to new user but super(..).post_save(form) removes it and sets only the
# groups selected in the form (without 'registered')
# workaround: add registered group again manually
from openslides.participant.api import get_registered_group # TODO: Test, if global import is possible
from openslides.users.api import get_registered_group # TODO: Test, if global import is possible
registered = get_registered_group()
self.object.groups.add(registered)
class UserDeleteView(DeleteView):
"""
Delete an participant.
Delete a user.
"""
required_permission = 'participant.can_manage_participant'
required_permission = 'users.can_manage'
model = User
success_url_name = 'user_overview'
success_url_name = 'user_list'
url_name_args = []
def pre_redirect(self, request, *args, **kwargs):
if self.object == self.request.user:
messages.error(request, _("You can not delete yourself."))
else:
super(UserDeleteView, self).pre_redirect(request, *args, **kwargs)
super().pre_redirect(request, *args, **kwargs)
def pre_post_redirect(self, request, *args, **kwargs):
if self.object == self.request.user:
messages.error(self.request, _("You can not delete yourself."))
else:
super(UserDeleteView, self).pre_post_redirect(request, *args, **kwargs)
super().pre_post_redirect(request, *args, **kwargs)
class SetUserStatusView(SingleObjectMixin, RedirectView):
"""
Activate or deactivate an user.
"""
required_permission = 'participant.can_manage_participant'
required_permission = 'users.can_manage'
allow_ajax = True
url_name = 'user_overview'
url_name = 'user_list'
url_name_args = []
model = User
@ -192,8 +188,6 @@ class SetUserStatusView(SingleObjectMixin, RedirectView):
messages.error(request, _("You can not deactivate yourself."))
else:
self.object.is_active = False
elif action == 'toggle':
self.object.is_active = not self.object.is_active
self.object.save()
return super(SetUserStatusView, self).pre_redirect(request, *args, **kwargs)
@ -203,27 +197,27 @@ class SetUserStatusView(SingleObjectMixin, RedirectView):
return context
class ParticipantsListPDF(PDFView):
class UsersListPDF(PDFView):
"""
Generate the userliste as PDF.
"""
required_permission = 'participant.can_see_participant'
filename = ugettext_lazy("Participant-list")
document_title = ugettext_lazy('List of Participants')
required_permission = 'users.can_see'
filename = ugettext_lazy("user-list")
document_title = ugettext_lazy('List of Users')
def append_to_pdf(self, pdf):
"""
Append PDF objects.
"""
participants_to_pdf(pdf)
users_to_pdf(pdf)
class ParticipantsPasswordsPDF(PDFView):
class UsersPasswordsPDF(PDFView):
"""
Generate the access data welcome paper for all participants as PDF.
Generate the access data welcome paper for all users as PDF.
"""
required_permission = 'participant.can_manage_participant'
filename = ugettext_lazy("Participant-access-data")
required_permission = 'users.can_manage'
filename = ugettext_lazy("User-access-data")
top_space = 0
def build_document(self, pdf_document, story):
@ -233,7 +227,7 @@ class ParticipantsPasswordsPDF(PDFView):
"""
Append PDF objects.
"""
participants_passwords_to_pdf(pdf)
users_passwords_to_pdf(pdf)
class UserCSVImportView(CSVImportView):
@ -241,16 +235,16 @@ class UserCSVImportView(CSVImportView):
Import users via CSV.
"""
import_function = staticmethod(import_users)
required_permission = 'participant.can_manage_participant'
success_url_name = 'user_overview'
template_name = 'participant/user_form_csv_import.html'
required_permission = 'users.can_manage'
success_url_name = 'user_list'
template_name = 'users/user_form_csv_import.html'
class ResetPasswordView(SingleObjectMixin, QuestionView):
"""
Set the Passwort for a user to his default password.
"""
required_permission = 'participant.can_manage_participant'
required_permission = 'users.can_manage'
model = User
allow_ajax = True
question_message = ugettext_lazy('Do you really want to reset the password?')
@ -264,17 +258,18 @@ class ResetPasswordView(SingleObjectMixin, QuestionView):
def on_clicked_yes(self):
self.object.reset_password()
self.object.save()
def get_final_message(self):
return _('The Password for %s was successfully reset.') % html_strong(self.object)
class GroupOverview(ListView):
class GroupListView(ListView):
"""
Overview over all groups.
"""
required_permission = 'participant.can_manage_participant'
template_name = 'participant/group_overview.html'
required_permission = 'users.can_manage'
template_name = 'users/group_list.html'
context_object_name = 'groups'
model = Group
@ -283,19 +278,19 @@ class GroupDetailView(DetailView, PermissionMixin):
"""
Classed based view to show a specific group in the interface.
"""
required_permission = 'participant.can_manage_participant'
required_permission = 'users.can_manage'
model = Group
template_name = 'participant/group_detail.html'
template_name = 'users/group_detail.html'
context_object_name = 'group'
def get_context_data(self, *args, **kwargs):
context = super(GroupDetailView, self).get_context_data(*args, **kwargs)
query = User.objects
if config['participant_sort_users_by_first_name']:
if config['users_sort_users_by_first_name']:
query = query.order_by('first_name')
else:
query = query.order_by('last_name')
context['group_members'] = query.filter(django_user__groups__in=[context['group']])
context['group_members'] = query.filter(groups__in=[context['group']])
return context
@ -303,57 +298,70 @@ class GroupCreateView(CreateView):
"""
Create a new group.
"""
required_permission = 'participant.can_manage_participant'
template_name = 'participant/group_edit.html'
required_permission = 'users.can_manage'
template_name = 'users/group_form.html'
context_object_name = 'group'
model = Group
form_class = GroupForm
success_url_name = 'user_group_overview'
success_url_name = 'group_list'
url_name_args = []
def get(self, request, *args, **kwargs):
delete_default_permissions()
return super(GroupCreateView, self).get(request, *args, **kwargs)
def get_apply_url(self):
"""
Returns the url when the user clicks on 'apply'.
"""
return self.get_url('group_update', args=[self.object.pk])
class GroupUpdateView(UpdateView):
"""
Update an existing group.
"""
required_permission = 'participant.can_manage_participant'
template_name = 'participant/group_edit.html'
required_permission = 'users.can_manage'
template_name = 'users/group_form.html'
model = Group
context_object_name = 'group'
form_class = GroupForm
success_url_name = 'user_group_overview'
url_name_args = []
success_url_name = 'group_list'
def get(self, request, *args, **kwargs):
delete_default_permissions()
return super(GroupUpdateView, self).get(request, *args, **kwargs)
return super().get(request, *args, **kwargs)
def get_form_kwargs(self, *args, **kwargs):
form_kwargs = super(GroupUpdateView, self).get_form_kwargs(*args, **kwargs)
form_kwargs = super().get_form_kwargs(*args, **kwargs)
form_kwargs.update({'request': self.request})
return form_kwargs
def get_apply_url(self):
"""
Returns the url when the user clicks on 'apply'.
"""
return self.get_url('group_update', args=[self.object.pk])
class GroupDeleteView(DeleteView):
"""
Delete a group.
"""
required_permission = 'participant.can_manage_participant'
required_permission = 'users.can_manage'
model = Group
success_url_name = 'user_group_overview'
success_url_name = 'group_list'
question_url_name = 'group_detail'
url_name_args = []
def pre_redirect(self, request, *args, **kwargs):
if not self.is_protected_from_deleting():
super(GroupDeleteView, self).pre_redirect(request, *args, **kwargs)
super().pre_redirect(request, *args, **kwargs)
def pre_post_redirect(self, request, *args, **kwargs):
if not self.is_protected_from_deleting():
super(GroupDeleteView, self).pre_post_redirect(request, *args, **kwargs)
super().pre_post_redirect(request, *args, **kwargs)
def is_protected_from_deleting(self):
"""
@ -370,10 +378,21 @@ class GroupDeleteView(DeleteView):
messages.error(
self.request,
_('You can not delete the last group containing the permission '
'to manage participants you are in.'))
'to manage users you are in.'))
return True
return False
def get_url_name_args(self):
try:
answer = self.get_answer()
except OpenSlidesError:
answer = 'no'
if self.request.method == 'POST' and answer != 'no':
return []
else:
return [self.object.pk]
def login(request):
extra_content = {}
@ -394,54 +413,41 @@ def login(request):
extra_content['next'] = reverse('password_change')
except User.DoesNotExist:
pass
return django_login(request, template_name='participant/login.html', extra_context=extra_content)
return django_login(request, template_name='users/login.html', extra_context=extra_content)
@login_required
@template('participant/settings.html')
def user_settings(request):
"""
Edit own user account.
"""
if request.method == 'POST':
form_user = UsersettingsForm(request.POST, instance=request.user)
if form_user.is_valid():
user = form_user.save(commit=False)
user.username = form_user.cleaned_data['user_name']
user.save()
language = request.LANGUAGE_CODE = \
request.session['django_language'] = form_user.cleaned_data['language']
activate(language)
messages.success(request, _('User settings successfully saved.'))
else:
messages.error(request, _('Please check the form for errors.'))
else:
language = request.session.get('django_language', request.LANGUAGE_CODE)
form_user = UsersettingsForm(instance=request.user, initial={'language': language})
class UserSettingsView(LoginMixin, UpdateView):
model = User
form_class = UsersettingsForm
success_url_name = 'user_settings'
url_name_args = []
template_name = 'users/settings.html'
return {
'form': form_user,
'edituser': request.user,
}
def get_initial(self):
initial = super().get_initial()
initial['language'] = self.request.session.get('django_language', self.request.LANGUAGE_CODE)
return initial
def form_valid(self, form):
self.request.LANGUAGE_CODE = self.request.session['django_language'] = form.cleaned_data['language']
activate(self.request.LANGUAGE_CODE)
return super().form_valid(form)
def get_object(self):
return self.request.user
@login_required
@template('participant/password_change.html')
def user_settings_password(request):
"""
Edit own password.
"""
if request.method == 'POST':
form = PasswordChangeForm(request.user, request.POST)
if form.is_valid():
form.save()
messages.success(request, _('Password successfully changed.'))
return redirect(reverse('core_dashboard'))
else:
messages.error(request, _('Please check the form for errors.'))
else:
form = PasswordChangeForm(user=request.user)
class UserPasswordSettingsView(LoginMixin, FormView):
form_class = PasswordChangeForm
success_url_name = 'core_dashboard'
template_name = 'users/password_change.html'
return {
'form': form,
}
def form_valid(self, form):
form.save()
messages.success(self.request, _('Password successfully changed.'))
return super().form_valid(form)
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs

View File

@ -0,0 +1,25 @@
from django.utils.translation import ugettext_lazy
from openslides.utils.widgets import Widget
from .models import User
class UserWidget(Widget):
"""
Provides a widget with all users. This is for short activation of
user slides.
"""
name = 'user'
verbose_name = ugettext_lazy('Users')
required_permission = 'core.can_manage_projector'
default_column = 1
default_weight = 60
default_active = False
template_name = 'users/widget_user.html'
more_link_pattern_name = 'user_list'
def get_context_data(self, **context):
return super(UserWidget, self).get_context_data(
users=User.objects.all(),
**context)

View File

@ -1,15 +0,0 @@
from openslides.utils.person.signals import receive_persons
from openslides.utils.person.api import (
generate_person_id, get_person, Person, Persons)
from openslides.utils.person.forms import PersonFormField, MultiplePersonFormField
from openslides.utils.person.models import PersonField, PersonMixin
__all__ = ['receive_persons', 'generate_person_id', 'get_person', 'Person',
'Persons', 'PersonFormField', 'MultiplePersonFormField',
'PersonField', 'PersonMixin', 'EmptyPerson']
class EmptyPerson(PersonMixin, Person):
@property
def person_id(self):
return 'empty'

View File

@ -1,116 +0,0 @@
from .signals import receive_persons
class Person(object):
"""
Meta-class for all person objects
"""
def person_id(self):
"""
Return an id for representation of ths person. Has to be unique.
"""
raise NotImplementedError('Any person object needs a person_id')
def __str__(self):
"""
Return a string for this person.
"""
return str(self.person_id)
@property
def sort_name(self):
"""
Return the part of the name, which is used for sorting.
For example the pre-name or the last-name
"""
return self.clean_name.lower()
@property
def clean_name(self):
"""
Return the name of this person without a suffix
"""
return str(self)
@property
def name_suffix(self):
"""
Return a suffix for the person-name.
"""
return ''
def get_absolute_url(self, link='detail'):
"""
Return an absolute url for the person.
The argument 'link' affects which url it is. Typically it is one of:
* detail
* edit
* delete
You should raise an 'ValueError', if your person does not have
one of this links, or use the AbsoluteURLMixin, which raises the
ValueError for you.
"""
try:
url = super(Person, self).get_absolute_url(link)
except AttributeError:
raise ValueError('This person object has no url.')
return url
class Persons(object):
"""
A Storage for a multiplicity of different Person-Objects.
"""
def __init__(self, person_prefix_filter=None, id_filter=None):
self.person_prefix_filter = person_prefix_filter
self.id_filter = id_filter
def __iter__(self):
try:
return iter(self._cache)
except AttributeError:
return self.iter_persons()
def __len__(self):
return len(list(self.__iter__()))
def __getitem__(self, key):
try:
return list(self)[key]
except IndexError:
from openslides.utils.person import EmptyPerson
return EmptyPerson()
def iter_persons(self):
self._cache = list()
for receiver, persons in receive_persons.send(
sender='persons', person_prefix_filter=self.person_prefix_filter, id_filter=self.id_filter):
for person in persons:
self._cache.append(person)
yield person
def generate_person_id(prefix, id):
assert prefix is not None
assert id is not None
if ':' in prefix:
raise ValueError("':' is not allowed in a the 'person_prefix'")
return "%s:%d" % (prefix, id)
def split_person_id(person_id):
data = person_id.split(':', 1)
if len(data) == 2 and data[0] and data[1]:
return data
raise TypeError("Invalid person_id: '%s'" % person_id)
def get_person(person_id):
try:
person_prefix, id = split_person_id(person_id)
except TypeError:
from openslides.utils.person import EmptyPerson
return EmptyPerson()
return Persons(person_prefix_filter=person_prefix, id_filter=id)[0]

View File

@ -1,67 +0,0 @@
from django import forms
from openslides.utils.person.api import get_person, Persons
class PersonChoices(object):
def __init__(self, field):
self.field = field
def __iter__(self):
if self.field.empty_label is not None:
yield (u'', self.field.empty_label)
for person in sorted(Persons(), key=lambda person: person.sort_name):
yield (person.person_id, person)
class PersonFormField(forms.fields.ChoiceField):
def __init__(self, required=True, initial=None, empty_label=u"---------",
*args, **kwargs):
if required and (initial is not None):
self.empty_label = None
else:
self.empty_label = empty_label
forms.fields.Field.__init__(self, required=required, initial=initial,
*args, **kwargs)
self.widget.choices = self.choices
def __deepcopy__(self, memo):
result = super(forms.fields.ChoiceField, self).__deepcopy__(memo)
return result
def _get_choices(self):
# If self._choices is set, then somebody must have manually set
# the property self.choices. In this case, just return self._choices.
if hasattr(self, '_choices'):
return self._choices
return PersonChoices(self)
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):
return super(PersonFormField, self).valid_value(value.person_id)
class MultiplePersonFormField(PersonFormField):
widget = forms.widgets.SelectMultiple
def __init__(self, *args, **kwargs):
super(MultiplePersonFormField, self).__init__(
empty_label=None, *args, **kwargs)
def to_python(self, value):
if hasattr(value, '__iter__'):
return [super(MultiplePersonFormField, self).to_python(v)
for v in value]
return super(MultiplePersonFormField, self).to_python(value)
def valid_value(self, value):
if hasattr(value, '__iter__'):
return [super(MultiplePersonFormField, self).valid_value(v)
for v in value]
return super(MultiplePersonFormField, self).valid_value(value)

View File

@ -1,77 +0,0 @@
from django.contrib.auth.models import AnonymousUser
from django.db import models
from openslides.utils.exceptions import OpenSlidesError
from .api import generate_person_id, get_person
from .forms import PersonFormField
class PersonField(models.fields.Field, metaclass=models.SubfieldBase):
def __init__(self, *args, **kwargs):
kwargs['max_length'] = 255
super(PersonField, self).__init__(*args, **kwargs)
def get_internal_type(self):
return "CharField"
def to_python(self, value):
"""
Convert an object to an user Object.
'value' has to be an object derivated from PersonMixin, None or has to
have an attribute 'person_id'.
"""
if isinstance(value, PersonMixin):
return value
elif value is None:
return None
elif isinstance(value, AnonymousUser):
raise AttributeError('An AnonymousUser can not be saved into the database.')
else:
try:
return get_person(value)
except AttributeError:
raise AttributeError('You can not save \'%s\' into a person field.'
% type(value))
def get_prep_value(self, value):
"""
Convert a person object to a string, to store it in the database.
"""
if value is None:
# For Fields with null=True
return None
elif isinstance(value, str):
# The object is already a a person_id
return value
elif hasattr(value, 'person_id'):
# The object is a person
return value.person_id
else:
OpenSlidesError('%s (%s) is no person' % (value, type(value)))
def formfield(self, **kwargs):
defaults = {'form_class': PersonFormField}
defaults.update(kwargs)
return super(PersonField, self).formfield(**defaults)
class PersonMixin(object):
@property
def person_id(self):
try:
return generate_person_id(self.person_prefix, self.pk)
except AttributeError:
raise AttributeError("%s has to have a attribute 'person_prefix'"
% self)
def __str__(self):
return self.person_id
def prepare_database_save(self, field):
if type(field) is PersonField:
return self.person_id
else:
return super(PersonMixin, self).prepare_database_save(field)

View File

@ -1,3 +0,0 @@
from django.dispatch import Signal
receive_persons = Signal(providing_args=['person_prefix_filter', 'id_filter'])

View File

@ -439,8 +439,8 @@ class FormView(PermissionMixin, ExtraContextMixin, FormMixin,
pass
class UpdateView(PermissionMixin, ExtraContextMixin,
ModelFormMixin, django_views.UpdateView):
class UpdateView(PermissionMixin, ExtraContextMixin, ModelFormMixin,
django_views.UpdateView):
"""
View to update an model object.
"""
@ -471,13 +471,12 @@ class DeleteView(SingleObjectMixin, QuestionView):
"""
View to delete an model object.
"""
success_url = None
success_url_name = None
def get(self, request, *args, **kwargs):
self.object = self.get_object()
return super(DeleteView, self).get(request, *args, **kwargs)
return super().get(request, *args, **kwargs)
def get_redirect_url(self, **kwargs):
"""

View File

@ -1,7 +1,7 @@
from django.test.client import Client
from openslides.config.api import config
from openslides.participant.models import User
from openslides.users.models import User
from openslides.utils.test import TestCase
@ -43,8 +43,7 @@ class PersonalInfoWidget(TestCase):
return assignment
def setUp(self):
self.user = User.objects.create(username='HansMeiser')
self.user.reset_password('default')
self.user = User.objects.create_user('HansMeiser', 'default')
self.client = Client()
self.client.login(username='HansMeiser', password='default')
@ -56,7 +55,7 @@ class PersonalInfoWidget(TestCase):
agenda = self.import_agenda()
if agenda:
item_1 = agenda.models.Item.objects.create(title='My Item Title iw5ohNgee4eiYahb5Eiv')
speaker = agenda.models.Speaker.objects.add(item=item_1, person=self.user)
speaker = agenda.models.Speaker.objects.add(item=item_1, user=self.user)
response = self.client.get('/dashboard/')
self.assertContains(response, 'I am on the list of speakers of the following items:', status_code=200)
self.assertContains(response, 'My Item Title iw5ohNgee4eiYahb5Eiv', status_code=200)

View File

@ -6,7 +6,7 @@ from django.test.client import Client
from openslides.agenda.models import Item, Speaker
from openslides.agenda.signals import agenda_list_of_speakers
from openslides.config.api import config
from openslides.participant.models import Group, User
from openslides.users.models import User
from openslides.projector.api import set_active_slide, register_slide_model
from openslides.utils.exceptions import OpenSlidesError
from openslides.utils.test import TestCase
@ -24,15 +24,15 @@ class ListOfSpeakerModelTests(TestCase):
def test_append_speaker(self):
# Append speaker1 to the list of item1
speaker1_item1 = Speaker.objects.add(self.speaker1, self.item1)
self.assertTrue(Speaker.objects.filter(person=self.speaker1, item=self.item1).exists())
self.assertTrue(Speaker.objects.filter(user=self.speaker1, item=self.item1).exists())
# Append speaker1 to the list of item2
speaker1_item2 = Speaker.objects.add(self.speaker1, self.item2)
self.assertTrue(Speaker.objects.filter(person=self.speaker1, item=self.item2).exists())
self.assertTrue(Speaker.objects.filter(user=self.speaker1, item=self.item2).exists())
# Append speaker2 to the list of item1
speaker2_item1 = Speaker.objects.add(self.speaker2, self.item1)
self.assertTrue(Speaker.objects.filter(person=self.speaker2, item=self.item1).exists())
self.assertTrue(Speaker.objects.filter(user=self.speaker2, item=self.item1).exists())
# Try to append speaker 1 again to the list of item1
with self.assertRaises(OpenSlidesError):
@ -69,7 +69,7 @@ class ListOfSpeakerModelTests(TestCase):
self.assertIsNone(speaker1_item1.end_time)
self.assertIsNone(speaker2_item1.begin_time)
speaker2_item1.begin_speach()
self.assertIsNotNone(Speaker.objects.get(person=self.speaker1, item=self.item1).end_time)
self.assertIsNotNone(Speaker.objects.get(user=self.speaker1, item=self.item1).end_time)
self.assertIsNotNone(speaker2_item1.begin_time)
@patch('openslides.agenda.models.update_projector_overlay')
@ -109,12 +109,12 @@ class SpeakerViewTestCase(TestCase):
self.admin_client.login(username='admin', password='admin')
# Speaker1
self.speaker1 = User.objects.create_user('speaker1', 'speaker1@user.user', 'speaker')
self.speaker1 = User.objects.create_user('speaker1', 'speaker')
self.speaker1_client = Client()
self.speaker1_client.login(username='speaker1', password='speaker')
# Speaker2
self.speaker2 = User.objects.create_user('speaker2', 'speaker2@user.user', 'speaker')
self.speaker2 = User.objects.create_user('speaker2', 'speaker')
self.speaker2_client = Client()
self.speaker2_client.login(username='speaker2', password='speaker')
@ -135,12 +135,12 @@ class SpeakerViewTestCase(TestCase):
class TestSpeakerAppendView(SpeakerViewTestCase):
def test_get(self):
self.assertFalse(Speaker.objects.filter(person=self.speaker1, item=self.item1).exists())
self.assertFalse(Speaker.objects.filter(user=self.speaker1, item=self.item1).exists())
self.assertEqual(Speaker.objects.filter(item=self.item1).count(), 0)
# Set speaker1 to item1
response = self.check_url('/agenda/1/speaker/', self.speaker1_client, 302)
self.assertTrue(Speaker.objects.filter(person=self.speaker1, item=self.item1).exists())
self.assertTrue(Speaker.objects.filter(user=self.speaker1, item=self.item1).exists())
self.assertEqual(Speaker.objects.filter(item=self.item1).count(), 1)
self.assertMessage(response, 'You were successfully added to the list of speakers.')
@ -162,23 +162,14 @@ class TestAgendaItemView(SpeakerViewTestCase):
def test_post(self):
# Set speaker1 to item1
response = self.admin_client.post(
'/agenda/1/', {'speaker': self.speaker1.person_id})
self.assertTrue(Speaker.objects.filter(person=self.speaker1, item=self.item1).exists())
'/agenda/1/', {'speaker': self.speaker1.id})
self.assertTrue(Speaker.objects.filter(user=self.speaker1, item=self.item1).exists())
# Try it again
response = self.admin_client.post(
'/agenda/1/', {'speaker': self.speaker1.person_id})
'/agenda/1/', {'speaker': self.speaker1.id})
self.assertFormError(response, 'form', 'speaker', 'speaker1 is already on the list of speakers.')
def test_group_as_speaker(self):
"""
Test to show a group as a speaker on the agenda-view.
"""
group = Group.objects.create(name='test', group_as_person=True)
Speaker.objects.add(group, self.item1)
self.assertTrue(Speaker.objects.filter(person=group.person_id, item=self.item1).exists())
self.admin_client.get('/agenda/1/')
class TestSpeakerDeleteView(SpeakerViewTestCase):
def test_get(self):
@ -190,7 +181,7 @@ class TestSpeakerDeleteView(SpeakerViewTestCase):
response = self.admin_client.post(
'/agenda/1/speaker/%d/del/' % speaker.pk, {'yes': 'yes'})
self.assertEqual(response.status_code, 302)
self.assertFalse(Speaker.objects.filter(person=self.speaker1, item=self.item1).exists())
self.assertFalse(Speaker.objects.filter(user=self.speaker1, item=self.item1).exists())
def test_post_as_user(self):
Speaker.objects.add(self.speaker1, self.item1)
@ -198,14 +189,14 @@ class TestSpeakerDeleteView(SpeakerViewTestCase):
response = self.speaker1_client.post(
'/agenda/1/speaker/del/', {'yes': 'yes'})
self.assertEqual(response.status_code, 302)
self.assertFalse(Speaker.objects.filter(person=self.speaker1, item=self.item1).exists())
self.assertFalse(Speaker.objects.filter(user=self.speaker1, item=self.item1).exists())
class TestSpeakerSpeakView(SpeakerViewTestCase):
def test_get(self):
url = '/agenda/1/speaker/%s/speak/' % self.speaker1.person_id
url = '/agenda/1/speaker/%s/speak/' % self.speaker1.pk
response = self.check_url(url, self.admin_client, 302)
self.assertMessage(response, 'user:2 is not on the list of item1.')
self.assertMessage(response, '2 is not on the list of item1.')
speaker = Speaker.objects.add(self.speaker1, self.item1)
response = self.check_url(url, self.admin_client, 302)
@ -257,7 +248,7 @@ class GlobalListOfSpeakersLinks(SpeakerViewTestCase):
set_active_slide('agenda', pk=1)
response = self.speaker1_client.get('/agenda/list_of_speakers/add/')
self.assertRedirects(response, '/agenda/1/')
self.assertEqual(Speaker.objects.get(item__pk='1').person, self.speaker1)
self.assertEqual(Speaker.objects.get(item__pk='1').user, self.speaker1)
self.assertMessage(response, 'You were successfully added to the list of speakers.')
perm = Permission.objects.filter(name='Can see agenda').get()
@ -281,7 +272,7 @@ class GlobalListOfSpeakersLinks(SpeakerViewTestCase):
response = self.speaker1_client.get('/agenda/list_of_speakers/add/')
self.assertRedirects(response, '/agenda/%d/' % agenda_item.pk)
self.assertEqual(Speaker.objects.get(item__pk=agenda_item.pk).person, self.speaker1)
self.assertEqual(Speaker.objects.get(item__pk=agenda_item.pk).user, self.speaker1)
self.assertMessage(response, 'You were successfully added to the list of speakers.')
def test_global_next_speaker_url(self):

View File

@ -7,7 +7,7 @@ from django.test.client import Client
from openslides.agenda.models import Item
from openslides.agenda.slides import agenda_slide
from openslides.participant.models import User
from openslides.users.models import User
from openslides.projector.api import set_active_slide
from openslides.utils.test import TestCase
@ -127,8 +127,7 @@ class ViewTest(TestCase):
self.refreshItems()
self.admin = User.objects.get(pk=1)
self.anonym, created = User.objects.get_or_create(username='testanonym')
self.anonym.reset_password('default')
self.anonym = User.objects.create_user('testanonym', 'default')
def refreshItems(self):
self.item1 = Item.objects.get(pk=self.item1.id)
@ -268,8 +267,7 @@ class ViewTest(TestCase):
# Prepare
self.item1.type = Item.ORGANIZATIONAL_ITEM
self.item1.save()
user = User.objects.create(username='testuser_EeBoPh5uyookoowoodii')
user.reset_password('default')
user = User.objects.create_user('testuser_EeBoPh5uyookoowoodii', 'default')
client = Client()
client.login(username='testuser_EeBoPh5uyookoowoodii', password='default')
# Test view with permission
@ -334,12 +332,8 @@ class ViewTest(TestCase):
class ConfigTest(TestCase):
def setUp(self):
self.admin = User.objects.create(username='config_test_admin')
self.admin.reset_password('default')
self.admin.is_superuser = True
self.admin.save()
self.client = Client()
self.client.login(username='config_test_admin', password='default')
self.client.login(username='admin', password='admin')
def test_config_collection_css_javascript(self):
response = self.client.get('/config/agenda/')

View File

@ -2,7 +2,7 @@ from django.test.client import Client
from openslides.agenda.models import Item, Speaker
from openslides.assignment.models import Assignment
from openslides.participant.models import User
from openslides.users.models import User
from openslides.utils.test import TestCase
@ -34,6 +34,6 @@ class AssignmentModelTest(TestCase):
self.assertEqual(item.speaker_set.count(), 1)
assignment.gen_poll()
self.assertTrue(item.speaker_set.filter(person=person_1).exists())
self.assertTrue(item.speaker_set.filter(person=person_2).exists())
self.assertTrue(item.speaker_set.filter(person=person_3).exists())
self.assertTrue(item.speaker_set.filter(user=person_1).exists())
self.assertTrue(item.speaker_set.filter(user=person_2).exists())
self.assertTrue(item.speaker_set.filter(user=person_3).exists())

View File

@ -1,7 +1,7 @@
from django.test.client import Client
from openslides.assignment.models import Assignment
from openslides.participant.models import User
from openslides.users.models import User
from openslides.utils.test import TestCase

View File

@ -2,7 +2,7 @@ from django.test.client import Client
from openslides.assignment.models import Assignment, AssignmentPoll
from openslides.config.api import config
from openslides.participant.models import Group, User
from openslides.users.models import Group, User
from openslides.utils.test import TestCase
@ -14,7 +14,7 @@ class AssignmentViewTestCase(TestCase):
self.admin_client.login(username='admin', password='admin')
# Staff
self.staff = User.objects.create_user('staff', 'staff@user.user', 'staff')
self.staff = User.objects.create_user('staff', 'staff')
staff_group = Group.objects.get(name='Staff')
self.staff.groups.add(staff_group)
self.staff.save()
@ -22,7 +22,7 @@ class AssignmentViewTestCase(TestCase):
self.staff_client.login(username='staff', password='staff')
# Delegate
self.delegate = User.objects.create_user('delegate', 'delegate@user.user', 'delegate')
self.delegate = User.objects.create_user(username='delegate', password='delegate')
delegate_group = Group.objects.get(name='Delegates')
self.delegate.groups.add(delegate_group)
self.delegate.save()
@ -30,7 +30,7 @@ class AssignmentViewTestCase(TestCase):
self.delegate_client.login(username='delegate', password='delegate')
# Registered
self.registered = User.objects.create_user('registered', 'registered@user.user', 'registered')
self.registered = User.objects.create_user(username='registered', password='registered')
self.registered_client = Client()
self.registered_client.login(username='registered', password='registered')

View File

@ -12,7 +12,7 @@ from openslides.config.api import (config, ConfigCollection, ConfigGroup,
ConfigGroupedCollection, ConfigVariable)
from openslides.config.exceptions import ConfigError, ConfigNotFound
from openslides.config.signals import config_signal
from openslides.participant.models import User
from openslides.users.models import User
from openslides.utils.test import TestCase
@ -105,12 +105,10 @@ class ConfigFormTest(TestCase):
perm = Permission.objects.get(content_type=ct, codename='can_manage')
# Setup two users
self.manager = User.objects.create(username='config_test_manager')
self.manager.reset_password('default')
self.manager = User.objects.create_user('config_test_manager', 'default')
self.manager.user_permissions.add(perm)
self.normal_user = User.objects.create(username='config_test_normal_user')
self.normal_user.reset_password('default')
self.normal_user = User.objects.create_user('config_test_normal_user', 'default')
# Login
self.client_manager = Client()
@ -254,8 +252,7 @@ class ConfigWeightTest(TestCase):
perm = Permission.objects.get(content_type=ct, codename='can_manage')
# Setup two users
self.manager = User.objects.create(username='config_test_manager')
self.manager.reset_password('default')
self.manager = User.objects.create_user('config_test_manager', 'default')
self.manager.user_permissions.add(perm)
# Login

View File

@ -7,7 +7,7 @@ from openslides.agenda.models import Item
from openslides.config.api import config
from openslides.core import views
from openslides.core.models import CustomSlide
from openslides.participant.models import User
from openslides.users.models import User
from openslides.utils.test import TestCase
@ -67,7 +67,7 @@ class SelectWidgetsViewTest(TestCase):
class VersionViewTest(TestCase):
def setUp(self):
User.objects.create_user('CoreMaximilian', 'xxx@xx.xx', 'default')
User.objects.create_user('CoreMaximilian', 'default')
self.client = Client()
self.client.login(username='CoreMaximilian', password='default')
@ -87,7 +87,7 @@ class VersionViewTest(TestCase):
class SearchViewTest(TestCase):
def test_simple_search(self):
Item.objects.create(title='agenda_item_bnghfdjkgndkjdfg')
User.objects.create_user('CoreMaximilian', 'xxx@xx.xx', 'default')
User.objects.create_user('CoreMaximilian', 'default')
self.client = Client()
self.client.login(username='CoreMaximilian', password='default')
response = self.client.get('/search/?q=agenda_item_bnghfd')

View File

@ -8,7 +8,7 @@ from django.core.files.uploadedfile import SimpleUploadedFile
from django.test.client import Client
from openslides.mediafile.models import Mediafile
from openslides.participant.models import User
from openslides.core.models import User
from openslides.utils.test import TestCase
@ -24,11 +24,9 @@ class MediafileTest(TestCase):
# Setup three different users
self.manager = User.objects.get(pk=1)
self.vip_user = User.objects.create(username='mediafile_test_vip_user')
self.vip_user.reset_password('default')
self.vip_user = User.objects.create_user('mediafile_test_vip_user', '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')
self.normal_user = User.objects.create_user('mediafile_test_normal_user', 'default')
# Setup a mediafile object
self.tmp_dir = settings.MEDIA_ROOT
@ -74,13 +72,16 @@ class MediafileTest(TestCase):
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">Administrator</option>', status_code=200)
self.assertContains(response, '<option value="1" selected="selected">Administrator</option>', status_code=200)
self.assertTemplateUsed(response, 'mediafile/mediafile_form.html')
response = clients['client_vip_user'].get('/mediafile/new/')
self.assertNotContains(response, '<select id="id_uploader" name="uploader">', status_code=200)
self.assertTemplateUsed(response, 'mediafile/mediafile_form.html')
response = clients['client_normal_user'].get('/mediafile/new/')
self.assertEqual(response.status_code, 403)
bad_client = Client()
response = bad_client.get('/mediafile/new/')
self.assertRedirects(response, expected_url='/login/?next=/mediafile/new/', status_code=302, target_status_code=200)
@ -92,7 +93,7 @@ class MediafileTest(TestCase):
response_1 = client_1.post('/mediafile/new/',
{'title': 'new_test_file_title_1',
'mediafile': new_file_1,
'uploader': self.normal_user.person_id})
'uploader': self.normal_user.pk})
self.assertRedirects(response_1, expected_url='/mediafile/', status_code=302, target_status_code=200)
object_1 = Mediafile.objects.latest('timestamp')
self.assertEqual(object_1.mediafile.url, '/media/file/new_test_file.txt')
@ -129,12 +130,15 @@ class MediafileTest(TestCase):
clients = self.login_clients()
response = clients['client_manager'].get('/mediafile/1/edit/')
self.assertContains(response, '---------', status_code=200)
self.assertContains(response, '<option value="user:3" selected="selected">mediafile_test_normal_user</option>', status_code=200)
self.assertContains(response, '<option value="3" selected="selected">mediafile_test_normal_user</option>', status_code=200)
self.assertTemplateUsed(response, 'mediafile/mediafile_form.html')
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.assertRedirects(response, expected_url='/login/?next=/mediafile/1/edit/', status_code=302, target_status_code=200)
@ -145,7 +149,7 @@ class MediafileTest(TestCase):
self.object.save()
response = clients['client_vip_user'].get('/mediafile/1/edit/')
self.assertNotContains(response, '---------', status_code=200)
self.assertNotContains(response, '<option value="user:2" selected="selected">mediafile_test_vip_user</option>', status_code=200)
self.assertNotContains(response, '<option value="2" selected="selected">mediafile_test_vip_user</option>', status_code=200)
self.assertTemplateUsed(response, 'mediafile/mediafile_form.html')
def test_edit_mediafile_post_request(self):
@ -158,7 +162,7 @@ class MediafileTest(TestCase):
response_1 = client_1.post('/mediafile/2/edit/',
{'title': 'new_test_file_title_1',
'mediafile': new_file_1,
'uploader': self.manager.person_id})
'uploader': self.manager.pk})
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')

View File

@ -1,29 +1,23 @@
import os
from io import BytesIO
from django.test.client import Client
from openslides.motion.csv_import import import_motions
from openslides.motion.models import Category, Motion
from openslides.participant.models import User
from openslides.users.models import User
from openslides.utils.test import TestCase
class CSVImport(TestCase):
def setUp(self):
# Admin
self.admin = User.objects.create_superuser('Admin_ieY0Eereimeimeizuosh', 'admin@admin.admin', 'eHiK1aiRahxaix0Iequ2')
self.admin_client = Client()
self.admin_client.login(username='Admin_ieY0Eereimeimeizuosh', password='eHiK1aiRahxaix0Iequ2')
# User1
self.user1 = User.objects.create_user('Admin_ieY0Eereimeimeizuosh', 'eHiK1aiRahxaix0Iequ2')
# Normal user
self.normal_user = User.objects.create_user('User_CiuNgo4giqueeChie5oi', 'user@user.user', 'eihi1Eequaek4eagaiKu')
self.normal_client = Client()
self.normal_client.login(username='User_CiuNgo4giqueeChie5oi', password='eihi1Eequaek4eagaiKu')
self.normal_user = User.objects.create_user('User_CiuNgo4giqueeChie5oi', 'eihi1Eequaek4eagaiKu')
# Category
self.category1 = Category.objects.create(name='Bildung', prefix='B1')
self.category2 = Category.objects.create(name='Bildung', prefix='B2')
Category.objects.create(name='Bildung', prefix='B1')
Category.objects.create(name='Bildung', prefix='B2')
def test_example_file_de(self):
special_user = User.objects.create_user(username='Harry_Holland',
@ -41,7 +35,7 @@ class CSVImport(TestCase):
self.assertEqual(Motion.objects.count(), 0)
with open(csv_dir + '/motions-demo_de.csv', 'rb') as f:
success_message, warning_message, error_message = import_motions(
csvfile=f, default_submitter=self.normal_user.person_id, override=False, importing_person=self.admin)
csvfile=f, default_submitter=self.normal_user, override=False, importing_person=self.user1)
self.assertEqual(Motion.objects.count(), 11)
motion1 = Motion.objects.get(pk=1)
@ -74,7 +68,7 @@ class CSVImport(TestCase):
csv_file = BytesIO()
csv_file.write(bytes('Header\nMalformed data,\n,Title,Text,,,\n', 'utf8'))
success_message, warning_message, error_message = import_motions(
csvfile=csv_file, default_submitter=self.normal_user.person_id, override=False)
csvfile=csv_file, default_submitter=self.normal_user.id, override=False)
self.assertEqual(success_message, '')
self.assertTrue('Line is malformed.' in error_message)
@ -82,7 +76,7 @@ class CSVImport(TestCase):
csv_file = BytesIO(bytes('Müller', 'iso-8859-15'))
success_message, warning_message, error_message = import_motions(
csvfile=csv_file,
default_submitter=self.normal_user.person_id,
default_submitter=self.normal_user.id,
override=False)
self.assertEqual(success_message, '')
self.assertIn('Import file has wrong character encoding, only UTF-8 is supported!', error_message)

View File

@ -1,7 +1,7 @@
from openslides.config.api import config
from openslides.motion.exceptions import WorkflowError
from openslides.motion.models import Motion, State, Workflow
from openslides.participant.models import User
from openslides.users.models import User
from openslides.utils.test import TestCase

View File

@ -1,6 +1,6 @@
from django.test.client import Client
from openslides.motion.models import Motion
from openslides.participant.models import User
from openslides.users.models import User
from openslides.utils.test import TestCase

View File

@ -7,7 +7,7 @@ from django.test.client import Client
from openslides.config.api import config
from openslides.mediafile.models import Mediafile
from openslides.motion.models import Category, Motion, MotionLog, State
from openslides.participant.models import Group, User
from openslides.users.models import Group, User
from openslides.utils.test import TestCase
@ -19,7 +19,7 @@ class MotionViewTestCase(TestCase):
self.admin_client.login(username='admin', password='admin')
# Staff
self.staff = User.objects.create_user('staff', 'staff@user.user', 'staff')
self.staff = User.objects.create_user('staff', 'staff')
staff_group = Group.objects.get(name='Staff')
self.staff.groups.add(staff_group)
self.staff.save()
@ -27,7 +27,7 @@ class MotionViewTestCase(TestCase):
self.staff_client.login(username='staff', password='staff')
# Delegate
self.delegate = User.objects.create_user('delegate', 'delegate@user.user', 'delegate')
self.delegate = User.objects.create_user('delegate', 'delegate')
delegate_group = Group.objects.get(name='Delegates')
self.delegate.groups.add(delegate_group)
self.delegate.save()
@ -35,7 +35,7 @@ class MotionViewTestCase(TestCase):
self.delegate_client.login(username='delegate', password='delegate')
# Registered
self.registered = User.objects.create_user('registered', 'registered@user.user', 'registered')
self.registered = User.objects.create_user('registered', 'registered')
self.registered_client = Client()
self.registered_client.login(username='registered', password='registered')
@ -95,7 +95,6 @@ class TestMotionDetailView(MotionViewTestCase):
self.assertContains(self.admin_client.get('/motion/1/'), 'registered')
self.registered.delete()
self.assertNotContains(self.admin_client.get('/motion/1/'), 'registered')
self.assertContains(self.admin_client.get('/motion/1/'), 'empty')
class TestMotionDetailVersionView(MotionViewTestCase):
@ -126,7 +125,7 @@ class TestMotionCreateView(MotionViewTestCase):
response = self.delegate_client.post(self.url, {'title': 'delegate motion',
'text': 'motion text',
'reason': 'motion reason',
'submitter': self.admin.person_id})
'submitter': self.admin.id})
self.assertEqual(response.status_code, 302)
motion = Motion.objects.get(versions__title='delegate motion')
self.assertTrue(motion.is_submitter(self.delegate))
@ -135,7 +134,7 @@ class TestMotionCreateView(MotionViewTestCase):
response = self.registered_client.post(self.url, {'title': 'registered motion',
'text': 'motion text',
'reason': 'motion reason',
'submitter': self.admin.person_id})
'submitter': self.admin.id})
self.assertEqual(response.status_code, 403)
self.assertFalse(Motion.objects.filter(versions__title='registered motion').exists())
@ -164,13 +163,13 @@ class TestMotionCreateView(MotionViewTestCase):
config['motion_identifier'] = 'manually'
response = self.admin_client.post(self.url, {'title': 'something',
'text': 'bar',
'submitter': self.admin.person_id,
'submitter': self.admin.id,
'identifier': 'uufag5faoX0thahBi8Fo'})
self.assertFormError(response, 'form', 'identifier', 'Motion with this Identifier already exists.')
def test_empty_text_field(self):
response = self.admin_client.post(self.url, {'title': 'foo',
'submitter': self.admin.person_id})
'submitter': self.admin.id})
self.assertFormError(response, 'form', 'text', 'This field is required.')
def test_identifier_with_category_prefix(self):
@ -201,7 +200,7 @@ class TestMotionUpdateView(MotionViewTestCase):
response = self.admin_client.post(self.url, {'title': 'new motion_title',
'text': 'motion text',
'reason': 'motion reason',
'submitter': self.admin.person_id,
'submitter': self.admin.id,
'workflow': 1})
self.assertRedirects(response, '/motion/1/')
motion = Motion.objects.get(pk=1)
@ -235,7 +234,7 @@ class TestMotionUpdateView(MotionViewTestCase):
'text': 'another motion text',
'reason': 'another motion reason',
'workflow': workflow.pk,
'submitter': self.admin.person_id})
'submitter': self.admin.id})
self.assertRedirects(response, '/motion/1/')
motion = Motion.objects.get(pk=self.motion1.pk)
self.assertEqual(motion.versions.count(), 2)
@ -254,7 +253,7 @@ class TestMotionUpdateView(MotionViewTestCase):
response = self.admin_client.post(self.url, {'title': 'another new motion_title',
'text': 'another motion text',
'reason': 'another motion reason',
'submitter': self.admin.person_id,
'submitter': self.admin.id,
'workflow': workflow.pk,
'disable_versioning': 'true'})
self.assertRedirects(response, '/motion/1/')
@ -278,7 +277,7 @@ class TestMotionUpdateView(MotionViewTestCase):
'text': 'eedieFoothae2iethuo3',
'reason': 'ier2laiy1veeGoo0mau2',
'workflow': workflow.pk,
'submitter': self.admin.person_id})
'submitter': self.admin.id})
self.assertRedirects(response, '/motion/1/')
motion = Motion.objects.get(pk=self.motion1.pk)
self.assertEqual(motion.versions.count(), 1)
@ -287,11 +286,11 @@ class TestMotionUpdateView(MotionViewTestCase):
self.assertEqual(self.motion1.state.workflow.pk, 1)
response = self.admin_client.post(self.url, {'title': 'oori4KiaghaeSeuzaim2',
'text': 'eequei1Tee1aegeNgee0',
'submitter': self.admin.person_id})
'submitter': self.admin.id})
self.assertEqual(Motion.objects.get(pk=self.motion1.pk).state.workflow.pk, 1)
response = self.admin_client.post(self.url, {'title': 'oori4KiaghaeSeuzaim2',
'text': 'eequei1Tee1aegeNgee0',
'submitter': self.admin.person_id,
'submitter': self.admin.id,
'workflow': 2})
self.assertRedirects(response, '/motion/1/')
self.assertEqual(Motion.objects.get(pk=self.motion1.pk).state.workflow.pk, 2)
@ -302,6 +301,7 @@ class TestMotionUpdateView(MotionViewTestCase):
motion = Motion.objects.create(title='cuoPhoX4Baifoxoothi3', text='zee7xei3taediR9loote')
response = self.staff_client.get('/motion/%s/' % motion.id)
self.assertNotContains(response, 'aengeing3quair3fieGi')
motion.support(self.registered)
self.registered.last_name = 'aengeing3quair3fieGi'
self.registered.save()
@ -313,7 +313,7 @@ class TestMotionUpdateView(MotionViewTestCase):
'/motion/%s/edit/' % motion.id,
{'title': 'oori4KiaghaeSeuzaim2',
'text': 'eequei1Tee1aegeNgee0',
'submitter': self.delegate.person_id})
'submitter': self.delegate.id})
self.assertEqual(response.status_code, 403)
motion.add_submitter(self.delegate)

View File

@ -1 +0,0 @@
VERSION = (9999, 9999, 9999, 'alpha', 1)

View File

@ -1,48 +0,0 @@
from django.db import models
from django.dispatch import receiver
from openslides.utils.person.models import PersonField, PersonMixin
from openslides.utils.person.signals import receive_persons
class TestPerson(PersonMixin, models.Model):
person_prefix = 'test'
name = models.CharField(max_length='255')
def __unicode__(self):
return self.name
def get_absolute_url(self, link='detail'):
return 'absolute_url_of_test_person'
class TestPersonToPerson(object):
def __init__(self, person_prefix_filter=None, id_filter=None):
self.person_prefix_filter = person_prefix_filter
self.id_filter = id_filter
def __iter__(self):
if (not self.person_prefix_filter or
self.person_prefix_filter == TestPerson.person_prefix):
if self.id_filter:
try:
yield TestPerson.objects.get(pk=self.id_filter)
except TestPerson.DoesNotExist:
pass
else:
for user in TestPerson.objects.all():
yield user
@receiver(receive_persons, dispatch_uid="test_person")
def receive_persons(sender, **kwargs):
return TestPersonToPerson(
person_prefix_filter=kwargs['person_prefix_filter'],
id_filter=kwargs['id_filter'])
class TestModel(models.Model):
person = PersonField()
def __unicode__(self):
return self.person

View File

@ -1,43 +0,0 @@
from django.contrib.auth.models import AnonymousUser
from openslides.utils.person.api import get_person
from openslides.utils.test import TestCase
from .models import TestModel, TestPerson
class PersonTest(TestCase):
def setUp(self):
self.person1 = TestPerson.objects.create(name='test1')
def test_update_of_person_field(self):
self.assertEqual(self.person1.person_id, 'test:1')
# save person field
test_object = TestModel.objects.create(person=self.person1)
self.assertEqual(test_object.person, self.person1)
# update person field
test_object.save()
self.assertEqual(TestModel.objects.get(pk=test_object.pk).person, self.person1)
def test_save_anonymous_user_in_person_field(self):
with self.assertRaisesRegex(
AttributeError,
'An AnonymousUser can not be saved into the database.'):
TestModel.objects.create(person=AnonymousUser())
def test_save_unsupported_object_in_person_field(self):
with self.assertRaisesRegex(
AttributeError,
'You can not save \'<class \'int\'>\' into a person field.'):
TestModel.objects.create(person=5)
def test_get_absolute_url_with_deleted_person(self):
person2 = TestPerson.objects.create(name='test2')
self.assertEqual(person2.get_absolute_url(), 'absolute_url_of_test_person')
person_id = person2.person_id
self.assertEqual(get_person(person_id).get_absolute_url(), 'absolute_url_of_test_person')
person2.delete()
with self.assertRaisesRegex(ValueError, 'This person object has no url.'):
get_person(person_id).get_absolute_url()

View File

@ -9,11 +9,11 @@ from openslides.global_settings import * # noqa
OPENSLIDES_USER_DATA_PATH = os.path.realpath(os.path.dirname(__file__))
# Use 'DEBUG = True' to get more details for server errors. Default for releases: False
DEBUG = True
DEBUG = False
TEMPLATE_DEBUG = DEBUG
# Make this unique, and don't share it with anybody.
SECRET_KEY = 'secred'
SECRET_KEY = 'secret'
# Database settings. Change this to use MySQL or PostgreSQL
DATABASES = {
@ -25,13 +25,6 @@ DATABASES = {
'HOST': '',
'PORT': ''}}
# Add OpenSlides plugins to this list
INSTALLED_PLUGINS = (
'tests.person_api',
)
INSTALLED_APPS += INSTALLED_PLUGINS
# Some other settings
TIME_ZONE = 'Europe/Berlin'

View File

@ -2,7 +2,7 @@ from io import BytesIO
from textwrap import dedent
from openslides.utils.test import TestCase
from openslides.participant.csv_import import import_users
from openslides.users.csv_import import import_users
class TestCSVImport(TestCase):

View File

@ -1,31 +1,25 @@
from openslides.participant.api import gen_password, gen_username
from openslides.participant.models import Group, User
from openslides.utils.person import get_person, Persons
from openslides.users.api import gen_password, gen_username
from openslides.users.models import Group, User
from openslides.utils.test import TestCase
class UserTest(TestCase):
def setUp(self):
self.user1 = User()
self.user1.first_name = u'Max'
self.user1.last_name = u'Mustermann'
self.user1.first_name = 'Max'
self.user1.last_name = 'Mustermann'
self.user1.username = gen_username(
self.user1.first_name, self.user1.last_name)
self.user1.default_password = gen_password()
self.user1.save()
self.django_user1 = self.user1.django_user
def test_participant_user(self):
self.assertEqual(self.django_user1.user, self.user1)
self.assertEqual(self.django_user1, self.user1.django_user)
def test_str(self):
self.assertEqual(str(self.user1), 'Max Mustermann')
def test_name_suffix(self):
self.user1.structure_level = u'München'
self.user1.structure_level = 'München'
self.user1.save()
self.assertEqual(str(self.user1), u'Max Mustermann (München)')
self.assertEqual(str(self.user1), 'Max Mustermann (München)')
def test_reset_password(self):
self.assertIsInstance(self.user1.default_password, str)
@ -35,39 +29,14 @@ class UserTest(TestCase):
self.user1.reset_password()
self.assertTrue(self.user1.check_password(self.user1.default_password))
def test_person_api(self):
self.assertTrue(hasattr(self.user1, 'person_id'))
self.assertEqual(self.user1.person_id, 'user:2')
self.assertEqual(get_person('user:2'), self.user1)
self.assertEqual(len(Persons(person_prefix_filter='user')), 2)
def test_get_absolute_url(self):
urls = (('detail', '/participant/2/'),
('update', '/participant/2/edit/'),
('delete', '/participant/2/del/'))
urls = (('detail', '/user/2/'),
('update', '/user/2/edit/'),
('delete', '/user/2/del/'))
for link, url in urls:
self.assertEqual(self.user1.get_absolute_url(link), url)
class GroupTest(TestCase):
def setUp(self):
self.group1 = Group.objects.create(name='Test Group')
self.django_group1 = self.group1.django_group
def test_group_group(self):
self.assertEqual(self.group1.django_group, self.django_group1)
self.assertEqual(self.group1, self.django_group1.group)
def test_person_api(self):
self.assertTrue(hasattr(self.group1, 'person_id'))
person_id = "group:%d" % self.group1.id
self.assertEqual(self.group1.person_id, person_id)
self.assertRaises(Group.DoesNotExist)
self.group1.group_as_person = True
self.group1.save()
self.assertEqual(get_person(person_id), self.group1)
class DefaultGroups(TestCase):
def test_pks_of_default_groups(self):
@ -86,7 +55,7 @@ class DefaultGroups(TestCase):
'agenda.can_see_orga_items',
'motion.can_see_motion',
'assignment.can_see_assignment',
'participant.can_see_participant',
'users.can_see',
'mediafile.can_see')
for perm_string in default_perms:
perm_string_list = []

View File

@ -1,7 +1,7 @@
from django.test.client import Client
from django.test.utils import override_settings
from openslides.participant.models import Group, User
from openslides.users.models import Group, User
from openslides.utils.test import TestCase
@ -12,8 +12,7 @@ class TestUmlautUser(TestCase):
"""
def setUp(self):
self.user = User.objects.create(username='äöü')
self.user.reset_password('äöü')
self.user = User.objects.create_user('äöü', 'äöü')
self.client = Client()
self.client.login(username='äöü', password='äöü')
@ -32,10 +31,10 @@ class TestUmlautUser(TestCase):
self.assertEqual(response.status_code, 302)
def test_permission(self):
response = self.client.get('/participant/1/edit/')
response = self.client.get('/user/1/edit/')
self.assertEqual(response.status_code, 403)
self.user.groups.add(Group.objects.get(pk=4))
response = self.client.get('/participant/1/edit/')
response = self.client.get('/user/1/edit/')
self.assertEqual(response.status_code, 200)

View File

@ -1,5 +1,5 @@
from openslides.participant.api import gen_username
from openslides.participant.models import User
from openslides.users.api import gen_username
from openslides.users.models import User
from openslides.utils.test import TestCase

View File

@ -5,8 +5,8 @@ from django.contrib.contenttypes.models import ContentType
from django.test.client import Client
from openslides.config.api import config
from openslides.participant.api import get_registered_group
from openslides.participant.models import get_protected_perm, Group, User
from openslides.users.api import get_registered_group
from openslides.users.models import Group, User
from openslides.utils.test import TestCase
@ -20,37 +20,41 @@ class UserViews(TestCase):
self.client.login(username='admin', password='admin')
def test_create(self):
response = self.client.get('/participant/new/')
self.assertTemplateUsed(response, 'participant/edit.html')
self.assertContains(response, 'New participant')
response = self.client.post('/participant/new/', {'first_name': 'test_name_ho8hui2niz4nohSupahb'})
self.assertRedirects(response, '/participant/')
response = self.client.get('/user/new/')
self.assertTemplateUsed(response, 'users/user_form.html')
self.assertContains(response, 'New user')
response = self.client.post('/user/new/', {'first_name': 'test_name_ho8hui2niz4nohSupahb'})
self.assertRedirects(response, '/user/')
def test_create_multiple(self):
response = self.client.get('/participant/new_multiple/')
self.assertTemplateUsed(response, 'participant/user_form_multiple.html')
self.assertContains(response, 'New multiple participants')
response = self.client.get('/user/new_multiple/')
self.assertTemplateUsed(response, 'users/user_form_multiple.html')
self.assertContains(response, 'New multiple users')
self.assertEqual(User.objects.count(), 1)
block = ('first_name_ksdjfhkjsdhf75utgeitrten last_name_khonizt958zh8fh\n'
'first_name_1_bmgnf7z8ru first_name_2_kjc98vivt last_name_dfg76kjkjuibv')
response = self.client.post('/participant/new_multiple/',
{'participants_block': block})
response = self.client.post('/user/new_multiple/',
{'users_block': block})
self.assertEqual(User.objects.count(), 3)
def test_update(self):
response = self.client.get('/participant/1/edit/')
self.assertTemplateUsed(response, 'participant/edit.html')
self.assertContains(response, 'Edit participant')
response = self.client.get('/user/1/edit/')
self.assertTemplateUsed(response, 'users/user_form.html')
self.assertContains(response, 'Edit user')
response = self.client.post(
'/participant/1/edit/',
{'user_name': 'test_name_unaewae5Ir0saijeac2I',
'/user/1/edit/',
{'username': 'test_name_unaewae5Ir0saijeac2I',
'first_name': 'test_name_aJi5jaizaVingaeF3Ohj',
'groups': '4',
'is_active': 'yes'})
self.assertRedirects(response, '/participant/')
self.assertRedirects(response, '/user/')
def test_activate(self):
response = self.client.get('/participant/1/status/activate/')
response = self.client.get('/user/1/status/activate/')
self.assertEqual(response.status_code, 302)
@ -71,45 +75,49 @@ class GroupViews(TestCase):
self.user_2.groups.add(self.delegate)
self.client = Client()
login_user = User.objects.create(username='loginusername', is_superuser=True)
login_user.reset_password('default')
self.client.login(username='loginusername', password='default')
self.client.login(username='admin', password='admin')
def test_detail(self):
response = self.client.get('/participant/group/3/')
response = self.client.get('/user/group/3/')
pattern = r'admins_first_name Administrator|aWei4ien6Se0vie0xeiv uquahx3Wohtieph9baer'
match = re.findall(pattern, response.content.decode('utf8'))
self.assertEqual(match[0], 'admins_first_name Administrator')
self.assertEqual(match[1], 'aWei4ien6Se0vie0xeiv uquahx3Wohtieph9baer')
config['participant_sort_users_by_first_name'] = True
self.assertTrue(config['participant_sort_users_by_first_name'])
response = self.client.get('/participant/group/3/')
config['users_sort_users_by_first_name'] = True
self.assertTrue(config['users_sort_users_by_first_name'])
response = self.client.get('/user/group/3/')
pattern = r'admins_first_name Administrator|aWei4ien6Se0vie0xeiv uquahx3Wohtieph9baer'
match = re.findall(pattern, response.content.decode('utf8'))
self.assertEqual(match[1], 'admins_first_name Administrator')
self.assertEqual(match[0], 'aWei4ien6Se0vie0xeiv uquahx3Wohtieph9baer')
def test_create(self):
response = self.client.get('/participant/group/new/')
self.assertTemplateUsed(response, 'participant/group_edit.html')
response = self.client.get('/user/group/new/')
self.assertTemplateUsed(response, 'users/group_form.html')
self.assertContains(response, 'New group')
response = self.client.post('/participant/group/new/', {'name': 'test_group_name_Oeli1aeXoobohv8eikai'})
self.assertRedirects(response, '/participant/group/')
response = self.client.post('/user/group/new/', {'name': 'test_group_name_Oeli1aeXoobohv8eikai'})
self.assertRedirects(response, '/user/group/')
def test_update(self):
response = self.client.get('/participant/group/1/edit/')
self.assertTemplateUsed(response, 'participant/group_edit.html')
response = self.client.get('/user/group/1/edit/')
self.assertTemplateUsed(response, 'users/group_form.html')
self.assertContains(response, 'Edit group')
response = self.client.post('/participant/group/1/edit/', {'name': 'test_group_name_ahFeicoz5jedie4Fop0U'})
self.assertRedirects(response, '/participant/group/')
response = self.client.post('/user/group/1/edit/', {'name': 'test_group_name_ahFeicoz5jedie4Fop0U'})
self.assertRedirects(response, '/user/group/')
class LockoutProtection(TestCase):
"""
Tests that a manager user can not lockout himself by doing
something that removes his last permission to manage participants. Tests
also that he can see the participant app (although there is no absolute
something that removes his last permission to manage users. Tests
also that he can see the user app (although there is no absolute
protection).
"""
def setUp(self):
@ -122,70 +130,69 @@ class LockoutProtection(TestCase):
self.assertFalse(self.user.is_superuser)
def test_delete_yourself(self):
response = self.client.get('/participant/1/del/')
self.assertRedirects(response, '/participant/1/')
response = self.client.get('/user/1/del/')
self.assertRedirects(response, '/user/1/')
self.assertTrue('You can not delete yourself.' in response.cookies['messages'].value)
response = self.client.post('/participant/1/del/',
response = self.client.post('/user/1/del/',
{'yes': 'yes'})
self.assertTrue('You can not delete yourself.' in response.cookies['messages'].value)
self.assertRedirects(response, '/participant/')
self.assertRedirects(response, '/user/')
self.assertEqual(User.objects.count(), 1)
def test_delete_last_manager_group(self):
response = self.client.get('/participant/group/4/del/')
self.assertRedirects(response, '/participant/group/4/')
response = self.client.get('/user/group/4/del/')
self.assertRedirects(response, '/user/group/4/')
self.assertTrue('You can not delete the last group containing the permission '
'to manage participants you are in.' in response.cookies['messages'].value)
response = self.client.post('/participant/group/4/del/',
'to manage users you are in.' in response.cookies['messages'].value)
response = self.client.post('/user/group/4/del/',
{'yes': 'yes'})
self.assertTrue('You can not delete the last group containing the permission '
'to manage participants you are in.' in response.cookies['messages'].value)
self.assertRedirects(response, '/participant/group/')
'to manage users you are in.' in response.cookies['messages'].value)
self.assertRedirects(response, '/user/group/')
self.assertEqual(Group.objects.count(), 4)
def test_remove_user_from_last_manager_group_via_UserUpdateView(self):
response = self.client.post('/participant/1/edit/',
response = self.client.post('/user/1/edit/',
{'username': 'arae0eQu8eeghoogeik0',
'groups': '3'})
self.assertFormError(
response=response,
form='form',
field=None,
errors='You can not remove the last group containing the permission to manage participants.')
errors='You can not remove the last group containing the permission to manage users.')
def test_remove_user_from_last_manager_group_via_GroupUpdateView(self):
User.objects.get_or_create(username='foo', pk=2)
response = self.client.post('/participant/group/4/edit/',
response = self.client.post('/user/group/4/edit/',
{'name': 'ChaeFaev4leephaiChae',
'users': '2'})
self.assertFormError(
response=response,
form='form',
field=None,
errors='You can not remove yourself from the last group containing the permission to manage participants.')
errors='You can not remove yourself from the last group containing the permission to manage users.')
def test_remove_perm_from_last_manager_group(self):
self.assertNotEqual(get_protected_perm().pk, 90)
response = self.client.post('/participant/group/4/edit/',
response = self.client.post('/user/group/4/edit/',
{'name': 'ChaeFaev4leephaiChae',
'users': '1',
'permissions': '90'})
'permissions': []})
self.assertFormError(
response=response,
form='form',
field=None,
errors='You can not remove the permission to manage participants from the last group you are in.')
errors='You can not remove the permission to manage users from the last group you are in.')
def test_remove_permission_can_see_participant_from_registered(self):
self.assertTrue(self.user.has_perm('participant.can_see_participant'))
def test_remove_permission_can_see_user_from_registered(self):
self.assertTrue(self.user.has_perm('users.can_see'))
# Remove perm from registered group
can_see_perm = Permission.objects.get(
content_type=ContentType.objects.get(app_label='participant', model='user'),
codename='can_see_participant')
content_type=ContentType.objects.get(app_label='users', model='user'),
codename='can_see')
get_registered_group().permissions.remove(can_see_perm)
# Reload user
self.user = User.objects.get(pk=1)
self.assertTrue(self.user.has_perm('participant.can_see_participant'))
self.assertTrue(self.user.has_perm('users.can_see'))
class TestUserSettings(TestCase):
@ -198,11 +205,13 @@ class TestUserSettings(TestCase):
response = self.admin_client.get('/usersettings/')
self.assertEqual(response.status_code, 200)
def test_pst(self):
def test_post(self):
response = self.admin_client.post('/usersettings/', {
'user_name': 'new_name',
'username': 'new_name',
'language': 'de'})
self.assertEqual(response.status_code, 200)
self.assertRedirects(response, '/usersettings/')
admin = User.objects.get(pk=1)
self.assertEqual(admin.username, 'new_name')