From 3527b0a75b62e0a46cf67e31dabf5c03927eeaf3 Mon Sep 17 00:00:00 2001 From: Oskar Hahn Date: Sat, 11 Oct 2014 14:34:49 +0200 Subject: [PATCH] Refactory of the participant app * New user model (used Django's AbstractBaseUser) * Renamed the app to users * removed person api See #861 Fixed #576 #478 --- openslides/__main__.py | 2 +- openslides/agenda/forms.py | 7 +- openslides/agenda/models.py | 28 +- openslides/agenda/personal_info.py | 4 +- openslides/agenda/templates/agenda/view.html | 6 +- openslides/agenda/urls.py | 2 +- openslides/agenda/views.py | 18 +- openslides/assignment/__init__.py | 2 +- openslides/assignment/forms.py | 5 +- openslides/assignment/models.py | 15 +- .../assignment/assignment_detail.html | 10 +- openslides/assignment/views.py | 23 +- openslides/core/__init__.py | 2 +- openslides/core/chatbox.py | 2 +- openslides/core/models.py | 8 + openslides/global_settings.py | 6 +- openslides/mediafile/models.py | 10 +- openslides/mediafile/views.py | 7 +- openslides/motion/__init__.py | 2 +- openslides/motion/csv_import.py | 6 +- openslides/motion/forms.py | 16 +- openslides/motion/models.py | 27 +- openslides/motion/pdf.py | 6 +- openslides/participant/middleware.py | 10 - openslides/participant/models.py | 225 --------------- .../templates/participant/group_detail.html | 47 ---- .../templates/participant/group_slide.html | 14 - .../templates/participant/widget_group.html | 28 -- openslides/participant/widgets.py | 45 --- openslides/urls.py | 9 +- openslides/{participant => users}/__init__.py | 0 openslides/{participant => users}/api.py | 15 +- .../{participant => users}/csv_import.py | 3 +- openslides/{participant => users}/forms.py | 77 ++--- .../{participant => users}/main_menu.py | 8 +- openslides/users/models.py | 136 +++++++++ openslides/{participant => users}/pdf.py | 44 +-- .../{participant => users}/search_indexes.py | 4 +- openslides/{participant => users}/signals.py | 76 ++--- openslides/{participant => users}/slides.py | 3 +- .../search/indexes/users}/user_text.txt | 0 .../templates/search/users-results.html} | 4 +- .../users/templates/users/group_detail.html | 55 ++++ .../templates/users/group_form.html} | 7 +- .../templates/users/group_list.html} | 27 +- .../templates/users}/login.html | 0 .../templates/users}/password_change.html | 0 .../templates/users}/settings.html | 0 .../templates/users}/user_detail.html | 26 +- .../templates/users/user_form.html} | 20 +- .../users}/user_form_csv_import.html | 15 +- .../templates/users}/user_form_multiple.html | 8 +- .../templates/users/user_list.html} | 58 ++-- .../templates/users}/user_slide.html | 0 .../templates/users}/widget_user.html | 4 +- openslides/{participant => users}/urls.py | 20 +- openslides/{participant => users}/views.py | 264 +++++++++--------- openslides/users/widgets.py | 25 ++ openslides/utils/person/__init__.py | 15 - openslides/utils/person/api.py | 116 -------- openslides/utils/person/forms.py | 67 ----- openslides/utils/person/models.py | 77 ----- openslides/utils/person/signals.py | 3 - openslides/utils/views.py | 7 +- tests/account/test_widgets.py | 7 +- tests/agenda/test_list_of_speakers.py | 45 ++- tests/agenda/tests.py | 14 +- tests/assignment/test_models.py | 8 +- tests/assignment/test_pdf.py | 2 +- tests/assignment/test_views.py | 8 +- tests/config/test_config.py | 11 +- tests/core/test_views.py | 6 +- tests/mediafile/tests.py | 24 +- tests/motion/test_csv_import.py | 24 +- tests/motion/test_models.py | 2 +- tests/motion/test_pdf.py | 2 +- tests/motion/test_views.py | 32 +-- tests/person_api/__init__.py | 1 - tests/person_api/models.py | 48 ---- tests/person_api/tests.py | 43 --- tests/settings.py | 11 +- tests/{participant => users}/__init__.py | 0 tests/{participant => users}/test_csv.py | 2 +- tests/{participant => users}/test_models.py | 51 +--- .../test_umlaut_user.py | 9 +- tests/{participant => users}/test_utils.py | 4 +- tests/{participant => users}/test_views.py | 133 +++++---- 87 files changed, 881 insertions(+), 1382 deletions(-) delete mode 100644 openslides/participant/middleware.py delete mode 100644 openslides/participant/models.py delete mode 100644 openslides/participant/templates/participant/group_detail.html delete mode 100644 openslides/participant/templates/participant/group_slide.html delete mode 100644 openslides/participant/templates/participant/widget_group.html delete mode 100644 openslides/participant/widgets.py rename openslides/{participant => users}/__init__.py (100%) rename openslides/{participant => users}/api.py (77%) rename openslides/{participant => users}/csv_import.py (96%) rename openslides/{participant => users}/forms.py (67%) rename openslides/{participant => users}/main_menu.py (54%) create mode 100644 openslides/users/models.py rename openslides/{participant => users}/pdf.py (76%) rename openslides/{participant => users}/search_indexes.py (68%) rename openslides/{participant => users}/signals.py (78%) rename openslides/{participant => users}/slides.py (56%) rename openslides/{participant/templates/search/indexes/participant => users/templates/search/indexes/users}/user_text.txt (100%) rename openslides/{participant/templates/search/participant-results.html => users/templates/search/users-results.html} (64%) create mode 100644 openslides/users/templates/users/group_detail.html rename openslides/{participant/templates/participant/group_edit.html => users/templates/users/group_form.html} (75%) rename openslides/{participant/templates/participant/group_overview.html => users/templates/users/group_list.html} (57%) rename openslides/{participant/templates/participant => users/templates/users}/login.html (100%) rename openslides/{participant/templates/participant => users/templates/users}/password_change.html (100%) rename openslides/{participant/templates/participant => users/templates/users}/settings.html (100%) rename openslides/{participant/templates/participant => users/templates/users}/user_detail.html (78%) rename openslides/{participant/templates/participant/edit.html => users/templates/users/user_form.html} (74%) rename openslides/{participant/templates/participant => users/templates/users}/user_form_csv_import.html (78%) rename openslides/{participant/templates/participant => users/templates/users}/user_form_multiple.html (69%) rename openslides/{participant/templates/participant/overview.html => users/templates/users/user_list.html} (74%) rename openslides/{participant/templates/participant => users/templates/users}/user_slide.html (100%) rename openslides/{participant/templates/participant => users/templates/users}/widget_user.html (90%) rename openslides/{participant => users}/urls.py (80%) rename openslides/{participant => users}/views.py (59%) create mode 100644 openslides/users/widgets.py delete mode 100644 openslides/utils/person/__init__.py delete mode 100644 openslides/utils/person/api.py delete mode 100644 openslides/utils/person/forms.py delete mode 100644 openslides/utils/person/models.py delete mode 100644 openslides/utils/person/signals.py delete mode 100644 tests/person_api/__init__.py delete mode 100644 tests/person_api/models.py delete mode 100644 tests/person_api/tests.py rename tests/{participant => users}/__init__.py (100%) rename tests/{participant => users}/test_csv.py (93%) rename tests/{participant => users}/test_models.py (53%) rename tests/{participant => users}/test_umlaut_user.py (81%) rename tests/{participant => users}/test_utils.py (89%) rename tests/{participant => users}/test_views.py (58%) diff --git a/openslides/__main__.py b/openslides/__main__.py index a7df86b3e..47d7e6b76 100644 --- a/openslides/__main__.py +++ b/openslides/__main__.py @@ -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: diff --git a/openslides/agenda/forms.py b/openslides/agenda/forms.py index fd6ad1e7e..d5654a0c2 100644 --- a/openslides/agenda/forms.py +++ b/openslides/agenda/forms.py @@ -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))) diff --git a/openslides/agenda/models.py b/openslides/agenda/models.py index 2efe091ba..2486ed7ca 100644 --- a/openslides/agenda/models.py +++ b/openslides/agenda/models.py @@ -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. diff --git a/openslides/agenda/personal_info.py b/openslides/agenda/personal_info.py index 8d132045e..b4dba94a7 100644 --- a/openslides/agenda/personal_info.py +++ b/openslides/agenda/personal_info.py @@ -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) diff --git a/openslides/agenda/templates/agenda/view.html b/openslides/agenda/templates/agenda/view.html index 99813b900..0d6b1675f 100644 --- a/openslides/agenda/templates/agenda/view.html +++ b/openslides/agenda/templates/agenda/view.html @@ -121,7 +121,7 @@ {% if speaker_dict.type == 'actual_speaker' %} {% trans 'End speach' %} {% elif speaker_dict.type == 'coming_speaker' %} - {% trans "Begin speach" %} {% endif %} {{ field }} - {% if perms.participant.can_see_participant and perms.participant.can_manage_participant %} - + {% if perms.users.can_see and perms.users.can_manage %} + {% endif %} {% if field.errors %} {{ field.errors }} diff --git a/openslides/agenda/urls.py b/openslides/agenda/urls.py index 9072e7406..9e82d5909 100644 --- a/openslides/agenda/urls.py +++ b/openslides/agenda/urls.py @@ -63,7 +63,7 @@ urlpatterns = patterns( views.SpeakerDeleteView.as_view(), name='agenda_speaker_delete'), - url(r'^(?P\d+)/speaker/(?P[^/]+)/speak/$', + url(r'^(?P\d+)/speaker/(?P[^/]+)/speak/$', views.SpeakerSpeakView.as_view(), name='agenda_speaker_speak'), diff --git a/openslides/agenda/views.py b/openslides/agenda/views.py index 95b078ea1..bef10c199 100644 --- a/openslides/agenda/views.py +++ b/openslides/agenda/views.py @@ -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() diff --git a/openslides/assignment/__init__.py b/openslides/assignment/__init__.py index f800d423c..eff95fa33 100644 --- a/openslides/assignment/__init__.py +++ b/openslides/assignment/__init__.py @@ -1 +1 @@ -from . import main_menu, personal_info, signals, slides, template, widgets # noqa +# TODO: apploader diff --git a/openslides/assignment/forms.py b/openslides/assignment/forms.py index e40b594f4..5bd16c14a 100644 --- a/openslides/assignment/forms.py +++ b/openslides/assignment/forms.py @@ -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")) diff --git a/openslides/assignment/models.py b/openslides/assignment/models.py index a6f89a46c..7e85602ee 100644 --- a/openslides/assignment/models.py +++ b/openslides/assignment/models.py @@ -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 diff --git a/openslides/assignment/templates/assignment/assignment_detail.html b/openslides/assignment/templates/assignment/assignment_detail.html index 2744f5f4a..c84469848 100644 --- a/openslides/assignment/templates/assignment/assignment_detail.html +++ b/openslides/assignment/templates/assignment/assignment_detail.html @@ -69,14 +69,14 @@ {{ person }} {% if perms.assignment.can_manage_assignment %} {% if assignment.status == "sea" or assignment.status == "vot" %} - + {% endif %} {% endif %} {% if person in assignment.elected %} | {% trans "elected" %} {% if perms.assignment.can_manage_assignment %} {% if assignment.status == "sea" or assignment.status == "vot" %} - + {% endif %} @@ -128,7 +128,7 @@ {% for person in blocked_candidates %}
  • {{ person }} - @@ -190,7 +190,7 @@ {% if candidate in assignment.elected %} {% if perms.assignment.can_manage_assignment %} - {% else %} @@ -199,7 +199,7 @@ {% endif %} {% else %} {% if perms.assignment.can_manage_assignment %} - {% endif %} {% endif %} diff --git a/openslides/assignment/views.py b/openslides/assignment/views.py index 845bfa873..b71b00e50 100644 --- a/openslides/assignment/views.py +++ b/openslides/assignment/views.py @@ -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("%s \ %s" % (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( diff --git a/openslides/core/__init__.py b/openslides/core/__init__.py index a5dd689db..b0ad1bf8f 100644 --- a/openslides/core/__init__.py +++ b/openslides/core/__init__.py @@ -1 +1 @@ -from . import main_menu, signals, slides, widgets # noqa +# TODO: apploading diff --git a/openslides/core/chatbox.py b/openslides/core/chatbox.py index 6b5e3ccf6..0dd1e486c 100644 --- a/openslides/core/chatbox.py +++ b/openslides/core/chatbox.py @@ -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: diff --git a/openslides/core/models.py b/openslides/core/models.py index 4fb41b227..82c1d6668 100644 --- a/openslides/core/models.py +++ b/openslides/core/models.py @@ -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 diff --git a/openslides/global_settings.py b/openslides/global_settings.py index 0e1453feb..8725496fd 100644 --- a/openslides/global_settings.py +++ b/openslides/global_settings.py @@ -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', ) diff --git a/openslides/mediafile/models.py b/openslides/mediafile/models.py index a9a159b33..37887a934 100644 --- a/openslides/mediafile/models.py +++ b/openslides/mediafile/models.py @@ -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.""" diff --git a/openslides/mediafile/views.py b/openslides/mediafile/views.py index 27ee77164..e58ec080a 100644 --- a/openslides/mediafile/views.py +++ b/openslides/mediafile/views.py @@ -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 diff --git a/openslides/motion/__init__.py b/openslides/motion/__init__.py index 9e426cdbe..498f2dd6f 100644 --- a/openslides/motion/__init__.py +++ b/openslides/motion/__init__.py @@ -1 +1 @@ -from . import main_menu, personal_info, signals, slides, widgets # noqa +# TODO: Apploading diff --git a/openslides/motion/csv_import.py b/openslides/motion/csv_import.py index b1d83844b..9989fc28c 100644 --- a/openslides/motion/csv_import.py +++ b/openslides/motion/csv_import.py @@ -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 diff --git a/openslides/motion/forms.py b/openslides/motion/forms.py index 85d348c79..028a602d6 100644 --- a/openslides/motion/forms.py +++ b/openslides/motion/forms.py @@ -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.')) diff --git a/openslides/motion/models.py b/openslides/motion/models.py index c206ac536..bc7ad0e2d 100644 --- a/openslides/motion/models.py +++ b/openslides/motion/models.py @@ -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 diff --git a/openslides/motion/pdf.py b/openslides/motion/pdf.py index ef942ecd8..145c43aea 100644 --- a/openslides/motion/pdf.py +++ b/openslides/motion/pdf.py @@ -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 diff --git a/openslides/participant/middleware.py b/openslides/participant/middleware.py deleted file mode 100644 index 5a8530584..000000000 --- a/openslides/participant/middleware.py +++ /dev/null @@ -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 diff --git a/openslides/participant/models.py b/openslides/participant/models.py deleted file mode 100644 index 51f4bdaee..000000000 --- a/openslides/participant/models.py +++ /dev/null @@ -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') diff --git a/openslides/participant/templates/participant/group_detail.html b/openslides/participant/templates/participant/group_detail.html deleted file mode 100644 index 641a1c18d..000000000 --- a/openslides/participant/templates/participant/group_detail.html +++ /dev/null @@ -1,47 +0,0 @@ -{% extends "base.html" %} - -{% load i18n %} -{% load tags %} - -{% block title %}{% trans group.name %} – {{ block.super }}{% endblock %} - -{% block content %} - -

    {% trans group.name %} - - {% trans "Back to overview" %} - - {% if perms.core.can_manage_projector %} - - - - {% endif %} - {% if perms.participant.can_manage_participant and group.pk != 1 and group.pk != 2 %} - - {% endif %} - -

    - -

    {{ group.description }}

    - -

    {% trans "Members" %}

    - -
      -{% for member in group_members %} -
    1. {{ member }}
    2. -{% empty %} -

      {% trans "No members available." %}

      -{% endfor %} -
    - -{% endblock %} diff --git a/openslides/participant/templates/participant/group_slide.html b/openslides/participant/templates/participant/group_slide.html deleted file mode 100644 index 8a2d8199c..000000000 --- a/openslides/participant/templates/participant/group_slide.html +++ /dev/null @@ -1,14 +0,0 @@ -{% load i18n %} - -

    - {% trans group.name %} - {% trans "Group" %} -

    - -

    {{ group.user_set.all.count }} {% trans "participants" %}

    - -
      -{% for member in group.user_set.all %} -
    1. {{ member }}
    2. -{% endfor %} -
    diff --git a/openslides/participant/templates/participant/widget_group.html b/openslides/participant/templates/participant/widget_group.html deleted file mode 100644 index b81462c8f..000000000 --- a/openslides/participant/templates/participant/widget_group.html +++ /dev/null @@ -1,28 +0,0 @@ -{% extends 'core/widget.html' %} - -{% load i18n %} -{% load tags %} - -{% block content %} - -{% endblock %} diff --git a/openslides/participant/widgets.py b/openslides/participant/widgets.py deleted file mode 100644 index 4d8a2a942..000000000 --- a/openslides/participant/widgets.py +++ /dev/null @@ -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) diff --git a/openslides/urls.py b/openslides/urls.py index 521560278..774e2ba0c 100644 --- a/openslides/urls.py +++ b/openslides/urls.py @@ -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'), ) diff --git a/openslides/participant/__init__.py b/openslides/users/__init__.py similarity index 100% rename from openslides/participant/__init__.py rename to openslides/users/__init__.py diff --git a/openslides/participant/api.py b/openslides/users/api.py similarity index 77% rename from openslides/participant/api.py rename to openslides/users/api.py index b95c39f8a..0b0b34b8f 100644 --- a/openslides/participant/api.py +++ b/openslides/users/api.py @@ -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') diff --git a/openslides/participant/csv_import.py b/openslides/users/csv_import.py similarity index 96% rename from openslides/participant/csv_import.py rename to openslides/users/csv_import.py index 5e52bcb22..66f1e848e 100644 --- a/openslides/participant/csv_import.py +++ b/openslides/users/csv_import.py @@ -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 = '' diff --git a/openslides/participant/forms.py b/openslides/users/forms.py similarity index 67% rename from openslides/participant/forms.py rename to openslides/users/forms.py index 3dbd426cb..8a5d0bddf 100644 --- a/openslides/participant/forms.py +++ b/openslides/users/forms.py @@ -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') diff --git a/openslides/participant/main_menu.py b/openslides/users/main_menu.py similarity index 54% rename from openslides/participant/main_menu.py rename to openslides/users/main_menu.py index 975b6f161..18a349d07 100644 --- a/openslides/participant/main_menu.py +++ b/openslides/users/main_menu.py @@ -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' diff --git a/openslides/users/models.py b/openslides/users/models.py new file mode 100644 index 000000000..1166a76d8 --- /dev/null +++ b/openslides/users/models.py @@ -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) diff --git a/openslides/participant/pdf.py b/openslides/users/pdf.py similarity index 76% rename from openslides/participant/pdf.py rename to openslides/users/pdf.py index ddaa4b8c9..70cee871f 100644 --- a/openslides/participant/pdf.py +++ b/openslides/users/pdf.py @@ -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', '
    '), + pdf.append(Paragraph(users_pdf_welcometitle, stylesheet['h2'])) + pdf.append(Paragraph(users_pdf_welcometext.replace('\r\n', '
    '), stylesheet['Paragraph12'])) pdf.append(PageBreak()) return pdf diff --git a/openslides/participant/search_indexes.py b/openslides/users/search_indexes.py similarity index 68% rename from openslides/participant/search_indexes.py rename to openslides/users/search_indexes.py index f142f19a8..380eda9e2 100644 --- a/openslides/participant/search_indexes.py +++ b/openslides/users/search_indexes.py @@ -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 diff --git a/openslides/participant/signals.py b/openslides/users/signals.py similarity index 78% rename from openslides/participant/signals.py rename to openslides/users/signals.py index cfa0a557e..5c44da2fc 100644 --- a/openslides/participant/signals.py +++ b/openslides/users/signals.py @@ -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() diff --git a/openslides/participant/slides.py b/openslides/users/slides.py similarity index 56% rename from openslides/participant/slides.py rename to openslides/users/slides.py index 5ee3edf28..1bdcfa892 100644 --- a/openslides/participant/slides.py +++ b/openslides/users/slides.py @@ -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') diff --git a/openslides/participant/templates/search/indexes/participant/user_text.txt b/openslides/users/templates/search/indexes/users/user_text.txt similarity index 100% rename from openslides/participant/templates/search/indexes/participant/user_text.txt rename to openslides/users/templates/search/indexes/users/user_text.txt diff --git a/openslides/participant/templates/search/participant-results.html b/openslides/users/templates/search/users-results.html similarity index 64% rename from openslides/participant/templates/search/participant-results.html rename to openslides/users/templates/search/users-results.html index 8fb97371d..a84ac3f34 100644 --- a/openslides/participant/templates/search/participant-results.html +++ b/openslides/users/templates/search/users-results.html @@ -1,10 +1,10 @@ {% load i18n %} {% load highlight %} -{% if perms.participant.can_see_participant %} +{% if perms.users.can_see %}
  • {{ result.object }}
    - {% trans "Participant" %}
    + {% trans "User" %}
    {% highlight result.text with request.GET.q %}
  • {% endif %} diff --git a/openslides/users/templates/users/group_detail.html b/openslides/users/templates/users/group_detail.html new file mode 100644 index 000000000..267af9f66 --- /dev/null +++ b/openslides/users/templates/users/group_detail.html @@ -0,0 +1,55 @@ +{% extends "base.html" %} + +{% load i18n %} +{% load tags %} + +{% block title %}{% trans group.name %} – {{ block.super }}{% endblock %} + +{% block content %} + +

    {% trans group.name %} + + + + {% trans "Back to overview" %} + + {% if perms.users.can_manage and group.pk != 1 and group.pk != 2 %} + + {% endif %} + +

    + +

    {{ group.description }}

    + +

    {% trans "Members" %}

    + +
      +{% for member in group_members %} +
    1. {{ member }}
    2. +{% empty %} +

      {% trans "No members available." %}

      +{% endfor %} +
    + +{% endblock %} diff --git a/openslides/participant/templates/participant/group_edit.html b/openslides/users/templates/users/group_form.html similarity index 75% rename from openslides/participant/templates/participant/group_edit.html rename to openslides/users/templates/users/group_form.html index 026ee268b..33b8df1d1 100644 --- a/openslides/participant/templates/participant/group_edit.html +++ b/openslides/users/templates/users/group_form.html @@ -19,7 +19,10 @@ {% trans "New group" %} {% endif %} - {% trans "Back to overview" %} + + + {% trans "Back to overview" %} + @@ -27,7 +30,7 @@ {% include "form.html" %}

    {% include "formbuttons_saveapply.html" %} - + {% trans 'Cancel' %}

    diff --git a/openslides/participant/templates/participant/group_overview.html b/openslides/users/templates/users/group_list.html similarity index 57% rename from openslides/participant/templates/participant/group_overview.html rename to openslides/users/templates/users/group_list.html index 952139daf..d1a2f9bf9 100644 --- a/openslides/participant/templates/participant/group_overview.html +++ b/openslides/users/templates/users/group_list.html @@ -19,8 +19,16 @@

    {% trans "Groups" %} - {% trans "New" %} - {% trans "Back to participants overview" %} + + + {% trans "New" %} + + + + + {% trans "Back to users overview" %} + +

    @@ -37,29 +45,22 @@ {{ group.pk }} {% if group.pk == 1 or group.pk == 2 %} - + {% endif %} - {% trans group.name %} + {% trans group.name %} {{ group.user_set.all.count }} - {% if perms.core.can_manage_projector %} - - - - {% endif %} - + {% if group.pk != 1 and group.pk != 2 %} - + {% endif %} diff --git a/openslides/participant/templates/participant/login.html b/openslides/users/templates/users/login.html similarity index 100% rename from openslides/participant/templates/participant/login.html rename to openslides/users/templates/users/login.html diff --git a/openslides/participant/templates/participant/password_change.html b/openslides/users/templates/users/password_change.html similarity index 100% rename from openslides/participant/templates/participant/password_change.html rename to openslides/users/templates/users/password_change.html diff --git a/openslides/participant/templates/participant/settings.html b/openslides/users/templates/users/settings.html similarity index 100% rename from openslides/participant/templates/participant/settings.html rename to openslides/users/templates/users/settings.html diff --git a/openslides/participant/templates/participant/user_detail.html b/openslides/users/templates/users/user_detail.html similarity index 78% rename from openslides/participant/templates/participant/user_detail.html rename to openslides/users/templates/users/user_detail.html index 3a0100e8d..f1f38a33b 100644 --- a/openslides/participant/templates/participant/user_detail.html +++ b/openslides/users/templates/users/user_detail.html @@ -9,23 +9,33 @@

    {{ shown_user.clean_name }} - {% trans "Back to overview" %} + + + + {% trans "Back to overview" %} + + {% if perms.core.can_manage_projector %} - + {% endif %} - {% if perms.participant.can_manage_participant %} + {% if perms.users.can_manage %} {% 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 %} - {% if perms.participant.can_manage_participant %} + {% if perms.users.can_manage %}
    {% trans "Administrative data" %} @@ -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 %} diff --git a/openslides/participant/templates/participant/edit.html b/openslides/users/templates/users/user_form.html similarity index 74% rename from openslides/participant/templates/participant/edit.html rename to openslides/users/templates/users/user_form.html index 4807d013b..38df1d19c 100644 --- a/openslides/participant/templates/participant/edit.html +++ b/openslides/users/templates/users/user_form.html @@ -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 %}

    {% if edit_user %} - {% trans "Edit participant" %} + {% trans "Edit user" %} {% else %} - {% trans "New participant" %} + {% trans "New user" %} {% endif %} - {% trans "Back to overview" %} + + + {% trans "Back to overview" %} +

    @@ -47,12 +50,15 @@ {% include "form.html" %} {% if edit_user %}

    - {% trans 'Reset to First Password' %} + + + {% trans 'Reset to First Password' %} +

    {% endif %}

    {% include "formbuttons_saveapply.html" %} - + {% trans 'Cancel' %}

    diff --git a/openslides/participant/templates/participant/user_form_csv_import.html b/openslides/users/templates/users/user_form_csv_import.html similarity index 78% rename from openslides/participant/templates/participant/user_form_csv_import.html rename to openslides/users/templates/users/user_form_csv_import.html index 22d1581de..c4692d2c6 100644 --- a/openslides/participant/templates/participant/user_form_csv_import.html +++ b/openslides/users/templates/users/user_form_csv_import.html @@ -2,18 +2,21 @@ {% load i18n %} -{% block title %}{% trans "Import participants" %} – {{ block.super }}{% endblock %} +{% block title %}{% trans "Import users" %} – {{ block.super }}{% endblock %} {% block content %}

    - {% trans 'Import participants' %} + {% trans 'Import users' %} - {% trans "Back to overview" %} + + + {% trans "Back to overview" %} +

    -

    {% trans 'Select a CSV file to import participants' %}.

    - +

    {% trans 'Select a CSV file to import users' %}.

    +

    {% trans 'Please note' %}:

    • @@ -40,7 +43,7 @@ - + {% trans 'Cancel' %}

      diff --git a/openslides/participant/templates/participant/user_form_multiple.html b/openslides/users/templates/users/user_form_multiple.html similarity index 69% rename from openslides/participant/templates/participant/user_form_multiple.html rename to openslides/users/templates/users/user_form_multiple.html index fff47d803..883a8db95 100644 --- a/openslides/participant/templates/participant/user_form_multiple.html +++ b/openslides/users/templates/users/user_form_multiple.html @@ -3,14 +3,14 @@ {% load i18n %} {% block title %} - {% trans 'New multiple participants' %} – {{ block.super }} + {% trans 'New multiple users' %} – {{ block.super }} {% endblock %} {% block content %}

      - {% trans 'New multiple participants' %} + {% trans 'New multiple users' %} - + {% trans 'Back to overview' %} @@ -19,7 +19,7 @@ {% include 'form.html' %}

      {% include 'formbuttons_save.html' %} - + {% trans 'Cancel' %}

      diff --git a/openslides/participant/templates/participant/overview.html b/openslides/users/templates/users/user_list.html similarity index 74% rename from openslides/participant/templates/participant/overview.html rename to openslides/users/templates/users/user_list.html index 6be4e004a..e23250d42 100644 --- a/openslides/participant/templates/participant/overview.html +++ b/openslides/users/templates/users/user_list.html @@ -4,7 +4,7 @@ {% load staticfiles %} {% load tags %} -{% block title %}{% trans "Participants" %} – {{ block.super }}{% endblock %} +{% block title %}{% trans "Users" %} – {{ block.super }}{% endblock %} {% block header %} @@ -17,27 +17,51 @@ {% block content %}

      - {% trans "Participants" %} + {% trans "Users" %} - {% if perms.participant.can_manage_participant %} - {% trans "New" %} - {% trans 'New multiple' %} - {% trans "Groups" %} - {% trans 'Import' %} + {% if perms.users.can_manage %} + + + {% trans "New" %} + + + + {% trans 'New multiple' %} + + + + {% trans "Groups" %} + + + + {% trans 'Import' %} + {% endif %} - {% if perms.participant.can_see_participant and perms.participant.can_manage_participant %} + {% if perms.users.can_see and perms.users.can_manage %} {% else %} - PDF + PDF {% endif %}

      @@ -51,7 +75,7 @@ {% trans "Structure level" %} {% trans "Group" %} {% trans "Committee" %} - {% if perms.participant.can_manage_participant %} + {% if perms.users.can_manage %} {% trans "Comment" %} {% trans "Last Login" %} {% trans "Actions" %} @@ -61,7 +85,7 @@ {% for user in users %} - {% if perms.participant.can_manage_participant %} + {% if perms.users.can_manage %} {% if user != request_user %} {{ user.title }} - {% if 'participant_sort_users_by_first_name'|get_config %} + {% if 'users_sort_users_by_first_name'|get_config %} {{ user.first_name }} {{ user.last_name }} {% else %} {{ user.last_name }}{% if user.last_name and user.first_name %},{% endif %} {{ user.first_name }} @@ -93,7 +117,7 @@ {% endfor %} {{ user.committee }} - {% if perms.participant.can_manage_participant %} + {% if perms.users.can_manage %} {% if user.comment %} @@ -110,7 +134,7 @@ {% if perms.core.can_manage_projector %} + rel="tooltip" data-original-title="{% trans 'Show user' %}"> {% endif %} diff --git a/openslides/participant/templates/participant/user_slide.html b/openslides/users/templates/users/user_slide.html similarity index 100% rename from openslides/participant/templates/participant/user_slide.html rename to openslides/users/templates/users/user_slide.html diff --git a/openslides/participant/templates/participant/widget_user.html b/openslides/users/templates/users/widget_user.html similarity index 90% rename from openslides/participant/templates/participant/widget_user.html rename to openslides/users/templates/users/widget_user.html index e9c002f0b..3e33f0795 100644 --- a/openslides/participant/templates/participant/widget_user.html +++ b/openslides/users/templates/users/widget_user.html @@ -11,7 +11,7 @@ rel="tooltip" data-original-title="{% trans 'Show' %}">   - {% if perms.participant.can_manage_participant %} + {% if perms.users.can_manage %} @@ -24,7 +24,7 @@ {{ user }}
    • {% empty %} -
    • {% trans 'No participants available.' %}
    • +
    • {% trans 'No users available.' %}
    • {% endfor %}
    {% endblock %} diff --git a/openslides/participant/urls.py b/openslides/users/urls.py similarity index 80% rename from openslides/participant/urls.py rename to openslides/users/urls.py index 2595b33b4..fe3aee262 100644 --- a/openslides/participant/urls.py +++ b/openslides/users/urls.py @@ -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\d+)/$', views.GroupDetailView.as_view(), - name='user_group_view'), + name='group_detail'), url(r'^group/(?P\d+)/edit/$', views.GroupUpdateView.as_view(), - name='user_group_edit'), + name='group_update'), url(r'^group/(?P\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'), ) diff --git a/openslides/participant/views.py b/openslides/users/views.py similarity index 59% rename from openslides/participant/views.py rename to openslides/users/views.py index ecd2f81e0..0f58b13fd 100644 --- a/openslides/participant/views.py +++ b/openslides/users/views.py @@ -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 diff --git a/openslides/users/widgets.py b/openslides/users/widgets.py new file mode 100644 index 000000000..73b73bacc --- /dev/null +++ b/openslides/users/widgets.py @@ -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) diff --git a/openslides/utils/person/__init__.py b/openslides/utils/person/__init__.py deleted file mode 100644 index 5c0f0a86d..000000000 --- a/openslides/utils/person/__init__.py +++ /dev/null @@ -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' diff --git a/openslides/utils/person/api.py b/openslides/utils/person/api.py deleted file mode 100644 index 27ff80b06..000000000 --- a/openslides/utils/person/api.py +++ /dev/null @@ -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] diff --git a/openslides/utils/person/forms.py b/openslides/utils/person/forms.py deleted file mode 100644 index 18aa139aa..000000000 --- a/openslides/utils/person/forms.py +++ /dev/null @@ -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) diff --git a/openslides/utils/person/models.py b/openslides/utils/person/models.py deleted file mode 100644 index af25f73f2..000000000 --- a/openslides/utils/person/models.py +++ /dev/null @@ -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) diff --git a/openslides/utils/person/signals.py b/openslides/utils/person/signals.py deleted file mode 100644 index a51511155..000000000 --- a/openslides/utils/person/signals.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.dispatch import Signal - -receive_persons = Signal(providing_args=['person_prefix_filter', 'id_filter']) diff --git a/openslides/utils/views.py b/openslides/utils/views.py index 7a9f92b02..d46922ad4 100644 --- a/openslides/utils/views.py +++ b/openslides/utils/views.py @@ -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): """ diff --git a/tests/account/test_widgets.py b/tests/account/test_widgets.py index 6abf27d16..c1851c1a7 100644 --- a/tests/account/test_widgets.py +++ b/tests/account/test_widgets.py @@ -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) diff --git a/tests/agenda/test_list_of_speakers.py b/tests/agenda/test_list_of_speakers.py index 2efa015e5..cac93b394 100644 --- a/tests/agenda/test_list_of_speakers.py +++ b/tests/agenda/test_list_of_speakers.py @@ -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): diff --git a/tests/agenda/tests.py b/tests/agenda/tests.py index f5de071d4..57c0a9c5a 100644 --- a/tests/agenda/tests.py +++ b/tests/agenda/tests.py @@ -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/') diff --git a/tests/assignment/test_models.py b/tests/assignment/test_models.py index 92b2146dd..abc112038 100644 --- a/tests/assignment/test_models.py +++ b/tests/assignment/test_models.py @@ -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()) diff --git a/tests/assignment/test_pdf.py b/tests/assignment/test_pdf.py index 88ea6bc00..e0995e92c 100644 --- a/tests/assignment/test_pdf.py +++ b/tests/assignment/test_pdf.py @@ -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 diff --git a/tests/assignment/test_views.py b/tests/assignment/test_views.py index d4ef3660e..1db52159f 100644 --- a/tests/assignment/test_views.py +++ b/tests/assignment/test_views.py @@ -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') diff --git a/tests/config/test_config.py b/tests/config/test_config.py index 0634b2840..42576742c 100644 --- a/tests/config/test_config.py +++ b/tests/config/test_config.py @@ -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 diff --git a/tests/core/test_views.py b/tests/core/test_views.py index 2321ff164..c55a109ce 100644 --- a/tests/core/test_views.py +++ b/tests/core/test_views.py @@ -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') diff --git a/tests/mediafile/tests.py b/tests/mediafile/tests.py index 54185ffa9..ae0239da2 100644 --- a/tests/mediafile/tests.py +++ b/tests/mediafile/tests.py @@ -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, '', status_code=200) + self.assertContains(response, '', status_code=200) self.assertTemplateUsed(response, 'mediafile/mediafile_form.html') + response = clients['client_vip_user'].get('/mediafile/new/') self.assertNotContains(response, '