diff --git a/euth/actions/admin.py b/euth/actions/admin.py
deleted file mode 100644
index 96c15fdf9..000000000
--- a/euth/actions/admin.py
+++ /dev/null
@@ -1,5 +0,0 @@
-from django.contrib import admin
-
-from .models import Action
-
-admin.site.register(Action)
diff --git a/euth/actions/migrations/0004_delete_action.py b/euth/actions/migrations/0004_delete_action.py
new file mode 100644
index 000000000..58bc08e00
--- /dev/null
+++ b/euth/actions/migrations/0004_delete_action.py
@@ -0,0 +1,16 @@
+# Generated by Django 3.2.20 on 2024-09-09 13:51
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('euth_actions', '0003_restrict_verbs_by_choices'),
+ ]
+
+ operations = [
+ migrations.DeleteModel(
+ name='Action',
+ ),
+ ]
diff --git a/euth/actions/models.py b/euth/actions/models.py
index b36dd115a..e69de29bb 100644
--- a/euth/actions/models.py
+++ b/euth/actions/models.py
@@ -1,68 +0,0 @@
-from django.conf import settings
-from django.contrib.contenttypes.fields import GenericForeignKey
-from django.contrib.contenttypes.models import ContentType
-from django.db import models
-from django.utils import timezone
-
-from adhocracy4.projects.models import Project
-
-from . import verbs
-
-
-class Action(models.Model):
-
- # actor, if actor is None the action was create by the system
- actor = models.ForeignKey(settings.AUTH_USER_MODEL,
- on_delete=models.CASCADE,
- blank=True,
- null=True)
-
- # target eg. idea
- target_content_type = models.ForeignKey(ContentType,
- blank=True,
- null=True,
- on_delete=models.CASCADE,
- related_name='target')
- target_object_id = models.CharField(max_length=255, blank=True, null=True)
- target = GenericForeignKey(
- ct_field='target_content_type', fk_field='target_object_id')
-
- # action object eg. comment
- action_object_content_type = models.ForeignKey(
- ContentType,
- blank=True,
- null=True,
- on_delete=models.CASCADE,
- related_name='action_object')
- action_object_object_id = models.CharField(
- max_length=255, blank=True, null=True)
- action_object = GenericForeignKey(
- ct_field='action_object_content_type',
- fk_field='action_object_object_id')
-
- # project
- project = models.ForeignKey(
- Project, on_delete=models.CASCADE, blank=True, null=True)
-
- timestamp = models.DateTimeField(default=timezone.now)
- public = models.BooleanField(default=True, db_index=True)
- verb = models.CharField(max_length=255, db_index=True, choices=verbs.all())
- description = models.TextField(blank=True, null=True)
-
- def __str__(self):
-
- ctx = {
- 'actor': self.actor.username if self.actor else 'system',
- 'verb': self.verb,
- 'action_object': self.action_object,
- 'target': self.target
- }
-
- if self.target:
- if self.action_object:
- return '{actor} {verb} {action_object} on {target}'.format(
- **ctx)
- return '{actor} {verb} {target}'.format(**ctx)
- if self.action_object:
- return '{actor} {verb} {action_object}'.format(**ctx)
- return '{actor} {verb}'.format(**ctx)
diff --git a/euth/actions/signals.py b/euth/actions/signals.py
index 4326a6b31..e69de29bb 100644
--- a/euth/actions/signals.py
+++ b/euth/actions/signals.py
@@ -1,49 +0,0 @@
-from django.apps import apps
-from django.conf import settings
-from django.db.models.signals import post_save
-from django.dispatch import receiver
-
-from adhocracy4.projects.models import Project
-from euth.actions import emails
-
-from . import verbs
-from .models import Action
-
-
-def add_action(sender, instance, created, **kwargs):
- verb = verbs.CREATE if created else verbs.UPDATE
- actor = instance.creator
-
- action = Action(
- actor=actor,
- verb=verb,
- action_object=instance
- )
-
- if hasattr(instance, 'project') and instance.project.__class__ is Project:
- action.project = instance.project
- action.target = instance.project
-
- if hasattr(instance, 'content_object'):
- action.target = instance.content_object
-
- action.save()
-
-
-for app, model in settings.ACTIONABLE:
- post_save.connect(add_action, apps.get_model(app, model))
-
-
-@receiver(post_save, sender=Action)
-def send_notification(sender, instance, created, **kwargs):
- action = instance
-
- if instance.verb == verbs.CREATE:
- emails.NotifyCreatorEmail.send(action)
-
- if action.target_content_type.model_class() is Project:
- emails.NotifyModeratorsEmail.send(action)
- emails.NotifyFollowersOnNewIdeaCreated.send(action)
-
- if instance.verb == verbs.COMPLETE:
- emails.NotifyFollowersOnPhaseIsOverSoonEmail.send(action)
diff --git a/euth/blueprints/__init__.py b/euth/blueprints/__init__.py
deleted file mode 100644
index 70a416075..000000000
--- a/euth/blueprints/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-default_app_config = 'euth.blueprints.apps.Config'
diff --git a/euth/blueprints/apps.py b/euth/blueprints/apps.py
deleted file mode 100644
index 0bb1a18d0..000000000
--- a/euth/blueprints/apps.py
+++ /dev/null
@@ -1,6 +0,0 @@
-from django.apps import AppConfig
-
-
-class Config(AppConfig):
- name = 'euth.blueprints'
- label = 'euth_blueprints'
diff --git a/euth/blueprints/blueprints.py b/euth/blueprints/blueprints.py
deleted file mode 100644
index 22429f65e..000000000
--- a/euth/blueprints/blueprints.py
+++ /dev/null
@@ -1,362 +0,0 @@
-from collections import namedtuple
-from enum import Enum
-from enum import unique
-
-from django.utils.translation import gettext_lazy as _
-
-from adhocracy4.polls import phases as poll_phases
-from euth.communitydebate import phases as communitydebate_phases
-from euth.documents import phases as documents_phases
-from euth.ideas import phases as ideas_phases
-from euth.maps import phases as map_phases
-
-from .names import BlueprintNames
-
-
-class BlueprintEnum(Enum):
- def __new__(cls, value, label):
- obj = object.__new__(cls)
- obj._value_ = len(cls.__members__) + 1
- obj._value = value
- obj.label = label
- return obj
-
- @property
- def value(self):
- return self._value
-
- @classmethod
- def get(cls, value):
- return next(m for m in cls if m._value == value)
-
-
-@unique
-class Aim(Enum):
- collect_ideas = (
- 'collect_ideas',
- _('Create and collect new ideas or visions.'),
- [_('(Urban) planning processes'),
- _('Develop concepts or guiding principles')]
- )
- discuss_topic = (
- 'discuss_topic',
- _('Gather feedback on a topic and discuss it in greater detail.'),
- [_('Discuss existing concepts or plans'),
- _('Develop solutions for existing problems')]
- )
- agenda_setting = (
- 'agenda_setting',
- _('Agenda Setting'),
- [_('Set the agenda of an event, a process, a project etc.')]
- )
- design_place = (
- 'design_place',
- _('Design a place.'),
- [_('(Urban) planning processes'),
- _('Small scale design projects, e.g. renew your premises')]
- )
- run_survey = (
- 'run_survey',
- _('Learn about what people like most.'),
- [_('Opinion polls, majority votes etc.')]
- )
- run_competition = (
- 'run_competition',
- _('Run a competition.'),
- [_('All sorts of competitions, '
- 'like idea contests etc.')]
- )
- work_document = (
- 'work_document',
- _('Work together with other people on a text document.'),
- [_('Draft or revise statutes, articles, charters etc.'),
- _('Involve different authors in writing a shared text')]
- )
- communitydebate = (
- 'communitydebate',
- _('Find and debate topics and questions.'),
- [_('Start a discussion and moderate it'),
- _('Participants can upload documents to deepen the exchange '
- 'and share information')]
- )
-
- def __new__(cls, value, label, examples):
- obj = object.__new__(cls)
- obj._value_ = value
- obj.label = label
- obj.examples = examples
- return obj
-
-
-@unique
-class Result(BlueprintEnum):
- collect_ideas = 3, _('Collection of commented ideas')
- majority_vote = 2, _('Majority vote')
- both = 1, _('Both')
-
-
-@unique
-class Experience(BlueprintEnum):
- five_projects = 4, _('More than 5 participative projects')
- two_projects = 3, _('More than 2 participative projects')
- one_project = 2, _('1-2 participative projects')
- no_projects = 1, _('I have no experiences in organising '
- 'participative projects')
-
-
-class Motivation(BlueprintEnum):
- high = 4, _('High motivation')
- medium = 3, _('Medium motivation')
- low = 2, _('Low motivation')
- not_found = 1, _('No motivation')
- unkown = 2, _('I don\'t know.')
-
-
-@unique
-class Participants(BlueprintEnum):
- few = 0, '< 25'
- some = 1, '25-50'
- many = 2, '50+'
-
-
-@unique
-class Duration(BlueprintEnum):
- one_weeks = 0, _('1-2 weeks')
- two_weeks = 1, _('2-4 weeks')
- four_weeks = 2, _('more than 4 weeks')
-
-
-@unique
-class Scope(BlueprintEnum):
- local = 0, _('Local')
- regional = 1, _('Regional')
- national = 2, _('National or international')
-
-
-class Accessibility(BlueprintEnum):
- very_easy = 1, _('Very easy to access')
- easy = 2, _('Easy to access')
- hard = 3, _('Hard to access')
- very_hard = 4, _('Very hard to access')
- unkown = 3, _('I don\'t know')
-
-
-ComplexityVector = namedtuple(
- 'ComplexityVector', [
- 'participants', 'duration', 'scope'
- ]
-)
-
-
-COMPLEXITY_VECTOR_AC = ComplexityVector(
- participants=(0, 0.5),
- duration=(0, 1),
- scope=(0, 0.5)
-)
-
-COMPLEXITY_VECTOR_BD = ComplexityVector(
- participants=(0, 1),
- duration=(0, 1),
- scope=(0, 1)
-)
-
-COMPLEXITY_VECTOR_E = ComplexityVector(
- participants=(0, 1 / 3),
- duration=(0, 0),
- scope=(0, 1 / 3)
-)
-
-COMPLEXITY_VECTOR_F = ComplexityVector(
- participants=(1, 1),
- duration=(0, 1),
- scope=(0, 0)
-)
-
-Requirements = namedtuple(
- 'Requirements', [
- 'aims', 'results', 'experience', 'motivation'
- ])
-
-
-Blueprint = namedtuple(
- 'Blueprint', [
- 'title', 'description', 'content', 'image', 'settings_model',
- 'requirements', 'complexity', 'type'
- ])
-
-
-blueprints = [
- (BlueprintNames.brainstorming.value,
- Blueprint(
- title=_('Brainstorming'),
- description=_('Collect ideas, questions and input concerning '
- 'a problem or a question from a wide array of people.'),
- content=[
- ideas_phases.CollectPhase(),
- ],
- image='images/brainstorming.png',
- settings_model=None,
- requirements=Requirements(
- aims=[Aim.collect_ideas, Aim.discuss_topic],
- results=[Result.collect_ideas],
- experience=Experience.no_projects,
- motivation=Motivation.not_found
- ),
- complexity=COMPLEXITY_VECTOR_AC,
- type=BlueprintNames.brainstorming.name
- )),
- (BlueprintNames.map_brainstorming.value,
- Blueprint(
- title=_('Spatial Brainstorming'),
- description=_('Collect ideas, questions and input concerning a '
- 'problem or a question from a wide array of people.'),
- content=[
- map_phases.CollectPhase(),
- ],
- image='images/spatial_brainstorming.png',
- settings_model=('a4maps', 'AreaSettings'),
- requirements=Requirements(
- aims=[Aim.design_place],
- results=[Result.collect_ideas],
- experience=Experience.no_projects,
- motivation=Motivation.not_found
- ),
- complexity=COMPLEXITY_VECTOR_AC,
- type=BlueprintNames.map_brainstorming.name
- )),
- (BlueprintNames.idea_challenge.value,
- Blueprint(
- title=_('Idea Challenge'),
- description=_('Run a challenge and find the best ideas to solve '
- 'a particular problem.'),
- content=[
- ideas_phases.CollectPhase(),
- ideas_phases.RatingPhase(),
- ],
- image='images/challenge.png',
- settings_model=None,
- requirements=Requirements(
- aims=[Aim.run_competition, Aim.run_survey],
- results=list(Result),
- experience=Experience.one_project,
- motivation=Motivation.low
- ),
- complexity=COMPLEXITY_VECTOR_BD,
- type=BlueprintNames.idea_challenge.name
- )),
- (BlueprintNames.map_idea_challenge.value,
- Blueprint(
- title=_('Spatial Idea Challenge'),
- description=_('Run a challenge concerning a certain area or space in '
- 'your community and find the best ideas to solve a '
- 'particular problem.'),
- content=[
- map_phases.CollectPhase(),
- map_phases.RatingPhase(),
- ],
- image='images/spatial_challenge.png',
- settings_model=('a4maps', 'AreaSettings'),
- requirements=Requirements(
- aims=[Aim.design_place],
- results=list(Result),
- experience=Experience.one_project,
- motivation=Motivation.low
- ),
- complexity=COMPLEXITY_VECTOR_BD,
- type=BlueprintNames.map_idea_challenge.name
- )),
- (BlueprintNames.agenda_setting.value,
- Blueprint(
- title=_('Agenda Setting'),
- description=_('You can involve everyone in planning a meeting. '
- 'Collect ideas for an upcoming event and let your '
- 'participants vote on the topics you want to tackle.'),
- content=[
- ideas_phases.CollectPhase(),
- ideas_phases.RatingPhase(),
- ],
- image='images/agenda_setting.png',
- settings_model=None,
- requirements=Requirements(
- aims=[Aim.collect_ideas, Aim.discuss_topic,
- Aim.run_survey, Aim.agenda_setting],
- results=list(Result),
- experience=Experience.one_project,
- motivation=Motivation.low
- ),
- complexity=COMPLEXITY_VECTOR_AC,
- type=BlueprintNames.agenda_setting.name
- )),
- (BlueprintNames.commenting_text.value,
- Blueprint(
- title=_('Text Review'),
- description=_('Let participants discuss individual paragraphs of a '
- 'text. This is ideal for discussing position papers or '
- 'a mission statements with many people.'),
- content=[
- documents_phases.CreateDocumentPhase(),
- documents_phases.CommentPhase(),
- ],
- image='images/text_review.png',
- settings_model=None,
- requirements=Requirements(
- aims=[Aim.work_document],
- results=None,
- experience=None,
- motivation=None
- ),
- complexity=COMPLEXITY_VECTOR_F,
- type=BlueprintNames.commenting_text.name
- )),
- (BlueprintNames.a4_poll.value,
- Blueprint(
- title=_('Poll'),
- description=_('Run customizable, multi-step polls on OPIN to get '
- 'detailed opinions on topics from the public or your '
- 'members.'),
- content=[
- poll_phases.VotingPhase(),
- ],
- image='images/poll.png',
- settings_model=None,
- requirements=Requirements(
- aims=[Aim.run_survey],
- results=[Result.majority_vote],
- experience=Experience.no_projects,
- motivation=Motivation.not_found
- ),
- complexity=COMPLEXITY_VECTOR_E,
- type=BlueprintNames.a4_poll.name
- )),
- (BlueprintNames.communitydebate.value,
- Blueprint(
- title=_('Community debate'),
- description=_('Collect topics and questions to discuss, '
- 'debate and prioritize them.'),
- content=[
- communitydebate_phases.DebatePhase(),
- ],
- image='images/brainstorming.png',
- settings_model=None,
- requirements=Requirements(
- aims=[Aim.communitydebate],
- results=[Result.both],
- experience=Experience.no_projects,
- motivation=Motivation.not_found
- ),
- complexity=COMPLEXITY_VECTOR_AC,
- type=BlueprintNames.communitydebate.name
- )),
-]
-
-
-fallbacks = {
- Aim.collect_ideas: BlueprintNames.brainstorming.value,
- Aim.discuss_topic: BlueprintNames.brainstorming.value,
- Aim.agenda_setting: BlueprintNames.agenda_setting.value,
- Aim.design_place: BlueprintNames.map_brainstorming.value,
- Aim.run_survey: BlueprintNames.a4_poll.value,
- Aim.run_competition: BlueprintNames.agenda_setting.value,
- Aim.work_document: BlueprintNames.commenting_text.value,
- Aim.communitydebate: BlueprintNames.communitydebate.value
-}
diff --git a/euth/blueprints/forms.py b/euth/blueprints/forms.py
deleted file mode 100644
index 2f5990937..000000000
--- a/euth/blueprints/forms.py
+++ /dev/null
@@ -1,103 +0,0 @@
-from django import forms
-from django.core.exceptions import ValidationError
-from django.utils.translation import gettext_lazy as _
-
-from . import blueprints
-
-
-class GetSuggestionForm(forms.Form):
- aim_choices = [
- {'value': a.value, 'label': a.label, 'example': a.examples}
- for a in blueprints.Aim
- ]
-
- aim = forms.ChoiceField(
- choices=[(aim.value, aim.label) for aim in blueprints.Aim],
- required=True
- )
-
- result = forms.ChoiceField(
- choices=[(r.value, r.label) for r in blueprints.Result],
- widget=forms.RadioSelect,
- required=True,
- label=_('What is the desired outcome of the project?'),
- )
-
- experience = forms.ChoiceField(
- choices=[(e.value, e.label) for e in blueprints.Experience],
- widget=forms.RadioSelect,
- required=True,
- label=_('How many on- and offline participative projects '
- 'have you organised and managed in the past?')
- )
-
- motivation = forms.ChoiceField(
- choices=[(m.value, m.label) for m in blueprints.Motivation],
- required=True,
- widget=forms.RadioSelect,
- label=_('How motivated are your participants to take part in a '
- ' participative process?')
- )
-
- participants = forms.ChoiceField(
- choices=[(m.value, m.label) for m in blueprints.Participants],
- required=True,
- widget=forms.RadioSelect,
- label=_('How many participants might take part in the project?')
- )
-
- scope = forms.ChoiceField(
- choices=[(m.value, m.label) for m in blueprints.Scope],
- required=True,
- widget=forms.RadioSelect,
- label=_('What is the scope of the project?')
- )
-
- duration = forms.ChoiceField(
- choices=[(m.value, m.label) for m in blueprints.Duration],
- required=True,
- widget=forms.RadioSelect,
- label=_('How long do you want your project to be?')
- )
-
- accessibility = forms.ChoiceField(
- choices=[(m.value, m.label) for m in blueprints.Accessibility],
- required=True,
- widget=forms.RadioSelect,
- label=_('How easy can you access your participants?')
- )
-
- def clean_aim(self, *args, **kwargs):
- try:
- return blueprints.Aim(self.cleaned_data['aim'])
- except KeyError:
- raise ValidationError(_('Invalid aim selected'))
-
- def _clean_enum(self, name, enum):
- try:
- return enum.get(int(self.cleaned_data[name]))
- except (KeyError, ValueError):
- raise ValidationError(_('Invalid selection'))
-
- def clean_result(self, *args, **kwargs):
- return self._clean_enum('result', blueprints.Result)
-
- def clean_experience(self, *args, **kwargs):
- return self._clean_enum('experience', blueprints.Experience)
-
- def clean_motivation(self, *args, **kwargs):
- return self._clean_enum('motivation', blueprints.Motivation)
-
- def clean_participants(self, *args, **kwargs):
- return self._clean_enum(
- 'participants', blueprints.Participants)
-
- def clean_scope(self, *args, **kwargs):
- return self._clean_enum('scope', blueprints.Scope)
-
- def clean_duration(self, *args, **kwargs):
- return self._clean_enum('duration', blueprints.Duration)
-
- def clean_accessibility(self, *args, **kwargs):
- return self._clean_enum(
- 'accessibility', blueprints.Accessibility)
diff --git a/euth/blueprints/migrations/__init__.py b/euth/blueprints/migrations/__init__.py
deleted file mode 100644
index e69de29bb..000000000
diff --git a/euth/blueprints/mixins.py b/euth/blueprints/mixins.py
deleted file mode 100644
index fcd8d969e..000000000
--- a/euth/blueprints/mixins.py
+++ /dev/null
@@ -1,7 +0,0 @@
-from .blueprints import blueprints
-
-
-class BlueprintMixin():
- @property
- def blueprint(self):
- return dict(blueprints)[self.kwargs['blueprint_slug']]
diff --git a/euth/blueprints/names.py b/euth/blueprints/names.py
deleted file mode 100644
index b14e1ef69..000000000
--- a/euth/blueprints/names.py
+++ /dev/null
@@ -1,12 +0,0 @@
-from enum import Enum
-
-
-class BlueprintNames(Enum):
- brainstorming = 'brainstorming'
- map_brainstorming = 'map-brainstorming'
- idea_challenge = 'idea-challenge'
- map_idea_challenge = 'map-idea-challenge'
- agenda_setting = 'agenda-setting'
- commenting_text = 'commenting-text'
- a4_poll = 'a4-poll'
- communitydebate = 'communitydebate'
diff --git a/euth/blueprints/static/euth_blueprintsuggest/js/blueprintsuggest.js b/euth/blueprints/static/euth_blueprintsuggest/js/blueprintsuggest.js
deleted file mode 100644
index b2d0283b5..000000000
--- a/euth/blueprints/static/euth_blueprintsuggest/js/blueprintsuggest.js
+++ /dev/null
@@ -1,89 +0,0 @@
-/* global django */
-(function ($) {
- const blueprintsuggest = {
- init: function () {
- $('.js-continue').on('click', this.clickContinueHandler.bind(this))
- $('.js-back').on('click', this.clickBackHandler)
- $('.js-send').on('click', this.clickSendHandler.bind(this))
- },
-
- validate: function ($tab) {
- /*
- * Ensure that for each group of radio buttons in $tab at least one is checked.
- */
- const $radioButtons = $tab.find('input[type=radio]')
- const radioButtonsByName = {}
- $radioButtons.map(function () {
- const $this = $(this)
- const name = $this.attr('name')
-
- if (!Object.prototype.hasOwnProperty.call(radioButtonsByName, name)) {
- radioButtonsByName[name] = $()
- }
- radioButtonsByName[name] = radioButtonsByName[name].add($this)
-
- return radioButtonsByName[name]
- })
- for (const key in radioButtonsByName) {
- if (Object.prototype.hasOwnProperty.call(radioButtonsByName, key)) {
- const $inputs = radioButtonsByName[key]
- if (!$inputs.filter(':checked').length) {
- return false
- }
- }
- }
-
- return true
- },
-
- clickContinueHandler: function (e) {
- const $this = $(e.target)
- const $tab = $this.parents('.tab-pane')
- const isValid = this.validate($tab)
-
- // remove old errorlist
- $tab.find('.errorlist').remove()
-
- if (!isValid) {
- // there's no radio button checked, not valid, so add new errorlist
- $tab.find('.dst-lightbox-progress').before(this.getErrorElement())
- return true
- }
-
- $tab.removeClass('active').next().addClass('active')
- return false
- },
-
- clickSendHandler: function (e) {
- const $this = $(e.target)
- const $tab = $this.parents('.tab-pane')
- const isValid = this.validate($tab)
-
- // remove old errorlist
- $tab.find('.errorlist').remove()
-
- if (!isValid) {
- // there is some radio button not checked, not valid, so add new errorlist
- $tab.find('.dst-lightbox-progress').before(this.getErrorElement())
- e.preventDefault()
- return false
- }
-
- return true
- },
-
- getErrorElement: function () {
- const text = django.gettext('Please set all values for your project.')
- return '
'
- },
-
- clickBackHandler: function () {
- const $tab = $(this).parents('.tab-pane')
- $tab.removeClass('active').prev().addClass('active')
- }
- }
-
- $(function () {
- blueprintsuggest.init()
- })
-}(jQuery))
diff --git a/euth/blueprints/templates/euth_blueprints/form.html b/euth/blueprints/templates/euth_blueprints/form.html
deleted file mode 100644
index 3027a16f4..000000000
--- a/euth/blueprints/templates/euth_blueprints/form.html
+++ /dev/null
@@ -1,27 +0,0 @@
-{% extends 'base.html' %}
-{% load i18n static %}
-
-{% block title %}{% trans 'Find the right project template' %}{% endblock %}
-
-{% block extra_js %}
- {{ block.super }}
-
-{% endblock %}
-
-{% block content %}
-
-{% endblock %}
diff --git a/euth/blueprints/templates/euth_blueprints/includes/breadcrumbs.html b/euth/blueprints/templates/euth_blueprints/includes/breadcrumbs.html
deleted file mode 100644
index 8aacf15cd..000000000
--- a/euth/blueprints/templates/euth_blueprints/includes/breadcrumbs.html
+++ /dev/null
@@ -1,10 +0,0 @@
-{% load i18n %}
-
-
-
- {% trans 'Aim' %}
- {% trans 'Fine tuning' %}
- {% trans 'Feasibility' %}
- {% trans 'Result' %}
-
-
diff --git a/euth/blueprints/templates/euth_blueprints/includes/infopage.html b/euth/blueprints/templates/euth_blueprints/includes/infopage.html
deleted file mode 100644
index de3fb14b7..000000000
--- a/euth/blueprints/templates/euth_blueprints/includes/infopage.html
+++ /dev/null
@@ -1,91 +0,0 @@
-{% load static i18n infopage wagtailsettings_tags %}
-{% get_settings %}
-
-
-
-
-
-
-
-
{{ blueprint.title }}
-
-
-
-
-
-
{% trans 'Project Phases' %}
-
-
- {% for phase_content in blueprint.content %}
-
-
-
-
-
-
-
Phase {{ forloop.counter }} {{ phase_content.description }}
-
-
- {% endfor %}
-
-
-
- {% get_project settings.home.HelpPages blueprint as p %}
- {% if p %}
-
-
{% trans 'Check out the example project' %}
-
- {% include 'euth_projects/includes/project_tile.html' with project=p open_in_new_tab=True %}
-
- {% endif %}
-
- {% if time %}
-
-
{% trans 'Estimated effort' %}
-
- {% blocktrans %}
-
- {{time}} hours
-
-
per week
- {% endblocktrans %}
-
-
- {% endif %}
-
- {% with settings.home.HelpPages.guidelines_page as guidelines %}
- {% if guidelines and guidelines.live %}
-
-
{% trans 'Guidelines for a successful project' %}
-
-
-
{% trans 'Get inspiration and tips for' %}
-
- {% trans 'setting up your participation project' %}
- {% trans 'activation and motivation of participants' %}
- {% trans 'moderation of discussions' %}
- {% trans 'and more' %}
-
-
-
{% trans 'Check out the guidelines!' %}
-
-
-
- {% endif %}
- {% endwith %}
-
-
-
-
-
-
-
diff --git a/euth/blueprints/templates/euth_blueprints/includes/rangebuttons.html b/euth/blueprints/templates/euth_blueprints/includes/rangebuttons.html
deleted file mode 100644
index 9dee01487..000000000
--- a/euth/blueprints/templates/euth_blueprints/includes/rangebuttons.html
+++ /dev/null
@@ -1,13 +0,0 @@
-{{ field.label }}
-{{ field.errors }}
-
diff --git a/euth/blueprints/templates/euth_blueprints/includes/step-aim.html b/euth/blueprints/templates/euth_blueprints/includes/step-aim.html
deleted file mode 100644
index ce5d0b1d3..000000000
--- a/euth/blueprints/templates/euth_blueprints/includes/step-aim.html
+++ /dev/null
@@ -1,46 +0,0 @@
-{% load i18n static %}
-
-
-
-
{% trans 'What do you want to do in your participation project?' %}
-
-
{% trans 'My Aim' %}
-
{% trans 'Use Cases' %}
-
- {% for radio in form.aim_choices %}
-
-
-
-
- {% for example in radio.example %}
- {{ example }}
- {% endfor %}
-
-
-
- {% endfor %}
-
- {% include 'euth_blueprints/includes/breadcrumbs.html' with aim=True %}
-
-
- {% trans 'Proceed' %}
-
-
-
-
diff --git a/euth/blueprints/templates/euth_blueprints/includes/step-feasibility.html b/euth/blueprints/templates/euth_blueprints/includes/step-feasibility.html
deleted file mode 100644
index d6003bda5..000000000
--- a/euth/blueprints/templates/euth_blueprints/includes/step-feasibility.html
+++ /dev/null
@@ -1,31 +0,0 @@
-{% load i18n static %}
-
-
-
-
{% trans 'Feasibility' %}
-
{% trans 'The questions below help us to estimate the effort you will have to invest to make your project a success!' %}
-
-
- {% include 'euth_blueprints/includes/rangebuttons.html' with field=form.participants %}
-
-
- {% include 'euth_blueprints/includes/rangebuttons.html' with field=form.scope %}
-
-
- {% include 'euth_blueprints/includes/rangebuttons.html' with field=form.duration %}
-
-
- {% include 'euth_blueprints/includes/rangebuttons.html' with field=form.accessibility %}
-
-
-
- {% include 'euth_blueprints/includes/breadcrumbs.html' with feasibility=True %}
-
-
- {% trans 'Proceed' %}
-
-
-
diff --git a/euth/blueprints/templates/euth_blueprints/includes/step-finetuning.html b/euth/blueprints/templates/euth_blueprints/includes/step-finetuning.html
deleted file mode 100644
index e116cda03..000000000
--- a/euth/blueprints/templates/euth_blueprints/includes/step-finetuning.html
+++ /dev/null
@@ -1,28 +0,0 @@
-{% load i18n static %}
-
-
-
-
{% trans 'Fine tuning' %}
-
{% trans 'With the following questions we want to get more detailed information to see which process on OPIN fits best.'%}
-
-
- {% include 'euth_blueprints/includes/rangebuttons.html' with field=form.result %}
-
-
- {% include 'euth_blueprints/includes/rangebuttons.html' with field=form.experience %}
-
-
- {% include 'euth_blueprints/includes/rangebuttons.html' with field=form.motivation %}
-
-
-
- {% include 'euth_blueprints/includes/breadcrumbs.html' with finetuning=True %}
-
-
- {% trans 'Proceed' %}
-
-
-
diff --git a/euth/blueprints/templates/euth_blueprints/result.html b/euth/blueprints/templates/euth_blueprints/result.html
deleted file mode 100644
index d0ad2b106..000000000
--- a/euth/blueprints/templates/euth_blueprints/result.html
+++ /dev/null
@@ -1,51 +0,0 @@
-{% extends 'base.html' %}
-{% load static i18n %}
-{% block content %}
-
-
-
-
- {% blocktrans count counter=blueprints|length %}
- This template matches your criterias!
- {% plural %}
- These templates match your criterias!
- {% endblocktrans %}
-
-
- {% for blueprint_slug, blueprint, time in blueprints %}
-
-
-
{{ blueprint.title }}
-
-
-
- {% for phase_content in blueprint.content %}
-
- Phase {{ forloop.counter }} : {{ phase_content.description }}
-
- {% endfor %}
-
-
-
-
-
-
- {% include 'euth_blueprints/includes/infopage.html'%}
-
-
- {% endfor %}
-
-
-
- {% include 'euth_blueprints/includes/breadcrumbs.html' with result=True %}
-
-
-{% endblock %}
diff --git a/euth/blueprints/templatetags/infopage.py b/euth/blueprints/templatetags/infopage.py
deleted file mode 100644
index d3e8d0d44..000000000
--- a/euth/blueprints/templatetags/infopage.py
+++ /dev/null
@@ -1,8 +0,0 @@
-from django import template
-
-register = template.Library()
-
-
-@register.simple_tag
-def get_project(helppages, blueprint):
- return getattr(helppages, blueprint.type, None)
diff --git a/euth/blueprints/urls.py b/euth/blueprints/urls.py
deleted file mode 100644
index ee0beef26..000000000
--- a/euth/blueprints/urls.py
+++ /dev/null
@@ -1,8 +0,0 @@
-from django.urls import re_path
-
-from . import views
-
-urlpatterns = [
- re_path(r'^(?P
[-\w_]+)/$',
- views.SuggestFormView.as_view(), name='blueprints-form'),
-]
diff --git a/euth/blueprints/views.py b/euth/blueprints/views.py
deleted file mode 100644
index 238587f47..000000000
--- a/euth/blueprints/views.py
+++ /dev/null
@@ -1,123 +0,0 @@
-import math
-
-from django.views import generic
-from rules.contrib import views as rules_views
-
-from euth.dashboard.mixins import DashboardBaseMixin
-
-from . import blueprints
-from . import forms
-
-
-def custom_round(x):
- if x % 1 < 0.5:
- return math.floor(x)
- else:
- return math.ceil(x)
-
-
-def filter_blueprints(aim, result, experience, motivation,
- participants, scope, duration, accessibility,
- options=blueprints.blueprints,
- fallbacks=blueprints.fallbacks):
- candidates = []
-
- for name, blueprint in options:
- if aim not in blueprint.requirements.aims:
- continue
-
- requirements = blueprint.requirements
-
- if result and experience and motivation:
-
- req_results = requirements.results
- req_experience = requirements.experience
- req_motivation = requirements.motivation
-
- if req_results and result not in req_results:
- continue
- if req_experience and req_experience.value > experience.value:
- continue
- if req_motivation and req_motivation.value > motivation.value:
- continue
-
- timeneeded = compute_time_needed(
- blueprint, participants, duration, scope,
- motivation, accessibility, experience)
- candidates.append((name, blueprint, timeneeded))
-
- if not candidates:
- name = fallbacks[aim]
- blueprint = dict(options)[name]
- timeneeded = compute_time_needed(
- blueprint, participants, duration, scope,
- motivation, accessibility, experience)
- candidates.append((name, blueprint, timeneeded))
-
- return candidates
-
-
-def compute_complexity(blueprint, participants, duration, scope):
-
- return custom_round(sum((
- blueprint.complexity.participants[0] +
- participants.value * blueprint.complexity.participants[1],
- blueprint.complexity.duration[0] +
- duration.value * blueprint.complexity.duration[1],
- blueprint.complexity.scope[0] +
- scope.value * blueprint.complexity.scope[1]
- )))
-
-
-def compute_mobilisation(motivation, accessibility):
- # modify to match different coding for motivation
- return custom_round((5 - motivation.value + accessibility.value) / 2)
-
-
-def compute_time_needed(
- blueprint, participants, duration, scope,
- motivation, accessibility, experience
-):
- complexity = compute_complexity(blueprint, participants, duration, scope)
- mobilisation = compute_mobilisation(motivation, accessibility)
- # modify to match different coding for experience
- value = (complexity + 1) * (mobilisation + 5 - experience.value)
-
- if value < 13:
- return 5
- elif value < 21:
- return 10
- elif value < 28:
- return 15
- elif value < 35:
- return 20
- elif value < 40:
- return 25
- elif value < 50:
- return 30
- else:
- return 35
-
-
-class SuggestFormView(DashboardBaseMixin,
- rules_views.PermissionRequiredMixin,
- generic.FormView):
- template_name = 'euth_blueprints/form.html'
- form_class = forms.GetSuggestionForm
- permission_required = 'a4projects.add_project'
-
- def form_valid(self, form):
- context = {
- 'blueprints': filter_blueprints(**form.cleaned_data),
- 'organisation': self.organisation,
- 'request': self.request # FIXME: should be done context processor
- }
- context.update(self.get_context_data())
- return self.response_class(
- request=self.request,
- template='euth_blueprints/result.html',
- context=context
- )
-
- def get_permission_object(self):
- return self.organisation
diff --git a/euth/communitydebate/admin.py b/euth/communitydebate/admin.py
deleted file mode 100644
index 78c49052c..000000000
--- a/euth/communitydebate/admin.py
+++ /dev/null
@@ -1,11 +0,0 @@
-from django.contrib import admin
-
-from euth.communitydebate import models
-
-
-class TopicAdmin(admin.ModelAdmin):
- list_filter = ('module__project', 'module')
-
-
-admin.site.register(models.Topic, TopicAdmin)
-admin.site.register(models.TopicFileUpload)
diff --git a/euth/communitydebate/forms.py b/euth/communitydebate/forms.py
deleted file mode 100644
index 07c6ab536..000000000
--- a/euth/communitydebate/forms.py
+++ /dev/null
@@ -1,40 +0,0 @@
-from django import forms
-
-from adhocracy4.categories import forms as category_forms
-from euth.contrib import widgets
-from euth.contrib.mixins import ImageRightOfUseMixin
-
-from . import models
-
-
-class TopicForm(category_forms.CategorizableFieldMixin,
- ImageRightOfUseMixin,
- forms.ModelForm):
-
- class Meta:
- model = models.Topic
- fields = ['name', 'description', 'image', 'category']
-
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.fields['category'].empty_label = '---'
-
- class Media:
- js = ('fileupload_formset.js',)
-
-
-class TopicFileUploadForm(forms.ModelForm):
-
- class Meta:
- model = models.TopicFileUpload
- fields = ['title', 'document']
- widgets = {
- 'document': widgets.FileUploadWidget()
- }
-
-
-TopicFileUploadFormset = forms.inlineformset_factory(models.Topic,
- models.TopicFileUpload,
- TopicFileUploadForm,
- extra=1, max_num=3,
- can_delete=True)
diff --git a/euth/communitydebate/migrations/0005_auto_20240909_1609.py b/euth/communitydebate/migrations/0005_auto_20240909_1609.py
new file mode 100644
index 000000000..26e401aac
--- /dev/null
+++ b/euth/communitydebate/migrations/0005_auto_20240909_1609.py
@@ -0,0 +1,21 @@
+# Generated by Django 3.2.20 on 2024-09-09 14:09
+
+from django.db import migrations, models
+import django.utils.timezone
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('euth_communitydebate', '0004_topicfileupload'),
+ ]
+
+ operations = [
+ migrations.DeleteModel(
+ name='TopicFileUpload',
+ ),
+ migrations.DeleteModel(
+ name='Topic',
+ ),
+
+ ]
diff --git a/euth/communitydebate/migrations/0006_delete_model.py.bak b/euth/communitydebate/migrations/0006_delete_model.py.bak
new file mode 100644
index 000000000..dac74728f
--- /dev/null
+++ b/euth/communitydebate/migrations/0006_delete_model.py.bak
@@ -0,0 +1,20 @@
+# Generated by Django 3.2.20 on 2024-09-09 14:12
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('euth_communitydebate', '0005_auto_20240909_1609'),
+ ]
+
+ operations = [
+ migrations.RunSQL(
+ sql=[
+ "DROP TABLE IF EXISTS euth_communitydebate_topic",
+ "DROP TABLE IF EXISTS euth_communitydebate_topicfileupload",
+ ],
+ reverse_sql=migrations.RunSQL.noop,
+ )
+ ]
diff --git a/euth/communitydebate/models.py b/euth/communitydebate/models.py
index b4c6fee9b..e69de29bb 100644
--- a/euth/communitydebate/models.py
+++ b/euth/communitydebate/models.py
@@ -1,64 +0,0 @@
-from autoslug import AutoSlugField
-from ckeditor.fields import RichTextField
-from django.contrib.contenttypes.fields import GenericRelation
-from django.db import models
-from django.urls import reverse
-
-from adhocracy4 import transforms
-from adhocracy4.categories.fields import CategoryField
-from adhocracy4.comments import models as comment_models
-from adhocracy4.images import fields
-from adhocracy4.models import base
-from adhocracy4.models import query
-from adhocracy4.modules import models as module_models
-from adhocracy4.ratings import models as rating_models
-from euth.contrib import validators
-
-
-class TopicQuerySet(query.RateableQuerySet, query.CommentableQuerySet):
- pass
-
-
-class Topic(module_models.Item):
- slug = AutoSlugField(populate_from='name', unique=True)
- name = models.CharField(max_length=120)
- description = RichTextField()
- image = fields.ConfiguredImageField(
- 'idea_image',
- upload_to='communitydebate/images',
- blank=True,
- )
- ratings = GenericRelation(rating_models.Rating,
- related_query_name='topic',
- object_id_field='object_pk')
- comments = GenericRelation(comment_models.Comment,
- related_query_name='topic',
- object_id_field='object_pk')
- category = CategoryField()
-
- objects = TopicQuerySet.as_manager()
-
- def __str__(self):
- return self.name
-
- def save(self, *args, **kwargs):
- self.description = transforms.clean_html_field(
- self.description)
- super(Topic, self).save(*args, **kwargs)
-
- def get_absolute_url(self):
- return reverse('topic-detail', args=[str(self.slug)])
-
-
-class TopicFileUpload(base.TimeStampedModel):
- title = models.CharField(max_length=256)
- document = models.FileField(
- upload_to='communitydebate/documents',
- validators=[validators.validate_file_type_and_size])
- topic = models.ForeignKey(
- Topic,
- on_delete=models.CASCADE
- )
-
- def __str__(self):
- return self.title
diff --git a/euth/communitydebate/phases.py b/euth/communitydebate/phases.py
deleted file mode 100644
index 8b9007d94..000000000
--- a/euth/communitydebate/phases.py
+++ /dev/null
@@ -1,27 +0,0 @@
-from django.utils.translation import gettext_lazy as _
-
-from adhocracy4 import phases
-
-from . import apps
-from . import models
-from . import views
-
-
-class DebatePhase(phases.PhaseContent):
- app = apps.Config.label
- phase = 'debate'
- view = views.TopicListView
-
- name = _('Debate phase')
- description = _('Add topics and debate them.')
- module_name = _('communitydebate')
- icon = 'far fa-comments'
-
- features = {
- 'crud': (models.Topic,),
- 'comment': (models.Topic,),
- 'rate': (models.Topic,),
- }
-
-
-phases.content.register(DebatePhase())
diff --git a/euth/communitydebate/rules.py b/euth/communitydebate/rules.py
deleted file mode 100644
index 01e074574..000000000
--- a/euth/communitydebate/rules.py
+++ /dev/null
@@ -1,38 +0,0 @@
-import rules
-from rules.predicates import is_superuser
-
-from adhocracy4.modules.predicates import is_context_initiator
-from adhocracy4.modules.predicates import is_context_member
-from adhocracy4.modules.predicates import is_context_moderator
-from adhocracy4.modules.predicates import is_owner
-from adhocracy4.modules.predicates import is_public_context
-from adhocracy4.phases.predicates import phase_allows_add
-from adhocracy4.phases.predicates import phase_allows_change
-from adhocracy4.phases.predicates import phase_allows_comment
-from adhocracy4.phases.predicates import phase_allows_rate
-
-from .models import Topic
-
-rules.add_perm('euth_communitydebate.comment_topic',
- is_superuser | is_context_moderator | is_context_initiator |
- (is_context_member & phase_allows_comment))
-
-
-rules.add_perm('euth_communitydebate.modify_topic',
- is_superuser | is_context_moderator | is_context_initiator |
- (is_context_member & is_owner & phase_allows_change))
-
-
-rules.add_perm('euth_communitydebate.propose_topic',
- is_superuser | is_context_moderator | is_context_initiator |
- (is_context_member & phase_allows_add(Topic)))
-
-
-rules.add_perm('euth_communitydebate.rate_topic',
- is_superuser | is_context_moderator | is_context_initiator |
- (is_context_member & phase_allows_rate))
-
-
-rules.add_perm('euth_communitydebate.view_topic',
- is_superuser | is_context_moderator | is_context_initiator |
- is_context_member | is_public_context)
diff --git a/euth/communitydebate/signals.py b/euth/communitydebate/signals.py
index 177db1619..e69de29bb 100644
--- a/euth/communitydebate/signals.py
+++ b/euth/communitydebate/signals.py
@@ -1,28 +0,0 @@
-import os
-
-from django.db.models.signals import post_delete
-from django.db.models.signals import pre_save
-from django.dispatch import receiver
-
-from .models import TopicFileUpload
-
-
-@receiver(post_delete, sender=TopicFileUpload)
-def delete_topic_file_upload(sender, instance, **kwargs):
- if instance.document:
- instance.document.delete(False)
-
-
-@receiver(pre_save, sender=TopicFileUpload)
-def delete_topic_file_upload_on_change(sender, instance, **kwargs):
- if not instance.pk:
- return False
- try:
- old_file = sender.objects.get(pk=instance.pk).document
- except sender.DoesNotExist:
- return False
-
- new_file = instance.document
- if not old_file == new_file:
- if os.path.isfile(old_file.path):
- os.remove(old_file.path)
diff --git a/euth/communitydebate/urls.py b/euth/communitydebate/urls.py
deleted file mode 100644
index fc2e3fc65..000000000
--- a/euth/communitydebate/urls.py
+++ /dev/null
@@ -1,14 +0,0 @@
-from django.urls import re_path
-
-from . import views
-
-urlpatterns = [
- re_path(r'create/module/(?P[-\w_]+)/$',
- views.TopicCreateView.as_view(), name='topic-create'),
- re_path(r'^(?P[-\w_]+)/edit/$',
- views.TopicUpdateView.as_view(), name='topic-update'),
- re_path(r'^(?P[-\w_]+)/delete/$',
- views.TopicDeleteView.as_view(), name='topic-delete'),
- re_path(r'^(?P[-\w_]+)/$',
- views.TopicDetailView.as_view(), name='topic-detail'),
-]
diff --git a/euth/communitydebate/views.py b/euth/communitydebate/views.py
deleted file mode 100644
index e6b4b3f58..000000000
--- a/euth/communitydebate/views.py
+++ /dev/null
@@ -1,168 +0,0 @@
-from django.contrib import messages
-from django.shortcuts import render
-from django.urls import reverse
-from django.utils.translation import gettext as _
-from django.views import generic
-from rules.contrib.views import PermissionRequiredMixin
-
-from adhocracy4.filters import views as filter_views
-from adhocracy4.modules.models import Module
-from euth.projects import mixins as prj_mixins
-
-from . import forms
-from . import models as communitydebate_models
-from .filters import TopicFilterSet
-
-
-class TopicListView(prj_mixins.ProjectPhaseMixin,
- filter_views.FilteredListView):
- model = communitydebate_models.Topic
- paginate_by = 15
- filter_set = TopicFilterSet
-
- def get_queryset(self):
- return super().get_queryset().filter(module=self.module)
-
-
-class TopicDetailView(PermissionRequiredMixin, generic.DetailView):
- model = communitydebate_models.Topic
- queryset = \
- communitydebate_models.Topic.objects\
- .annotate_positive_rating_count() \
- .annotate_negative_rating_count()
- permission_required = 'euth_communitydebate.view_topic'
-
- def get_context_data(self, **kwargs):
- context = super().get_context_data()
- upload_files = communitydebate_models.TopicFileUpload.objects\
- .filter(topic=self.object)
- context['upload_files'] = upload_files
- return context
-
-
-class TopicCreateView(PermissionRequiredMixin, generic.CreateView):
- model = communitydebate_models.Topic
- form_class = forms.TopicForm
- permission_required = 'euth_communitydebate.propose_topic'
- template_name = 'euth_communitydebate/topic_form.html'
-
- @property
- def raise_exception(self):
- return self.request.user.is_authenticated
-
- def dispatch(self, *args, **kwargs):
- mod_slug = self.kwargs[self.slug_url_kwarg]
- self.module = Module.objects.get(slug=mod_slug)
- self.project = self.module.project
- return super().dispatch(*args, **kwargs)
-
- def get_permission_object(self, *args, **kwargs):
- return self.module
-
- def get_context_data(self, upload_forms=None, **kwargs):
- context = super().get_context_data(**kwargs)
- context['project'] = self.project
- context['mode'] = 'create'
- if not upload_forms:
- upload_forms = forms.TopicFileUploadFormset()
- context['upload_forms'] = upload_forms
- return context
-
- def form_valid(self, form):
- form.instance.creator = self.request.user
- form.instance.module = self.module
- return super().form_valid(form)
-
- def get_form_kwargs(self):
- kwargs = super().get_form_kwargs()
- kwargs['module'] = self.module
- return kwargs
-
- def post(self, request, *args, **kwargs):
- self.object = None
- form = self.get_form()
- if form.is_valid():
- topic = form.save(commit=False)
- upload_forms = forms.TopicFileUploadFormset(request.POST,
- request.FILES,
- instance=topic)
- if upload_forms.is_valid():
- response = self.form_valid(form)
- upload_forms.save()
- messages.add_message(request,
- messages.SUCCESS,
- _('Topic '
- 'successfully created'))
- return response
-
- else:
- upload_forms = forms.TopicFileUploadFormset(request.POST,
- request.FILES)
- return render(request, self.template_name,
- self.get_context_data(upload_forms=upload_forms))
-
-
-class TopicUpdateView(PermissionRequiredMixin, generic.UpdateView):
- model = communitydebate_models.Topic
- form_class = forms.TopicForm
- permission_required = 'euth_communitydebate.modify_topic'
- template_name = 'euth_communitydebate/topic_form.html'
-
- @property
- def raise_exception(self):
- return self.request.user.is_authenticated
-
- def dispatch(self, *args, **kwargs):
- self.object = self.get_object()
- return super().dispatch(*args, **kwargs)
-
- def get_context_data(self, upload_forms=None, **kwargs):
- context = super().get_context_data(**kwargs)
- context['project'] = self.object.project
- context['mode'] = 'update'
- if not upload_forms:
- upload_forms = forms.TopicFileUploadFormset(
- instance=self.get_object())
- context['upload_forms'] = upload_forms
- return context
-
- def get_form_kwargs(self):
- kwargs = super().get_form_kwargs()
- kwargs['module'] = kwargs.get('instance').module
- return kwargs
-
- def post(self, request, *args, **kwargs):
- form = self.get_form()
- upload_forms = forms.TopicFileUploadFormset(request.POST,
- request.FILES,
- instance=self.object)
- if upload_forms.is_valid() and form.is_valid():
- response = self.form_valid(form)
- upload_forms.save()
- messages.add_message(request,
- messages.SUCCESS,
- _('Topic successfully '
- 'updated'))
- else:
- response = render(request,
- self.template_name,
- self.get_context_data(upload_forms=upload_forms))
- return response
-
-
-class TopicDeleteView(PermissionRequiredMixin, generic.DeleteView):
- model = communitydebate_models.Topic
- success_message = _("Your topic has been deleted")
- permission_required = 'euth_communitydebate.modify_topic'
-
- @property
- def raise_exception(self):
- return self.request.user.is_authenticated
-
- def delete(self, request, *args, **kwargs):
- messages.success(self.request, self.success_message)
- return super(TopicDeleteView, self).delete(request, *args, **kwargs)
-
- def get_success_url(self):
- return reverse('project-detail',
- kwargs={'slug': self.object.project.slug})
diff --git a/euth/dashboard/templates/euth_dashboard/dropdown.html b/euth/dashboard/templates/euth_dashboard/dropdown.html
deleted file mode 100644
index 2f2a73502..000000000
--- a/euth/dashboard/templates/euth_dashboard/dropdown.html
+++ /dev/null
@@ -1,28 +0,0 @@
-{% load thumbnail avatar project_tags i18n rules static %}
-
-
-
- {% if is_user %}
-
- {% endif %}
- {{ title }}
- {% if orgs %}
-
- {% endif %}
-
- {% if orgs %}
-
- {% endif %}
-
diff --git a/euth/dashboard/templatetags/__init__.py b/euth/dashboard/templatetags/__init__.py
deleted file mode 100644
index e69de29bb..000000000
diff --git a/euth/dashboard/templatetags/form_tags.py b/euth/dashboard/templatetags/form_tags.py
deleted file mode 100644
index 12f1feebe..000000000
--- a/euth/dashboard/templatetags/form_tags.py
+++ /dev/null
@@ -1,36 +0,0 @@
-from django import template
-from django.conf import settings
-
-from adhocracy4 import phases
-
-register = template.Library()
-
-
-@register.simple_tag
-def add(number1, number2):
- return number1 + number2
-
-
-@register.simple_tag
-def next(some_list, current_index):
- try:
- return some_list[int(current_index) + 1]
- except (IndexError, TypeError, ValueError):
- return ''
-
-
-@register.simple_tag
-def getPhaseName(type):
- name = phases.content.__getitem__(type).name
- return name
-
-
-@register.simple_tag
-def getAllowedFileTypes():
- fileformats = settings.FILE_ALIASES['*']['fileformats']
- return ', '.join([name for name, mimetype in fileformats])
-
-
-@register.simple_tag
-def get_disabled(project):
- return 'disabled' if project and project.is_archived else ''
diff --git a/euth/dashboard/templatetags/react_user_list.py b/euth/dashboard/templatetags/react_user_list.py
deleted file mode 100644
index 435f7434a..000000000
--- a/euth/dashboard/templatetags/react_user_list.py
+++ /dev/null
@@ -1,32 +0,0 @@
-import json
-
-from django import template
-from django.db.models.functions import Lower
-from django.utils.html import format_html
-from rest_framework.serializers import ListSerializer
-
-from euth.users.serializers import UserWithMailSerializer
-
-register = template.Library()
-
-
-@register.simple_tag()
-def react_user_list(users, project, identifier):
- users = users.order_by(Lower('username'))
- user_list = ListSerializer(users, child=UserWithMailSerializer()).data
-
- format_strings = {
- 'users': json.dumps(user_list),
- 'project': project.pk,
- 'listen_to': identifier,
- }
-
- return format_html(
- (
- ' '
- ),
- **format_strings
- )
diff --git a/euth/documents/admin.py b/euth/documents/admin.py
deleted file mode 100644
index e643b2075..000000000
--- a/euth/documents/admin.py
+++ /dev/null
@@ -1,11 +0,0 @@
-from django.contrib import admin
-
-from . import models
-
-
-class ParagraphAdmin(admin.ModelAdmin):
- list_filter = ('document',)
-
-
-admin.site.register(models.Document)
-admin.site.register(models.Paragraph, ParagraphAdmin)
diff --git a/euth/documents/api.py b/euth/documents/api.py
deleted file mode 100644
index 4b8211047..000000000
--- a/euth/documents/api.py
+++ /dev/null
@@ -1,27 +0,0 @@
-from rest_framework import mixins
-from rest_framework import viewsets
-
-from adhocracy4.api.mixins import ModuleMixin
-from adhocracy4.api.permissions import ViewSetRulesPermission
-
-from .models import Document
-from .serializers import DocumentSerializer
-
-
-class DocumentViewSet(mixins.CreateModelMixin,
- mixins.UpdateModelMixin,
- ModuleMixin,
- viewsets.GenericViewSet):
- queryset = Document.objects.all()
- serializer_class = DocumentSerializer
- permission_classes = (ViewSetRulesPermission,)
-
- def get_permission_object(self):
- return self.module
-
- def get_serializer_context(self):
- context = super().get_serializer_context()
- context.update({
- 'module_pk': self.module_pk,
- })
- return context
diff --git a/euth/documents/exports.py b/euth/documents/exports.py
deleted file mode 100644
index 1e80bbc84..000000000
--- a/euth/documents/exports.py
+++ /dev/null
@@ -1,33 +0,0 @@
-from django.utils import timezone
-from django.utils.translation import gettext_lazy as _
-
-from adhocracy4.comments.models import Comment
-from adhocracy4.exports import mixins as export_mixins
-from adhocracy4.exports import views as export_views
-from euth.exports import mixins as euth_export_mixins
-from euth.exports import register_export
-
-
-@register_export(_('Documents with comments'))
-class DocumentExportView(
- export_mixins.ExportModelFieldsMixin,
- euth_export_mixins.UserGeneratedContentExportMixin,
- export_mixins.ItemExportWithLinkMixin,
- export_mixins.ItemExportWithRatesMixin,
- euth_export_mixins.ItemExportWithRepliesToMixin,
- export_views.BaseItemExportView
-):
-
- model = Comment
-
- fields = ['id', 'comment', 'created']
-
- def get_queryset(self):
- try:
- return self.module.item_set.first().document.clustered_comments
- except AttributeError:
- return Comment.objects.none()
-
- def get_base_filename(self):
- return '%s_%s' % (self.project.slug,
- timezone.now().strftime('%Y%m%dT%H%M%S'))
diff --git a/euth/documents/migrations/0003_auto_20240910_1120.py b/euth/documents/migrations/0003_auto_20240910_1120.py
new file mode 100644
index 000000000..8f42e2142
--- /dev/null
+++ b/euth/documents/migrations/0003_auto_20240910_1120.py
@@ -0,0 +1,23 @@
+# Generated by Django 3.2.20 on 2024-09-10 09:20
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('euth_documents', '0002_add_paragraph_ordering_by_weight'),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name='paragraph',
+ name='document',
+ ),
+ migrations.DeleteModel(
+ name='Document',
+ ),
+ migrations.DeleteModel(
+ name='Paragraph',
+ ),
+ ]
diff --git a/euth/documents/models.py b/euth/documents/models.py
index 9dc681097..e69de29bb 100644
--- a/euth/documents/models.py
+++ b/euth/documents/models.py
@@ -1,82 +0,0 @@
-from ckeditor.fields import RichTextField
-from django.contrib.contenttypes.fields import GenericRelation
-from django.db import models
-from django.utils.functional import cached_property
-
-from adhocracy4 import transforms
-from adhocracy4.comments import models as comment_models
-from adhocracy4.models import base
-from adhocracy4.modules import models as module_models
-
-from . import validators
-
-
-class Document(module_models.Item):
- name = models.CharField(max_length=120)
- comments = GenericRelation(comment_models.Comment,
- related_query_name='document',
- object_id_field='object_pk')
-
- def __str__(self):
- return "{}_document_{}".format(str(self.module), self.pk)
-
- def clean(self, *args, **kwargs):
- validators.single_document_per_module(self.module, self.pk)
- super().clean(*args, **kwargs)
-
- def get_absolute_url(self):
- from django.urls import reverse
- return reverse('project-detail', args=[str(self.project.slug)])
-
- @property
- def clustered_comments(self):
- comments = (
- comment_models.Comment.objects.filter(
- document__module=self.module) |
- comment_models.Comment.objects.filter(
- paragraph__document__module=self.module) |
- comment_models.Comment.objects.filter(
- parent_comment__paragraph__document__module=self.module) |
- comment_models.Comment.objects.filter(
- parent_comment__document__module=self.module)
- )
- return comments
-
-
-class Paragraph(base.TimeStampedModel):
- name = models.CharField(max_length=120, blank=True)
- text = RichTextField()
- weight = models.PositiveIntegerField()
- document = models.ForeignKey(Document,
- on_delete=models.CASCADE,
- related_name='paragraphs')
- comments = GenericRelation(comment_models.Comment,
- related_query_name='paragraph',
- object_id_field='object_pk')
-
- class Meta:
- ordering = ('weight',)
-
- def __str__(self):
- return "{}_paragraph_{}".format(str(self.document), self.weight)
-
- def save(self, *args, **kwargs):
- self.text = transforms.clean_html_field(
- self.text, 'image-editor')
- super().save(*args, **kwargs)
-
- def get_absolute_url(self):
- from django.urls import reverse
- return reverse('paragraph-detail', args=[str(self.pk)])
-
- @cached_property
- def creator(self):
- return self.document.creator
-
- @cached_property
- def module(self):
- return self.document.module
-
- @cached_property
- def project(self):
- return self.document.project
diff --git a/euth/documents/phases.py b/euth/documents/phases.py
deleted file mode 100644
index 01fac3f97..000000000
--- a/euth/documents/phases.py
+++ /dev/null
@@ -1,48 +0,0 @@
-from django.utils.translation import gettext_lazy as _
-
-from adhocracy4 import phases
-
-from . import apps
-from . import models
-from . import views
-
-
-class CreateDocumentPhase(phases.PhaseContent):
- """
- Allows no interaction for participants, only
- creation for moderators.
- """
- app = apps.Config.label
- phase = 'create_document'
- view = views.DocumentCreateView
-
- name = _('Create document phase')
- module_name = _('commenting text')
- description = _('Create text for the project.')
- icon = 'far fa-file-alt'
-
- features = {}
-
-
-phases.content.register(CreateDocumentPhase())
-
-
-class CommentPhase(phases.PhaseContent):
- """
- Allows only commenting of paragraphs.
- """
- app = apps.Config.label
- phase = 'comment'
- view = views.DocumentDetailView
-
- name = _('Comment phase')
- module_name = _('commenting text')
- description = _('Collect comments for the text.')
- icon = 'far fa-comment'
-
- features = {
- 'comment': (models.Paragraph, models.Document),
- }
-
-
-phases.content.register(CommentPhase())
diff --git a/euth/documents/rules.py b/euth/documents/rules.py
deleted file mode 100644
index 6f4814e2d..000000000
--- a/euth/documents/rules.py
+++ /dev/null
@@ -1,29 +0,0 @@
-import rules
-from rules.predicates import is_superuser
-
-from adhocracy4.modules.predicates import is_context_initiator
-from adhocracy4.modules.predicates import is_context_member
-from adhocracy4.modules.predicates import is_context_moderator
-from adhocracy4.modules.predicates import is_public_context
-from adhocracy4.phases.predicates import phase_allows_comment
-
-rules.add_perm('euth_documents.comment_paragraph',
- is_superuser | is_context_moderator | is_context_initiator |
- (is_context_member & phase_allows_comment))
-
-rules.add_perm('euth_documents.comment_document',
- is_superuser | is_context_moderator | is_context_initiator |
- (is_context_member & phase_allows_comment))
-
-rules.add_perm('euth_documents.add_document',
- is_superuser | is_context_moderator | is_context_initiator)
-
-rules.add_perm('euth_documents.change_document',
- is_superuser | is_context_moderator | is_context_initiator)
-
-rules.add_perm('euth_documents.create_document',
- is_superuser | is_context_moderator | is_context_initiator)
-
-rules.add_perm('euth_documents.view_paragraph',
- is_superuser | is_context_moderator | is_context_initiator |
- is_context_member | is_public_context)
diff --git a/euth/documents/serializers.py b/euth/documents/serializers.py
deleted file mode 100644
index e0c9c82f4..000000000
--- a/euth/documents/serializers.py
+++ /dev/null
@@ -1,65 +0,0 @@
-from rest_framework import serializers
-
-from adhocracy4.modules.models import Module
-
-from . import validators
-from .models import Document
-from .models import Paragraph
-
-
-class ParagraphSerializer(serializers.Serializer):
- id = serializers.IntegerField(required=False)
- name = serializers.CharField(
- required=False,
- allow_blank=True,
- max_length=Paragraph._meta.get_field('name').max_length
- )
- weight = serializers.IntegerField()
- text = serializers.CharField()
-
-
-class DocumentSerializer(serializers.ModelSerializer):
- paragraphs = ParagraphSerializer(many=True, partial=True)
-
- class Meta:
- model = Document
- exclude = ('creator', 'module')
-
- def validate(self, data):
- if self.instance:
- document_pk = self.instance.pk
- else:
- document_pk = None
- module_pk = self.context['module_pk']
- module = Module.objects.get(pk=module_pk)
- validators.single_document_per_module(module, document_pk)
- data['module'] = module
- return data
-
- def create(self, validated_data):
- paragraphs = validated_data.pop('paragraphs')
- user = self.context['request'].user
- document = Document.objects.create(creator=user, **validated_data)
-
- for paragraph in paragraphs:
- Paragraph.objects.create(document=document, **paragraph)
-
- return document
-
- def update(self, instance, validated_data):
- instance.name = validated_data['name']
- instance.save()
- paragraphs = validated_data.pop('paragraphs')
-
- paragraph_ids = [item['id'] for item in paragraphs if 'id' in item]
- instance.paragraphs.exclude(id__in=paragraph_ids).delete()
-
- for paragraph in paragraphs:
- paragraph['document'] = instance
- if 'id' in paragraph:
- instance.paragraphs.filter(id=paragraph['id'])\
- .update(**paragraph)
- else:
- instance.paragraphs.create(**paragraph)
-
- return instance
diff --git a/euth/documents/templates/euth_documents/document_detail.html b/euth/documents/templates/euth_documents/document_detail.html
deleted file mode 100644
index 38ab23096..000000000
--- a/euth/documents/templates/euth_documents/document_detail.html
+++ /dev/null
@@ -1,46 +0,0 @@
-{% extends 'euth_projects/project_detail.html' %}
-{% load i18n react_comments_async %}
-
-{% block phase_content %}
-
-
-
- {% if object %}
-
-
-
- {% trans 'Comments'%}
-
-
- {% react_comments_async object %}
-
-
-
-
- {% endif %}
-
-
-{% endblock %}
diff --git a/euth/documents/templates/euth_documents/document_form.html b/euth/documents/templates/euth_documents/document_form.html
deleted file mode 100644
index 1d8783290..000000000
--- a/euth/documents/templates/euth_documents/document_form.html
+++ /dev/null
@@ -1,13 +0,0 @@
-{% extends 'euth_projects/project_detail.html' %}
-{% load react_paragraphs i18n rules %}
-
-{% block phase_content %}
-
- {% has_perm 'euth_documents.create_document' request.user view.module as show_create_form %}
- {% if show_create_form %}
- {% react_paragraphs view.document view.module %}
- {% else %}
- {% trans 'Moderators are currently creating a document.' %}
- {% endif %}
-
-{% endblock %}
diff --git a/euth/documents/templates/euth_documents/paragraph_detail.html b/euth/documents/templates/euth_documents/paragraph_detail.html
deleted file mode 100644
index 929e799cd..000000000
--- a/euth/documents/templates/euth_documents/paragraph_detail.html
+++ /dev/null
@@ -1,30 +0,0 @@
-{% extends "euth_projects/project_detail.html" %}
-{% load i18n react_comments_async react_ratings thumbnail%}
-
-{% block title %}
-{% trans 'Add a comment' %}
-{% endblock %}
-
-{% block content %}
-
-
-
-
{{ paragraph.name }}
-
{{ paragraph.text | safe }}
-
- {% trans 'Comments'%}
-
-
- {% react_comments_async paragraph %}
-
-
-
-{% endblock %}
diff --git a/euth/documents/templates/euth_documents/react_paragraphs.html b/euth/documents/templates/euth_documents/react_paragraphs.html
deleted file mode 100644
index 8a1d915ce..000000000
--- a/euth/documents/templates/euth_documents/react_paragraphs.html
+++ /dev/null
@@ -1,6 +0,0 @@
-{% load static i18n %}
-
-
diff --git a/euth/documents/templatetags/__init__.py b/euth/documents/templatetags/__init__.py
deleted file mode 100644
index e69de29bb..000000000
diff --git a/euth/documents/templatetags/react_paragraphs.py b/euth/documents/templatetags/react_paragraphs.py
deleted file mode 100644
index d7a37be88..000000000
--- a/euth/documents/templatetags/react_paragraphs.py
+++ /dev/null
@@ -1,27 +0,0 @@
-import json
-
-from ckeditor_uploader.widgets import CKEditorUploadingWidget
-from django import template
-
-from euth.documents.serializers import DocumentSerializer
-
-register = template.Library()
-
-
-@register.inclusion_tag('euth_documents/react_paragraphs.html',
- takes_context=True)
-def react_paragraphs(context, doc, module):
-
- serializer = DocumentSerializer(doc)
- document = serializer.data
- widget = CKEditorUploadingWidget(config_name='image-editor')
- widget._set_config()
- config = widget.config
-
- context = {
- 'document': json.dumps(document),
- 'module': module.pk,
- 'config': json.dumps(config),
- }
-
- return context
diff --git a/euth/documents/urls.py b/euth/documents/urls.py
deleted file mode 100644
index 4c076e115..000000000
--- a/euth/documents/urls.py
+++ /dev/null
@@ -1,8 +0,0 @@
-from django.urls import path
-
-from . import views
-
-urlpatterns = [
- path('/', views.ParagraphDetailView.as_view(),
- name='paragraph-detail'),
-]
diff --git a/euth/documents/validators.py b/euth/documents/validators.py
deleted file mode 100644
index d485d0756..000000000
--- a/euth/documents/validators.py
+++ /dev/null
@@ -1,15 +0,0 @@
-from django.core.exceptions import ValidationError
-from django.utils.translation import gettext as _
-
-
-def single_document_per_module(module, pk=None):
- from .models import Document
- siblings = Document.objects.filter(module=module)
-
- if pk:
- siblings = siblings.exclude(pk=pk)
-
- if len(siblings) != 0:
- raise ValidationError(
- _('Document for that module already exists')
- )
diff --git a/euth/documents/views.py b/euth/documents/views.py
deleted file mode 100644
index 3a234db0c..000000000
--- a/euth/documents/views.py
+++ /dev/null
@@ -1,33 +0,0 @@
-from django.views import generic
-from rules.contrib.views import PermissionRequiredMixin
-
-from euth.projects import mixins as prj_mixins
-
-from . import models
-
-
-class DocumentCreateView(
- prj_mixins.ProjectPhaseMixin,
- generic.TemplateView
-):
- template_name = 'euth_documents/document_form.html'
-
- @property
- def document(self):
- return models.Document.objects.filter(module=self.module).first()
-
-
-class DocumentDetailView(generic.DetailView, prj_mixins.ProjectPhaseMixin):
- model = models.Document
-
- def get_object(self):
- return models.Document.objects.filter(module=self.module).first()
-
-
-class ParagraphDetailView(PermissionRequiredMixin, generic.DetailView):
- permission_required = 'euth_documents.view_paragraph'
- model = models.Paragraph
-
- @property
- def raise_exception(self):
- return self.request.user.is_authenticated
diff --git a/euth/exports/__init__.py b/euth/exports/__init__.py
deleted file mode 100644
index eb43aef2c..000000000
--- a/euth/exports/__init__.py
+++ /dev/null
@@ -1,30 +0,0 @@
-from django.apps import apps as django_apps
-
-default_app_config = 'euth.exports.apps.Config'
-
-
-def register_export(description):
- def export_view_decorator(view_cls):
- exports.register(description, view_cls)
- return lambda cls: cls
- return export_view_decorator
-
-
-class ExportsRegistry:
- _registry = {}
-
- def register(self, description, cls):
- app_config = django_apps.get_containing_app_config(cls.__module__)
- app_label = app_config.label
- app_exports = self._registry.get(app_label, [])
- app_exports.append((description, cls))
- self._registry[app_label] = sorted(app_exports, key=lambda e: e[1])
-
- def __getitem__(self, module):
- return self._registry[module.phases.first().content().app]
-
- def __contains__(self, module):
- return module.phases.first().content().app in self._registry
-
-
-exports = ExportsRegistry()
diff --git a/euth/exports/apps.py b/euth/exports/apps.py
deleted file mode 100644
index e29da0227..000000000
--- a/euth/exports/apps.py
+++ /dev/null
@@ -1,10 +0,0 @@
-from django.apps import AppConfig
-
-
-class Config(AppConfig):
- name = 'euth.exports'
- label = 'euth_exports'
-
- def ready(self):
- from django.utils.module_loading import autodiscover_modules
- autodiscover_modules('exports', register_to=self.module.exports)
diff --git a/euth/exports/dashboard.py b/euth/exports/dashboard.py
deleted file mode 100644
index f54ea1d15..000000000
--- a/euth/exports/dashboard.py
+++ /dev/null
@@ -1,36 +0,0 @@
-from django.urls import reverse
-from django.utils.translation import gettext_lazy as _
-
-from adhocracy4.dashboard import DashboardComponent
-from adhocracy4.dashboard import components
-
-from . import exports
-from . import views
-
-
-class ExportModuleComponent(DashboardComponent):
- identifier = 'module_export'
- weight = 50
- label = _('Export Excel')
-
- def is_effective(self, module):
- return not module.project.is_draft and module in exports
-
- def get_progress(self, module):
- return 0, 0
-
- def get_base_url(self, module):
- return reverse('a4dashboard:export-module', kwargs={
- 'module_slug': module.slug,
- 'export_id': 0,
- })
-
- def get_urls(self):
- return [
- (r'^modules/(?P[-\w_]+)/export/(?P\d+)/$',
- views.ExportModuleDispatcher.as_view(),
- 'export-module'),
- ]
-
-
-components.register_module(ExportModuleComponent())
diff --git a/euth/exports/mixins.py b/euth/exports/mixins.py
deleted file mode 100644
index f6861aa44..000000000
--- a/euth/exports/mixins.py
+++ /dev/null
@@ -1,54 +0,0 @@
-from django.core.exceptions import ObjectDoesNotExist
-from django.utils.translation import gettext as _
-
-from adhocracy4.exports.views import VirtualFieldMixin
-
-
-class ItemExportWithLocationMixin(VirtualFieldMixin):
- def get_virtual_fields(self, virtual):
- if 'location_lon' not in virtual:
- virtual['location_lon'] = _('Location (Longitude)')
- if 'location_lat' not in virtual:
- virtual['location_lat'] = _('Location (Latitude)')
- return super().get_virtual_fields(virtual)
-
- def get_location_lon_data(self, item):
- if hasattr(item, 'point'):
- point = item.point
- if 'geometry' in point:
- return point['geometry']['coordinates'][0]
- return ''
-
- def get_location_lat_data(self, item):
- if hasattr(item, 'point'):
- point = item.point
- if 'geometry' in point:
- return point['geometry']['coordinates'][1]
- return ''
-
-
-class ItemExportWithRepliesToMixin(VirtualFieldMixin):
- def get_virtual_fields(self, virtual):
- virtual['replies_to'] = _('replies to')
- return super().get_virtual_fields(virtual)
-
- def get_replies_to_data(self, comment):
- try:
- return comment.parent_comment.get().pk
- except ObjectDoesNotExist:
- return ''
-
-
-class UserGeneratedContentExportMixin(VirtualFieldMixin):
- def get_virtual_fields(self, virtual):
- if 'creator' not in virtual:
- virtual['creator'] = _('Creator')
- if 'created' not in virtual:
- virtual['created'] = _('Created')
- return super().get_virtual_fields(virtual)
-
- def get_creator_data(self, item):
- return item.creator.username
-
- def get_created_data(self, item):
- return item.created.isoformat()
diff --git a/euth/exports/views.py b/euth/exports/views.py
deleted file mode 100644
index 6afe42457..000000000
--- a/euth/exports/views.py
+++ /dev/null
@@ -1,33 +0,0 @@
-from django.views import generic
-
-from adhocracy4.modules import models as module_models
-from adhocracy4.rules import mixins as rules_mixins
-
-from . import exports
-
-
-class ExportModuleDispatcher(rules_mixins.PermissionRequiredMixin,
- generic.View):
- permission_required = 'a4projects.change_project'
-
- def dispatch(self, request, *args, **kwargs):
- export_id = int(kwargs.pop('export_id'))
- module = module_models.Module.objects.get(slug=kwargs['module_slug'])
- project = module.project
-
- self.project = project
-
- # Since the PermissionRequiredMixin.dispatch method is never called
- # we have to check permissions manually
- if not self.has_permission():
- return self.handle_no_permission()
-
- module_exports = exports[module]
- assert len(module_exports) > export_id
-
- # Dispatch the request to the export view
- view = module_exports[export_id][1].as_view()
- return view(request, module=module, *args, **kwargs)
-
- def get_permission_object(self):
- return self.project
diff --git a/euth/follows/admin.py b/euth/follows/admin.py
deleted file mode 100644
index 341c9d3a6..000000000
--- a/euth/follows/admin.py
+++ /dev/null
@@ -1,10 +0,0 @@
-from django.contrib import admin
-
-from . import models
-
-
-class FollowAdmin(admin.ModelAdmin):
- list_display = ('__str__', 'creator', 'project', 'enabled')
-
-
-admin.site.register(models.Follow, FollowAdmin)
diff --git a/euth/follows/api.py b/euth/follows/api.py
deleted file mode 100644
index 245adc832..000000000
--- a/euth/follows/api.py
+++ /dev/null
@@ -1,26 +0,0 @@
-from django_filters.rest_framework import DjangoFilterBackend
-from rest_framework import mixins
-from rest_framework import permissions
-from rest_framework import viewsets
-
-from euth.contrib.api.mixins import AllowPUTAsCreateMixin
-
-from . import models
-from .serializers import FollowSerializer
-
-
-class FollowViewSet(AllowPUTAsCreateMixin,
- mixins.ListModelMixin,
- mixins.RetrieveModelMixin,
- mixins.UpdateModelMixin,
- viewsets.GenericViewSet):
-
- lookup_field = 'project__slug'
- queryset = models.Follow.objects
- serializer_class = FollowSerializer
- permission_classes = (permissions.IsAuthenticated,)
- filter_backends = (DjangoFilterBackend,)
- filterset_fields = ('enabled', )
-
- def get_queryset(self):
- return self.queryset.filter(creator=self.request.user)
diff --git a/euth/follows/migrations/0002_delete_follow.py b/euth/follows/migrations/0002_delete_follow.py
new file mode 100644
index 000000000..3d6d16a96
--- /dev/null
+++ b/euth/follows/migrations/0002_delete_follow.py
@@ -0,0 +1,16 @@
+# Generated by Django 3.2.20 on 2024-09-10 09:23
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('euth_follows', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.DeleteModel(
+ name='Follow',
+ ),
+ ]
diff --git a/euth/follows/models.py b/euth/follows/models.py
index 410b2a17a..e69de29bb 100644
--- a/euth/follows/models.py
+++ b/euth/follows/models.py
@@ -1,18 +0,0 @@
-from django.db import models
-
-from adhocracy4.models import base
-from adhocracy4.projects import models as prj_models
-
-
-class Follow(base.UserGeneratedContentModel):
- project = models.ForeignKey(
- prj_models.Project,
- on_delete=models.CASCADE
- )
- enabled = models.BooleanField(default=True)
-
- class Meta:
- unique_together = ('project', 'creator')
-
- def __str__(self):
- return 'Follow({}, enabled={})'.format(self.project, self.enabled)
diff --git a/euth/follows/serializers.py b/euth/follows/serializers.py
deleted file mode 100644
index 1925494f0..000000000
--- a/euth/follows/serializers.py
+++ /dev/null
@@ -1,32 +0,0 @@
-from rest_framework import serializers
-
-from adhocracy4.projects import models as prj_models
-
-from . import models
-
-
-class FollowSerializer(serializers.ModelSerializer):
-
- project = serializers.SlugRelatedField(read_only=True, slug_field='slug')
- follows = serializers.SerializerMethodField()
- creator = serializers.HiddenField(
- default=serializers.CurrentUserDefault()
- )
-
- class Meta:
- model = models.Follow
- read_only_field = ('project', 'creator', 'created', 'modified')
- exclude = ('id',)
-
- def save(self, *args, **kwargs):
- if 'project__slug' in kwargs:
- slug = kwargs.pop('project__slug')
- project = prj_models.Project.objects.get(slug=slug)
- kwargs['project'] = project
- return super().save(*args, **kwargs)
-
- def get_follows(self, obj):
- return models.Follow.objects.filter(
- project=obj.project,
- enabled=True
- ).count()
diff --git a/euth/follows/signals.py b/euth/follows/signals.py
index 2c05bab15..e69de29bb 100644
--- a/euth/follows/signals.py
+++ b/euth/follows/signals.py
@@ -1,17 +0,0 @@
-from django.db.models.signals import post_save
-
-from . import models
-
-
-def autofollow_hook(instance, **kwargs):
- if hasattr(instance.project, 'id'):
- models.Follow.objects.get_or_create(
- project=instance.project,
- creator=instance.creator,
- defaults={
- 'enabled': True,
- })
-
-
-post_save.connect(autofollow_hook, 'a4comments.Comment')
-post_save.connect(autofollow_hook, 'euth_ideas.Idea')
diff --git a/euth/ideas/admin.py b/euth/ideas/admin.py
deleted file mode 100644
index c3d5c668b..000000000
--- a/euth/ideas/admin.py
+++ /dev/null
@@ -1,10 +0,0 @@
-from django.contrib import admin
-
-from . import models
-
-
-class IdeaAdmin(admin.ModelAdmin):
- list_filter = ('module__project', 'module')
-
-
-admin.site.register(models.Idea, IdeaAdmin)
diff --git a/euth/ideas/exports.py b/euth/ideas/exports.py
deleted file mode 100644
index 7c87774dd..000000000
--- a/euth/ideas/exports.py
+++ /dev/null
@@ -1,28 +0,0 @@
-from django.utils.translation import gettext_lazy as _
-
-from adhocracy4.exports import mixins as a4_export_mixins
-from adhocracy4.exports import views as a4_export_views
-from euth.exports import register_export
-
-from . import models
-
-
-@register_export(_('Ideas with comments'))
-class IdeaExportView(a4_export_mixins.ItemExportWithLinkMixin,
- a4_export_mixins.ExportModelFieldsMixin,
- a4_export_mixins.ItemExportWithRatesMixin,
- a4_export_mixins.ItemExportWithCategoriesMixin,
- a4_export_mixins.ItemExportWithCommentCountMixin,
- a4_export_mixins.ItemExportWithCommentsMixin,
- a4_export_views.BaseItemExportView):
-
- model = models.Idea
- fields = ['name', 'description']
- html_fields = ['description']
-
- def get_queryset(self):
- return super().get_queryset() \
- .filter(module=self.module)\
- .annotate_comment_count()\
- .annotate_positive_rating_count()\
- .annotate_negative_rating_count()
diff --git a/euth/ideas/filters.py b/euth/ideas/filters.py
deleted file mode 100644
index a8ccb216d..000000000
--- a/euth/ideas/filters.py
+++ /dev/null
@@ -1,41 +0,0 @@
-from django.utils.translation import gettext_lazy as _
-
-from adhocracy4.categories import filters as cat_filters
-from adhocracy4.filters import widgets
-from adhocracy4.filters.filters import DefaultsFilterSet
-from adhocracy4.filters.filters import DynamicChoicesOrderingFilter
-from euth.ideas.models import Idea
-
-ORDERING_CHOICES = [
- ('newest', _('Most Recent')),
- ('comments', _('Most Comments')),
- ('support', _('Most Support'))
-]
-
-
-class OrderingFilterWidget(widgets.DropdownLinkWidget):
- label = _('Sort by')
-
-
-class IdeaFilterSet(DefaultsFilterSet):
-
- defaults = {
- 'ordering': 'newest'
- }
-
- category = cat_filters.CategoryFilter()
-
- ordering = DynamicChoicesOrderingFilter(
- fields=(
- ('-created', 'newest'),
- ('-comment_count', 'comments'),
- ('-positive_rating_count', 'support')
- ),
- choices=ORDERING_CHOICES,
- empty_label=None,
- widget=OrderingFilterWidget
- )
-
- class Meta:
- model = Idea
- fields = ['category', 'ordering']
diff --git a/euth/ideas/forms.py b/euth/ideas/forms.py
deleted file mode 100644
index 139ac600d..000000000
--- a/euth/ideas/forms.py
+++ /dev/null
@@ -1,19 +0,0 @@
-from django import forms
-
-from adhocracy4.categories import forms as category_forms
-from euth.contrib.mixins import ImageRightOfUseMixin
-
-from . import models
-
-
-class IdeaForm(category_forms.CategorizableFieldMixin,
- ImageRightOfUseMixin,
- forms.ModelForm):
-
- class Meta:
- model = models.Idea
- fields = ['name', 'description', 'image', 'category']
-
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.fields['category'].empty_label = '---'
diff --git a/euth/ideas/migrations/0007_delete_idea.py b/euth/ideas/migrations/0007_delete_idea.py
new file mode 100644
index 000000000..cd40b7ea3
--- /dev/null
+++ b/euth/ideas/migrations/0007_delete_idea.py
@@ -0,0 +1,17 @@
+# Generated by Django 3.2.20 on 2024-09-10 09:26
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('euth_maps', '0006_delete_mapidea'),
+ ('euth_ideas', '0006_alter_idea_image'),
+ ]
+
+ operations = [
+ migrations.DeleteModel(
+ name='Idea',
+ ),
+ ]
diff --git a/euth/ideas/models.py b/euth/ideas/models.py
index 9bac9bacc..e69de29bb 100644
--- a/euth/ideas/models.py
+++ b/euth/ideas/models.py
@@ -1,53 +0,0 @@
-from autoslug import AutoSlugField
-from ckeditor.fields import RichTextField
-from django.contrib.contenttypes.fields import GenericRelation
-from django.db import models
-from django.urls import reverse
-from django.utils.translation import gettext_lazy as _
-
-from adhocracy4 import transforms
-from adhocracy4.categories.fields import CategoryField
-from adhocracy4.comments import models as comment_models
-from adhocracy4.images import fields
-from adhocracy4.models import query
-from adhocracy4.modules import models as module_models
-from adhocracy4.ratings import models as rating_models
-
-
-class IdeaQuerySet(query.RateableQuerySet, query.CommentableQuerySet):
- pass
-
-
-class Idea(module_models.Item):
- slug = AutoSlugField(populate_from='name', unique=True)
- name = models.CharField(max_length=120)
- description = RichTextField()
- image = fields.ConfiguredImageField(
- 'idea_image',
- upload_to='ideas/images',
- blank=True,
- help_prefix=_(
- 'Please make sure the image is at least 400x200 px in '
- 'dimensions and at most 5 MB in file size.'
- ),
- )
- ratings = GenericRelation(rating_models.Rating,
- related_query_name='idea',
- object_id_field='object_pk')
- comments = GenericRelation(comment_models.Comment,
- related_query_name='idea',
- object_id_field='object_pk')
- category = CategoryField()
-
- objects = IdeaQuerySet.as_manager()
-
- def __str__(self):
- return self.name
-
- def save(self, *args, **kwargs):
- self.description = transforms.clean_html_field(
- self.description)
- super(Idea, self).save(*args, **kwargs)
-
- def get_absolute_url(self):
- return reverse('idea-detail', args=[str(self.slug)])
diff --git a/euth/ideas/phases.py b/euth/ideas/phases.py
deleted file mode 100644
index 27325670c..000000000
--- a/euth/ideas/phases.py
+++ /dev/null
@@ -1,94 +0,0 @@
-from django.utils.translation import gettext_lazy as _
-
-from adhocracy4 import phases
-
-from . import apps
-from . import models
-from . import views
-
-
-class IssuePhase(phases.PhaseContent):
- app = apps.Config.label
- phase = 'issue'
- view = views.IdeaListView
-
- name = _('Issue phase')
- description = _('Add new ideas.')
- module_name = _('ideas collection')
- icon = 'far fa-lightbulb'
-
- features = {
- 'crud': (models.Idea,),
- }
-
-
-class CollectPhase(phases.PhaseContent):
- app = apps.Config.label
- phase = 'collect'
- view = views.IdeaListView
-
- name = _('Collect phase')
- description = _('Add new ideas and comment them.')
- module_name = _('ideas collection')
- icon = 'far fa-lightbulb'
-
- features = {
- 'crud': (models.Idea,),
- 'comment': (models.Idea,),
- }
-
-
-class RatingPhase(phases.PhaseContent):
- app = apps.Config.label
- phase = 'rating'
- view = views.IdeaListView
-
- name = _('Rating phase')
- module_name = _('ideas collection')
- description = _('Get quantative feeback by rating the collected ideas.')
- icon = 'fas fa-chevron-up'
-
- features = {
- 'rate': (models.Idea,)
- }
-
-
-class FeedbackPhase(phases.PhaseContent):
- app = apps.Config.label
- phase = 'feedback'
- view = views.IdeaListView
-
- name = _('Feedback phase')
- description = _('Get feedback for collected ideas through rates and '
- 'comments.')
- module_name = _('ideas collection')
- icon = 'far fa-comment'
-
- features = {
- 'rate': (models.Idea,),
- 'comment': (models.Idea,)
- }
-
-
-class UniversalPhase(phases.PhaseContent):
- app = apps.Config.label
- phase = 'universal'
- view = views.IdeaListView
-
- name = _('Universal phase')
- module_name = _('ideas collection')
- description = _('Use all features of the idea collection.')
- icon = 'far fa-lightbulb'
-
- features = {
- 'crud': (models.Idea,),
- 'comment': (models.Idea,),
- 'rate': (models.Idea,),
- }
-
-
-phases.content.register(IssuePhase())
-phases.content.register(CollectPhase())
-phases.content.register(RatingPhase())
-phases.content.register(FeedbackPhase())
-phases.content.register(UniversalPhase())
diff --git a/euth/ideas/rules.py b/euth/ideas/rules.py
deleted file mode 100644
index 53ce71a16..000000000
--- a/euth/ideas/rules.py
+++ /dev/null
@@ -1,41 +0,0 @@
-import rules
-from rules.predicates import is_superuser
-
-from adhocracy4.modules.predicates import is_context_initiator
-from adhocracy4.modules.predicates import is_context_member
-from adhocracy4.modules.predicates import is_context_moderator
-from adhocracy4.modules.predicates import is_owner
-from adhocracy4.modules.predicates import is_public_context
-from adhocracy4.phases.predicates import phase_allows_add
-from adhocracy4.phases.predicates import phase_allows_change
-from adhocracy4.phases.predicates import phase_allows_comment
-from adhocracy4.phases.predicates import phase_allows_rate
-
-from .models import Idea
-
-rules.add_perm('euth_ideas.rate_idea',
- is_superuser | is_context_moderator | is_context_initiator |
- (is_context_member & phase_allows_rate))
-
-
-rules.add_perm('euth_ideas.comment_idea',
- is_superuser | is_context_moderator | is_context_initiator |
- (is_context_member & phase_allows_comment))
-
-
-rules.add_perm('euth_ideas.modify_idea',
- is_superuser | is_context_moderator | is_context_initiator |
- (is_context_member & is_owner & phase_allows_change))
-
-
-rules.add_perm('euth_ideas.propose_idea',
- is_superuser | is_context_moderator | is_context_initiator |
- (is_context_member & phase_allows_add(Idea)))
-
-
-rules.add_perm('euth_ideas.view_idea',
- is_superuser | is_context_moderator | is_context_initiator |
- is_context_member | is_public_context)
-
-rules.add_perm('euth_ideas.export_ideas',
- is_superuser | is_context_moderator | is_context_initiator)
diff --git a/euth/ideas/signals.py b/euth/ideas/signals.py
index 61d23c86a..e69de29bb 100644
--- a/euth/ideas/signals.py
+++ b/euth/ideas/signals.py
@@ -1,25 +0,0 @@
-from django.db.models.signals import post_delete
-from django.db.models.signals import post_init
-from django.db.models.signals import post_save
-from django.dispatch import receiver
-
-from adhocracy4.images import services as image_services
-
-from .models import Idea
-
-
-@receiver(post_init, sender=Idea)
-def backup_image_path(sender, instance, **kwargs):
- instance._current_image_file = instance.image
-
-
-@receiver(post_save, sender=Idea)
-def delete_old_image(sender, instance, **kwargs):
- if hasattr(instance, '_current_image_file'):
- if instance._current_image_file != instance.image:
- image_services.delete_images([instance._current_image_file])
-
-
-@receiver(post_delete, sender=Idea)
-def delete_images_for_Idea(sender, instance, **kwargs):
- image_services.delete_images([instance.image])
diff --git a/euth/ideas/templatetags/__init__.py b/euth/ideas/templatetags/__init__.py
deleted file mode 100644
index e69de29bb..000000000
diff --git a/euth/ideas/templatetags/idea_tags.py b/euth/ideas/templatetags/idea_tags.py
deleted file mode 100644
index 3897f44f2..000000000
--- a/euth/ideas/templatetags/idea_tags.py
+++ /dev/null
@@ -1,29 +0,0 @@
-from django import template
-
-from euth.ideas.models import Idea
-
-register = template.Library()
-
-
-@register.simple_tag
-def get_range(number, listcount):
- if number < 3:
- return range(1, 6)
- elif number > listcount - 2:
- return range(listcount - 4, listcount + 1)
- else:
- return range(number - 2, number + 3)
-
-
-@register.simple_tag
-def is_idea_list(module):
- return Idea.objects.filter(module=module).count() > 0
-
-
-@register.simple_tag
-def combined_url_parameter(request_query_dict, **kwargs):
- combined_query_dict = request_query_dict.copy()
- for key in kwargs:
- combined_query_dict.setlist(key, [kwargs[key]])
- encoded_parameter = '?' + combined_query_dict.urlencode()
- return encoded_parameter
diff --git a/euth/ideas/urls.py b/euth/ideas/urls.py
deleted file mode 100644
index 99255cac4..000000000
--- a/euth/ideas/urls.py
+++ /dev/null
@@ -1,14 +0,0 @@
-from django.urls import re_path
-
-from . import views
-
-urlpatterns = [
- re_path(r'create/module/(?P[-\w_]+)/$',
- views.IdeaCreateView.as_view(), name='idea-create'),
- re_path(r'^(?P[-\w_]+)/edit/$',
- views.IdeaUpdateView.as_view(), name='idea-update'),
- re_path(r'^(?P[-\w_]+)/delete/$',
- views.IdeaDeleteView.as_view(), name='idea-delete'),
- re_path(r'^(?P[-\w_]+)/$',
- views.IdeaDetailView.as_view(), name='idea-detail'),
-]
diff --git a/euth/ideas/views.py b/euth/ideas/views.py
deleted file mode 100644
index bcd0ecebb..000000000
--- a/euth/ideas/views.py
+++ /dev/null
@@ -1,118 +0,0 @@
-from django.contrib import messages
-from django.shortcuts import get_object_or_404
-from django.urls import reverse
-from django.utils.translation import gettext as _
-from django.views import generic
-from rules.contrib.views import PermissionRequiredMixin
-
-from adhocracy4.filters import views as filter_views
-from adhocracy4.modules.models import Module
-from euth.projects import mixins as prj_mixins
-
-from . import forms
-from . import models as idea_models
-from .filters import IdeaFilterSet
-
-
-class IdeaListView(
- prj_mixins.ProjectPhaseMixin,
- filter_views.FilteredListView
-):
- model = idea_models.Idea
- paginate_by = 15
- filter_set = IdeaFilterSet
-
- def get_queryset(self):
- return super().get_queryset().filter(module=self.module)
-
-
-class IdeaDetailView(PermissionRequiredMixin, generic.DetailView):
- model = idea_models.Idea
- queryset = idea_models.Idea.objects.annotate_positive_rating_count() \
- .annotate_negative_rating_count()
- permission_required = 'euth_ideas.view_idea'
-
- @property
- def raise_exception(self):
- return self.request.user.is_authenticated
-
- def get_context_data(self, **kwargs):
- context = super().get_context_data(**kwargs)
- context['author_is_moderator'] = self.object.creator in self.object. \
- project.moderators.all()
- return context
-
-
-class IdeaUpdateView(PermissionRequiredMixin, generic.UpdateView):
- model = idea_models.Idea
- form_class = forms.IdeaForm
- permission_required = 'euth_ideas.modify_idea'
-
- @property
- def raise_exception(self):
- return self.request.user.is_authenticated
-
- def get_context_data(self, **kwargs):
- context = super().get_context_data(**kwargs)
- context['project'] = self.object.project
- context['mode'] = 'update'
- return context
-
- def get_form_kwargs(self):
- kwargs = super().get_form_kwargs()
- kwargs['module'] = kwargs.get('instance').module
- return kwargs
-
-
-class IdeaCreateView(PermissionRequiredMixin, generic.CreateView):
- model = idea_models.Idea
- form_class = forms.IdeaForm
- permission_required = 'euth_ideas.propose_idea'
-
- @property
- def raise_exception(self):
- return self.request.user.is_authenticated
-
- def dispatch(self, *args, **kwargs):
- mod_slug = self.kwargs[self.slug_url_kwarg]
- self.module = get_object_or_404(Module, slug=mod_slug)
- self.project = self.module.project
- return super().dispatch(*args, **kwargs)
-
- def get_permission_object(self, *args, **kwargs):
- return self.module
-
- def get_context_data(self, **kwargs):
- context = super().get_context_data(**kwargs)
- context['slug'] = self.module.slug
- context['project'] = self.project
- context['mode'] = 'create'
- return context
-
- def form_valid(self, form):
- form.instance.creator = self.request.user
- form.instance.module = self.module
- return super().form_valid(form)
-
- def get_form_kwargs(self):
- kwargs = super().get_form_kwargs()
- kwargs['module'] = self.module
- return kwargs
-
-
-class IdeaDeleteView(PermissionRequiredMixin, generic.DeleteView):
- model = idea_models.Idea
- success_message = _("Your Idea has been deleted")
- permission_required = 'euth_ideas.modify_idea'
-
- @property
- def raise_exception(self):
- return self.request.user.is_authenticated
-
- def delete(self, request, *args, **kwargs):
- messages.success(self.request, self.success_message)
- return super(IdeaDeleteView, self).delete(request, *args, **kwargs)
-
- def get_success_url(self):
- return reverse('project-detail',
- kwargs={'slug': self.object.project.slug})
diff --git a/euth/maps/admin.py b/euth/maps/admin.py
deleted file mode 100644
index d4b0ce711..000000000
--- a/euth/maps/admin.py
+++ /dev/null
@@ -1,5 +0,0 @@
-from django.contrib import admin
-
-from . import models
-
-admin.site.register(models.MapIdea)
diff --git a/euth/maps/exports.py b/euth/maps/exports.py
deleted file mode 100644
index b6fd5c8b8..000000000
--- a/euth/maps/exports.py
+++ /dev/null
@@ -1,30 +0,0 @@
-from django.utils.translation import gettext_lazy as _
-
-from adhocracy4.exports import mixins as a4_export_mixins
-from adhocracy4.exports import views as a4_export_views
-from euth.exports import register_export
-from euth.exports.mixins import ItemExportWithLocationMixin
-
-from . import models
-
-
-@register_export(_('MapIdeas with comments'))
-class MapIdeaExportView(a4_export_mixins.ItemExportWithLinkMixin,
- a4_export_mixins.ExportModelFieldsMixin,
- ItemExportWithLocationMixin,
- a4_export_mixins.ItemExportWithRatesMixin,
- a4_export_mixins.ItemExportWithCategoriesMixin,
- a4_export_mixins.ItemExportWithCommentCountMixin,
- a4_export_mixins.ItemExportWithCommentsMixin,
- a4_export_views.BaseItemExportView):
-
- model = models.MapIdea
- fields = ['name', 'description']
- html_fields = ['description']
-
- def get_queryset(self):
- return super().get_queryset() \
- .filter(module=self.module)\
- .annotate_comment_count()\
- .annotate_positive_rating_count()\
- .annotate_negative_rating_count()
diff --git a/euth/maps/forms.py b/euth/maps/forms.py
deleted file mode 100644
index 704994184..000000000
--- a/euth/maps/forms.py
+++ /dev/null
@@ -1,25 +0,0 @@
-from django import forms
-from django.utils.translation import gettext as _
-
-from adhocracy4.categories import forms as category_forms
-from adhocracy4.maps import widgets
-from euth.contrib.mixins import ImageRightOfUseMixin
-
-from . import models
-
-
-class MapIdeaForm(category_forms.CategorizableFieldMixin,
- ImageRightOfUseMixin,
- forms.ModelForm):
-
- class Meta:
- model = models.MapIdea
- fields = ['name', 'description', 'image', 'point', 'category']
-
- def __init__(self, *args, **kwargs):
- self.settings = kwargs.pop('settings_instance')
- super().__init__(*args, **kwargs)
- self.fields['point'].widget = widgets.MapChoosePointWidget(
- polygon=self.settings.polygon)
- self.fields['point'].error_messages['required'] = _(
- 'Please locate your proposal on the map.')
diff --git a/euth/maps/migrations/0006_delete_mapidea.py b/euth/maps/migrations/0006_delete_mapidea.py
new file mode 100644
index 000000000..02858b78e
--- /dev/null
+++ b/euth/maps/migrations/0006_delete_mapidea.py
@@ -0,0 +1,16 @@
+# Generated by Django 3.2.20 on 2024-09-10 09:26
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('euth_maps', '0005_move_model_change_point_delete_areasettings'),
+ ]
+
+ operations = [
+ migrations.DeleteModel(
+ name='MapIdea',
+ ),
+ ]
diff --git a/euth/maps/models.py b/euth/maps/models.py
index d5dce00b9..e69de29bb 100644
--- a/euth/maps/models.py
+++ b/euth/maps/models.py
@@ -1,17 +0,0 @@
-from django.utils.translation import gettext_lazy as _
-
-from adhocracy4.maps import fields as map_fields
-from euth.ideas import models as idea_models
-
-
-class MapIdea(idea_models.Idea):
- point = map_fields.PointField(
- verbose_name=_('Where can your idea be located on a map?'),
- help_text=_('Click inside marked area to set a marker. '
- 'Drag and drop marker to change place.'))
-
- objects = idea_models.IdeaQuerySet.as_manager()
-
- def get_absolute_url(self):
- from django.urls import reverse
- return reverse('map-idea-detail', args=[str(self.slug)])
diff --git a/euth/maps/phases.py b/euth/maps/phases.py
deleted file mode 100644
index d85995ee5..000000000
--- a/euth/maps/phases.py
+++ /dev/null
@@ -1,61 +0,0 @@
-from django.utils.translation import gettext_lazy as _
-
-from adhocracy4 import phases
-
-from . import apps
-from . import models
-from . import views
-
-
-class IssuePhase(phases.PhaseContent):
- app = apps.Config.label
- phase = 'issue'
- view = views.MapIdeaListView
-
- name = _('Issue Phase')
- description = _('Add, comment and rate new ideas on a map.')
- module_name = _('ideas collection')
- icon = 'far fa-map'
-
- features = {
- 'crud': (models.MapIdea,),
- 'comment': (models.MapIdea,),
- 'rate': (models.MapIdea,),
- }
-
-
-class CollectPhase(phases.PhaseContent):
- app = apps.Config.label
- phase = 'collect'
- view = views.MapIdeaListView
-
- name = _('Collect Phase')
- description = _('Add and comment new ideas on a map.')
- module_name = _('ideas collection')
- icon = 'far fa-map'
-
- features = {
- 'crud': (models.MapIdea,),
- 'comment': (models.MapIdea,),
- }
-
-
-class RatingPhase(phases.PhaseContent):
- app = apps.Config.label
- phase = 'rating'
- view = views.MapIdeaListView
-
- name = _('Rating Phase')
- description = _('Get quantative feeback by rating the collected '
- 'ideas on a map.')
- module_name = _('ideas collection')
- icon = 'fas fa-chevron-up'
-
- features = {
- 'rate': (models.MapIdea,),
- }
-
-
-phases.content.register(IssuePhase())
-phases.content.register(CollectPhase())
-phases.content.register(RatingPhase())
diff --git a/euth/maps/rules.py b/euth/maps/rules.py
deleted file mode 100644
index d9cfed86a..000000000
--- a/euth/maps/rules.py
+++ /dev/null
@@ -1,38 +0,0 @@
-import rules
-from rules.predicates import is_superuser
-
-from adhocracy4.modules.predicates import is_context_initiator
-from adhocracy4.modules.predicates import is_context_member
-from adhocracy4.modules.predicates import is_context_moderator
-from adhocracy4.modules.predicates import is_owner
-from adhocracy4.modules.predicates import is_public_context
-from adhocracy4.phases.predicates import phase_allows_add
-from adhocracy4.phases.predicates import phase_allows_change
-from adhocracy4.phases.predicates import phase_allows_comment
-from adhocracy4.phases.predicates import phase_allows_rate
-
-from .models import MapIdea
-
-rules.add_perm('euth_maps.rate_mapidea',
- is_superuser | is_context_moderator | is_context_initiator |
- (is_context_member & phase_allows_rate))
-
-
-rules.add_perm('euth_maps.comment_mapidea',
- is_superuser | is_context_moderator | is_context_initiator |
- (is_context_member & phase_allows_comment))
-
-
-rules.add_perm('euth_maps.modify_mapidea',
- is_superuser | is_context_moderator | is_context_initiator |
- (is_context_member & is_owner & phase_allows_change))
-
-
-rules.add_perm('euth_maps.propose_mapidea',
- is_superuser | is_context_moderator | is_context_initiator |
- (is_context_member & phase_allows_add(MapIdea)))
-
-
-rules.add_perm('euth_maps.view_mapidea',
- is_superuser | is_context_moderator | is_context_initiator |
- is_context_member | is_public_context)
diff --git a/euth/maps/urls.py b/euth/maps/urls.py
deleted file mode 100644
index e7d60914a..000000000
--- a/euth/maps/urls.py
+++ /dev/null
@@ -1,14 +0,0 @@
-from django.urls import re_path
-
-from . import views
-
-urlpatterns = [
- re_path(r'create/module/(?P[-\w_]+)/$',
- views.MapIdeaCreateView.as_view(), name='map-idea-create'),
- re_path(r'^(?P[-\w_]+)/edit/$',
- views.MapIdeaUpdateView.as_view(), name='map-idea-update'),
- re_path(r'^(?P[-\w_]+)/delete/$',
- views.MapIdeaDeleteView.as_view(), name='map-idea-delete'),
- re_path(r'^(?P[-\w_]+)/$',
- views.MapIdeaDetailView.as_view(), name='map-idea-detail'),
-]
diff --git a/euth/maps/views.py b/euth/maps/views.py
deleted file mode 100644
index 7f1acb842..000000000
--- a/euth/maps/views.py
+++ /dev/null
@@ -1,43 +0,0 @@
-from euth.ideas import views as idea_views
-
-from . import forms
-from .models import MapIdea
-
-
-class MapIdeaListView(idea_views.IdeaListView):
- model = MapIdea
-
-
-class MapIdeaCreateView(idea_views.IdeaCreateView):
- model = MapIdea
- form_class = forms.MapIdeaForm
- permission_required = 'euth_maps.propose_mapidea'
-
- def get_form_kwargs(self):
- kwargs = super().get_form_kwargs()
- kwargs['settings_instance'] = self.module.settings_instance
- return kwargs
-
-
-class MapIdeaUpdateView(idea_views.IdeaUpdateView):
- model = MapIdea
- permission_required = 'euth_maps.modify_mapidea'
- form_class = forms.MapIdeaForm
-
- def get_form_kwargs(self):
- kwargs = super().get_form_kwargs()
- kwargs['settings_instance'] = self.object.module.settings_instance
- return kwargs
-
-
-class MapIdeaDeleteView(idea_views.IdeaDeleteView):
- model = MapIdea
- permission_required = 'euth_maps.modify_mapidea'
-
-
-class MapIdeaDetailView(idea_views.IdeaDetailView):
- model = MapIdea
- permission_required = 'euth_maps.view_mapidea'
- queryset = MapIdea.objects\
- .annotate_positive_rating_count()\
- .annotate_negative_rating_count()
diff --git a/euth/maps/widgets.py b/euth/maps/widgets.py
deleted file mode 100644
index e9c4025cd..000000000
--- a/euth/maps/widgets.py
+++ /dev/null
@@ -1,67 +0,0 @@
-from django.conf import settings
-from django.forms.widgets import Widget
-from django.template import loader
-from django.utils.safestring import mark_safe
-
-
-class MapChoosePolygonWidget(Widget):
-
- class Media:
- js = (
- 'leaflet.js',
- 'a4maps_choose_polygon.js',
- )
-
- css = {'all': [
- 'a4maps_choose_polygon.css',
- ]}
-
- def render(self, name, value, attrs, renderer=None):
-
- context = {
- 'map_url': settings.BASE_MAP,
- 'bbox': settings.MAP_BOUNDING_BOX,
- 'name': name,
- 'polygon': value
- }
-
- return mark_safe(
- loader.render_to_string(
- 'euth_maps/map_choose_polygon_widget.html',
- context
- )
- )
-
-
-class MapChoosePointWidget(Widget):
-
- def __init__(self, polygon, attrs=None):
- self.polygon = polygon
- super().__init__(attrs)
-
- class Media:
- js = (
- 'leaflet.js',
- 'a4maps_choose_point.js',
- )
- css = {'all': [
- 'leaflet.css',
- 'a4maps_choose_point.css'
- ]}
-
- def render(self, name, value, attrs, renderer=None):
-
- context = {
- 'map_url': settings.BASE_MAP,
- 'bbox': settings.MAP_BOUNDING_BOX,
- 'name': name,
- 'point': value,
- 'polygon': self.polygon
- }
-
- return mark_safe(
- loader.render_to_string(
- 'euth_maps/map_choose_point_widget.html',
- context
- )
- )
diff --git a/euth/memberships/admin.py b/euth/memberships/admin.py
deleted file mode 100644
index 6aac8efae..000000000
--- a/euth/memberships/admin.py
+++ /dev/null
@@ -1,6 +0,0 @@
-from django.contrib import admin
-
-from . import models
-
-admin.site.register(models.Request)
-admin.site.register(models.Invite)
diff --git a/euth/memberships/dashboard.py b/euth/memberships/dashboard.py
deleted file mode 100644
index d14b094fb..000000000
--- a/euth/memberships/dashboard.py
+++ /dev/null
@@ -1,53 +0,0 @@
-from django.urls import reverse
-from django.utils.translation import gettext_lazy as _
-
-from adhocracy4.dashboard import DashboardComponent
-from adhocracy4.dashboard import components
-
-from . import views
-
-
-class MembershipRequestComponent(DashboardComponent):
- identifier = 'members'
- weight = 10
- label = _('Members')
-
- def is_effective(self, project_or_module):
- return project_or_module.is_private or project_or_module.is_semipublic
-
- def get_base_url(self, project):
- return reverse('a4dashboard:members', kwargs={
- 'project_slug': project.slug
- })
-
- def get_urls(self):
- return [
- (r'^members/project/(?P[-\w_]+)/$',
- views.MembershipsDashboardView.as_view(component=self),
- 'members'),
- ]
-
-
-class MembershipInvitesComponent(DashboardComponent):
- identifier = 'invites'
- weight = 10
- label = _('Invited Members')
-
- def is_effective(self, project_or_module):
- return project_or_module.is_private or project_or_module.is_semipublic
-
- def get_base_url(self, project):
- return reverse('a4dashboard:invites', kwargs={
- 'project_slug': project.slug
- })
-
- def get_urls(self):
- return [
- (r'^invites/project/(?P[-\w_]+)/$',
- views.InviteDashboardView.as_view(component=self),
- 'invites'),
- ]
-
-
-components.register_project(MembershipRequestComponent())
-components.register_project(MembershipInvitesComponent())
diff --git a/euth/memberships/emails.py b/euth/memberships/emails.py
deleted file mode 100644
index eec296c2a..000000000
--- a/euth/memberships/emails.py
+++ /dev/null
@@ -1,23 +0,0 @@
-from adhocracy4 import emails
-
-
-class InviteEmail(emails.ExternalNotification):
- template_name = 'euth_memberships/emails/invite'
-
-
-class RequestReceivedEmail(emails.ModeratorNotification):
- template_name = 'euth_memberships/emails/request_received'
-
-
-class RequestAcceptedEmail(
- emails.mixins.SyncEmailMixin,
- emails.UserNotification
-):
- template_name = 'euth_memberships/emails/request_accepted'
-
-
-class RequestDeniedEmail(
- emails.mixins.SyncEmailMixin,
- emails.UserNotification
-):
- template_name = 'euth_memberships/emails/request_denied'
diff --git a/euth/memberships/forms.py b/euth/memberships/forms.py
deleted file mode 100644
index 9d69515b7..000000000
--- a/euth/memberships/forms.py
+++ /dev/null
@@ -1,103 +0,0 @@
-import re
-
-from django import forms
-from django.core.exceptions import ValidationError
-from django.core.validators import RegexValidator
-from django.utils.translation import gettext_lazy as _
-
-from euth.users.models import User
-
-from . import models
-
-
-class RequestModerationForm(forms.ModelForm):
-
- ACTIONS = (
- ('accept', 'Accept'),
- ('decline', 'Decline'),
- )
-
- action = forms.ChoiceField(
- choices=ACTIONS,
- required=False,
- widget=forms.RadioSelect()
- )
-
- class Meta:
- model = models.Request
- fields = ['action']
-
-
-class InviteForm(forms.ModelForm):
- accept = forms.CharField(required=False)
- reject = forms.CharField(required=False)
-
- class Meta:
- model = models.Invite
- fields = ['accept', 'reject']
-
- def clean(self):
- data = self.data
- if 'accept' not in data and 'reject' not in data:
- raise ValidationError('Reject or accept')
- return data
-
- def is_accepted(self):
- data = self.data
- return 'accept' in data and 'reject' not in data
-
-
-class InviteModerationForm(forms.ModelForm):
- delete = forms.BooleanField(initial=False, required=False)
-
- class Meta:
- model = models.Invite
- fields = ['delete']
-
-
-class ParticipantsModerationForm(forms.ModelForm):
- delete = forms.BooleanField(initial=False, required=False)
-
- class Meta:
- model = User
- fields = ['delete']
-
-
-class ProjectInviteForm(forms.Form):
- emails = forms.CharField(
- label=_('E-mail addresses of invited users'),
- help_text=_('Enter the e-mail addresses of users who you want '
- 'to invite, separated by commas. Invited users will get '
- 'an email to confirm their membership in the project.'),
- widget=forms.TextInput(attrs={
- 'placeholder': 'magdalena@example.com, yves@example.com,'
- ' nadine@example.com…'}
- ),
- validators=[RegexValidator(
- # a list of emails, separated by commas with optional space after
- regex=r'^([^@]+@[^@\s]+\.[^@\s,]+((,\s?)|$))+$',
- message=_('Please enter correct e-mail addresses, separated by '
- 'commas.')
- )]
- )
-
- def __init__(self, project, *args, **kwargs):
- self.project = project
- super().__init__(*args, **kwargs)
-
- def clean_emails(self):
- emails_str = self.cleaned_data['emails'].strip(' ,')
- emails = re.split(r',\s?', emails_str)
-
- query = {
- 'project': self.project,
- 'email__in': emails,
- }
- existing = models.Invite.objects.filter(**query) \
- .values_list('email', flat=True)
- if existing:
- for address in existing:
- raise ValidationError(
- '{} already invited'.format(address)
- )
- return emails
diff --git a/euth/memberships/migrations/0003_auto_20240910_1129.py b/euth/memberships/migrations/0003_auto_20240910_1129.py
new file mode 100644
index 000000000..26d915148
--- /dev/null
+++ b/euth/memberships/migrations/0003_auto_20240910_1129.py
@@ -0,0 +1,31 @@
+# Generated by Django 3.2.20 on 2024-09-10 09:29
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('euth_memberships', '0002_add_invitations'),
+ ]
+
+ operations = [
+ migrations.AlterUniqueTogether(
+ name='request',
+ unique_together=None,
+ ),
+ migrations.RemoveField(
+ model_name='request',
+ name='creator',
+ ),
+ migrations.RemoveField(
+ model_name='request',
+ name='project',
+ ),
+ migrations.DeleteModel(
+ name='Invite',
+ ),
+ migrations.DeleteModel(
+ name='Request',
+ ),
+ ]
diff --git a/euth/memberships/models.py b/euth/memberships/models.py
index 158476ea0..e69de29bb 100644
--- a/euth/memberships/models.py
+++ b/euth/memberships/models.py
@@ -1,86 +0,0 @@
-import uuid
-
-from django.conf import settings
-from django.db import models
-from django.urls import reverse
-
-from adhocracy4.models import base
-from adhocracy4.projects import models as prj_models
-
-from . import emails
-
-
-class InviteManager(models.Manager):
- def invite(self, creator, project, email):
- invite = super().create(project=project, creator=creator, email=email)
- emails.InviteEmail.send(invite)
- return invite
-
-
-class Invite(base.TimeStampedModel):
- """
- An invite to join a privte project.
- """
- creator = models.ForeignKey(
- settings.AUTH_USER_MODEL,
- on_delete=models.CASCADE
- )
- project = models.ForeignKey(
- prj_models.Project,
- on_delete=models.CASCADE
- )
- email = models.EmailField()
- token = models.UUIDField(default=uuid.uuid4, unique=True)
-
- objects = InviteManager()
-
- class Meta:
- unique_together = ('email', 'project')
-
- def __str__(self):
- return 'Invite to {s.project} for {s.email}'.format(s=self)
-
- def get_absolute_url(self):
- url_kwargs = {'invite_token': self.token}
- return reverse('membership-invite-detail', kwargs=url_kwargs)
-
- def accept(self, user):
- self.project.participants.add(user)
- self.delete()
-
- def reject(self):
- self.delete()
-
-
-class RequestManager(models.Manager):
- def request_membership(self, project, user):
- request = super().create(creator=user, project=project)
- emails.RequestReceivedEmail.send(request)
- return request
-
-
-class Request(base.UserGeneratedContentModel):
- """
- A request for joining a private project.
- """
- project = models.ForeignKey(
- prj_models.Project,
- on_delete=models.CASCADE
- )
-
- objects = RequestManager()
-
- class Meta:
- unique_together = ('creator', 'project')
-
- def __str__(self):
- return 'Request by {s.creator} for {s.project}'.format(s=self)
-
- def accept(self):
- self.project.participants.add(self.creator)
- self.delete()
- emails.RequestAcceptedEmail.send(self)
-
- def decline(self):
- self.delete()
- emails.RequestDeniedEmail.send(self)
diff --git a/euth/memberships/projects_urls.py b/euth/memberships/projects_urls.py
deleted file mode 100644
index 2048735c2..000000000
--- a/euth/memberships/projects_urls.py
+++ /dev/null
@@ -1,8 +0,0 @@
-from django.urls import re_path
-
-from . import views
-
-urlpatterns = [
- re_path(r'^(?P[-\w_]+)/$', views.RequestsProjectDetailView.as_view(),
- name='project-detail'),
-]
diff --git a/euth/memberships/urls.py b/euth/memberships/urls.py
deleted file mode 100644
index 1769b3d86..000000000
--- a/euth/memberships/urls.py
+++ /dev/null
@@ -1,22 +0,0 @@
-from django.urls import re_path
-
-from . import views
-
-urlpatterns = [
- re_path(
- r'^apply/(?P[-\w_]+)/$',
- views.RequestView.as_view(),
- name='memberships-request'
- ),
-
- re_path(
- r'^invites/(?P[-\w_]+)/$',
- views.InviteDetailView.as_view(),
- name='membership-invite-detail'
- ),
- re_path(
- r'^invites/(?P[-\w_]+)/accept/$',
- views.InviteUpdateView.as_view(),
- name='membership-invite-update'
- ),
-]
diff --git a/euth/memberships/views.py b/euth/memberships/views.py
deleted file mode 100644
index 6ed48e5aa..000000000
--- a/euth/memberships/views.py
+++ /dev/null
@@ -1,293 +0,0 @@
-from django import forms
-from django.contrib import messages
-from django.contrib.auth import mixins as mixin
-from django.http import Http404
-from django.shortcuts import redirect
-from django.utils.translation import gettext_lazy as _
-from django.views import generic
-from rules.contrib import views as rules_views
-
-from adhocracy4.dashboard import mixins
-from adhocracy4.projects import models as prj_models
-from adhocracy4.projects.mixins import ProjectMixin
-from euth.projects import mixins as prj_mixins
-from euth.users import models as user_models
-
-from . import forms as membership_forms
-from . import models as membership_models
-
-
-class InviteDashboardView(
- ProjectMixin,
- mixins.DashboardBaseMixin,
- mixins.DashboardComponentMixin,
- generic.detail.SingleObjectMixin,
- generic.FormView
-):
- model = prj_models.Project
- fields = []
- permission_required = 'a4projects.change_project'
- project_url_kwarg = 'project_slug'
- template_name = 'euth_memberships/dashboard_invites.html'
- form_class = membership_forms.ProjectInviteForm
-
- def get_permission_object(self):
- return self.project
-
- def dispatch(self, request, *args, **kwargs):
- self.object = self.get_object()
- return super().dispatch(request, *args, **kwargs)
-
- def get_object(self, queryset=None):
- return self.project
-
- def get_form_kwargs(self):
- kwargs = super().get_form_kwargs()
- kwargs['project'] = self.object
- return kwargs
-
- def process_invites(self, invite_formset):
- for form in invite_formset:
- if form.is_valid():
- data = form.cleaned_data
- if 'delete' in data and data['delete']:
- form.instance.delete()
-
- def post(self, request, *args, **kwargs):
- if 'submit_action' in request.POST:
- if request.POST['submit_action'] == 'remove_invites':
- MembershipInviteFormset = forms.modelformset_factory(
- membership_models.Invite,
- membership_forms.InviteModerationForm,
- extra=0)
- form_set = MembershipInviteFormset(request.POST)
- self.process_invites(form_set)
- messages.add_message(request,
- messages.SUCCESS,
- _('Project member invites '
- 'have been updated'))
- response = redirect(self.get_success_url())
- else:
- response = super().post(request, *args, **kwargs)
- return response
-
- @property
- def formset(self):
- membership_invites = \
- membership_models.Invite.objects.filter(project=self.project)
- MembershipInviteFormset = forms.modelformset_factory(
- membership_models.Invite,
- membership_forms.InviteModerationForm,
- extra=0)
- return MembershipInviteFormset(queryset=membership_invites)
-
- def form_valid(self, form):
- emails = form.cleaned_data['emails']
- user = self.request.user
- project = self.object
- for email in emails:
- membership_models.Invite.objects.invite(user, project, email)
- messages.add_message(self.request,
- messages.SUCCESS,
- _('Invites have been sent'))
- return super().form_valid(form)
-
- @property
- def raise_exception(self):
- return self.request.user.is_authenticated
-
-
-class MembershipsDashboardView(
- ProjectMixin,
- mixins.DashboardBaseMixin,
- mixins.DashboardComponentMixin,
- generic.TemplateView
-):
- model = prj_models.Project
- fields = []
- permission_required = 'a4projects.change_project'
- project_url_kwarg = 'project_slug'
- template_name = 'euth_memberships/dashboard_members.html'
-
- def get_permission_object(self):
- return self.project
-
- def get_object(self, queryset=None):
- return self.project
-
- @property
- def membership_request_form(self):
- membership_requests = \
- membership_models.Request.objects.filter(project=self.project)
- MembershipRequestFormset = forms.modelformset_factory(
- membership_models.Request,
- membership_forms.RequestModerationForm,
- extra=0)
- return MembershipRequestFormset(queryset=membership_requests)
-
- @property
- def member_delete_form(self):
- members = self.project.participants.all()
- MemberFormset = forms.modelformset_factory(
- user_models.User,
- membership_forms.ParticipantsModerationForm,
- extra=0)
- return MemberFormset(queryset=members)
-
- def process_requests(self, data):
- MembershipRequestFormset = forms.modelformset_factory(
- membership_models.Request,
- membership_forms.RequestModerationForm,
- extra=0)
- for form in MembershipRequestFormset(data):
- if form.is_valid():
- if form.cleaned_data['action'] == 'accept':
- form.instance.accept()
- if form.cleaned_data['action'] == 'decline':
- form.instance.decline()
-
- def process_members(self, data):
- MemberFormset = forms.modelformset_factory(
- user_models.User,
- membership_forms.ParticipantsModerationForm,
- extra=0)
- for form in MemberFormset(data):
- if form.is_valid():
- data = form.cleaned_data
- if 'delete' in data and data['delete']:
- self.project.participants.remove(form.instance)
-
- def post(self, request, *args, **kwargs):
- if 'submit_action' in request.POST:
- if request.POST['submit_action'] == 'remove_members':
- self.process_members(request.POST)
- messages.add_message(self.request,
- messages.SUCCESS,
- _('Project membership requests'
- ' have been updated'))
- if request.POST['submit_action'] == 'update_request':
- self.process_requests(request.POST)
- messages.add_message(request,
- messages.SUCCESS,
- _('Project membership requests'
- ' have been updated'))
- response = redirect(self.get_success_url())
- else:
- response = super().post(request, *args, **kwargs)
- return response
-
- @property
- def raise_exception(self):
- return self.request.user.is_authenticated
-
-
-class RequestsProjectDetailView(
- rules_views.PermissionRequiredMixin,
- prj_mixins.PhaseDispatchMixin,
- generic.DetailView
-):
- model = prj_models.Project
- permission_required = 'a4projects.view_project'
- project_url_kwarg = 'slug'
-
- @property
- def raise_exception(self):
- return self.request.user.is_authenticated
-
- @property
- def project(self):
- """
- Emulate ProjectMixin interface for template sharing.
- """
- return self.get_object()
-
- def handle_no_permission(self):
- """
- Check if user could join
- """
- user = self.request.user
- is_member = user.is_authenticated and self.project.has_member(user)
-
- if is_member:
- return super().handle_no_permission()
- else:
- return self.handle_no_membership()
-
- def handle_no_membership(self):
- membership_impossible = (
- not self.request.user.is_authenticated
- or self.project.is_draft
- or self.project.has_member(self.request.user)
- )
-
- if membership_impossible:
- return super().handle_no_permission()
- else:
- return redirect('memberships-request',
- project_slug=self.project.slug)
-
-
-class InviteDetailView(generic.DetailView):
- model = membership_models.Invite
- slug_field = 'token'
- slug_url_kwarg = 'invite_token'
-
- def dispatch(self, request, invite_token, *args, **kwargs):
- if request.user.is_authenticated:
- return redirect(
- 'membership-invite-update',
- invite_token=invite_token
- )
- else:
- return super().dispatch(request, *args, **kwargs)
-
-
-class InviteUpdateView(mixin.LoginRequiredMixin, generic.UpdateView):
- model = membership_models.Invite
- form_class = membership_forms.InviteForm
- slug_field = 'token'
- slug_url_kwarg = 'invite_token'
-
- def form_valid(self, form):
- if form.is_accepted():
- form.instance.accept(self.request.user)
- return redirect(form.instance.project.get_absolute_url())
- else:
- form.instance.reject()
- return redirect('/')
-
-
-class RequestView(mixin.LoginRequiredMixin, generic.DetailView):
- """
- Displays membership request if it exists or allows to create one.
- """
- model = membership_models.Request
- slug_field = 'project__slug'
- slug_url_kwarg = 'project_slug'
- context_object_name = 'join_request'
-
- def get_queryset(self):
- return self.model.objects.filter(creator=self.request.user)
-
- def get(self, request, *args, **kwargs):
- if self.project.has_member(request.user):
- return redirect(self.project.get_absolute_url())
- else:
- return super().get(request, *args, **kwargs)
-
- def post(self, request, *args, **kwargs):
- user = request.user
- project = self.project
- membership_models.Request.objects.request_membership(project, user)
- return redirect(self.request.path)
-
- def get_object(self, queryset=None):
- try:
- return super().get_object(queryset)
- except Http404:
- return None
-
- @property
- def project(self):
- project_slug = self.kwargs[self.slug_url_kwarg]
- return prj_models.Project.objects.get(slug=project_slug)
diff --git a/euth/offlinephases/admin.py b/euth/offlinephases/admin.py
deleted file mode 100644
index ceb025f53..000000000
--- a/euth/offlinephases/admin.py
+++ /dev/null
@@ -1,8 +0,0 @@
-from django.contrib import admin
-
-from . import models
-
-admin.site.register(models.Offlinephase)
-admin.site.register(models.FileUpload)
-admin.site.register(models.OfflineEvent)
-admin.site.register(models.OfflineEventFileUpload)
diff --git a/euth/offlinephases/apps.py b/euth/offlinephases/apps.py
index 3253fe242..01146b767 100644
--- a/euth/offlinephases/apps.py
+++ b/euth/offlinephases/apps.py
@@ -4,6 +4,3 @@
class Config(AppConfig):
name = 'euth.offlinephases'
label = 'euth_offlinephases'
-
- def ready(self):
- import euth.offlinephases.signals # noqa:F401
diff --git a/euth/offlinephases/dashboard.py b/euth/offlinephases/dashboard.py
deleted file mode 100644
index bd7dbedec..000000000
--- a/euth/offlinephases/dashboard.py
+++ /dev/null
@@ -1,43 +0,0 @@
-from django.urls import reverse
-from django.utils.translation import gettext_lazy as _
-
-from adhocracy4.dashboard import DashboardComponent
-from adhocracy4.dashboard import components
-
-from . import views
-
-
-class OfflineEventsComponent(DashboardComponent):
- identifier = 'offlineevents'
- weight = 20
- label = _('Offline Events')
-
- def is_effective(self, project):
- return True
-
- def get_progress(self, project):
- return 0, 0
-
- def get_base_url(self, project):
- return reverse('a4dashboard:offlineevent-list', kwargs={
- 'project_slug': project.slug
- })
-
- def get_urls(self):
- return [
- (r'^offlineevents/projects/(?P[-\w_]+)/$',
- views.OfflineEventListView.as_view(component=self),
- 'offlineevent-list'),
- (r'^offlineevents/create/project/(?P[-\w_]+)/$',
- views.OfflineEventCreateView.as_view(component=self),
- 'offlineevent-create'),
- (r'^offlineevents/(?P[-\w_]+)/update/$',
- views.OfflineEventUpdateView.as_view(component=self),
- 'offlineevent-update'),
- (r'^offlineevents/(?P[-\w_]+)/delete/$',
- views.OfflineEventDeleteView.as_view(component=self),
- 'offlineevent-delete')
- ]
-
-
-components.register_project(OfflineEventsComponent())
diff --git a/euth/offlinephases/forms.py b/euth/offlinephases/forms.py
deleted file mode 100644
index 1554e8056..000000000
--- a/euth/offlinephases/forms.py
+++ /dev/null
@@ -1,31 +0,0 @@
-from django import forms
-from django.utils.translation import gettext_lazy as _
-
-from adhocracy4.forms.fields import DateTimeField
-from euth.contrib import widgets
-
-from . import models
-
-
-class OfflineEventForm(forms.ModelForm):
-
- date = DateTimeField(
- time_format='%H:%M',
- required=True,
- require_all_fields=False,
- label=(_('Date'), _('Time'))
- )
-
- class Meta:
- model = models.OfflineEvent
- fields = ['name', 'date', 'description']
-
-
-class FileUploadForm(forms.ModelForm):
-
- class Meta:
- model = models.OfflineEventFileUpload
- fields = ['title', 'document']
- widgets = {
- 'document': widgets.FileUploadWidget()
- }
diff --git a/euth/offlinephases/migrations/0007_auto_20240910_1555.py b/euth/offlinephases/migrations/0007_auto_20240910_1555.py
new file mode 100644
index 000000000..ae9e4b0c4
--- /dev/null
+++ b/euth/offlinephases/migrations/0007_auto_20240910_1555.py
@@ -0,0 +1,23 @@
+# Generated by Django 3.2.20 on 2024-09-10 13:55
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('euth_offlinephases', '0006_alter_offlineevent_file_upload'),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name='offlineeventfileupload',
+ name='offlineevent',
+ ),
+ migrations.DeleteModel(
+ name='OfflineEvent',
+ ),
+ migrations.DeleteModel(
+ name='OfflineEventFileUpload',
+ ),
+ ]
diff --git a/euth/offlinephases/migrations/0008_auto_20240910_1557.py b/euth/offlinephases/migrations/0008_auto_20240910_1557.py
new file mode 100644
index 000000000..1a0d18cf5
--- /dev/null
+++ b/euth/offlinephases/migrations/0008_auto_20240910_1557.py
@@ -0,0 +1,23 @@
+# Generated by Django 3.2.20 on 2024-09-10 13:57
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('euth_offlinephases', '0007_auto_20240910_1555'),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name='offlinephase',
+ name='phase',
+ ),
+ migrations.DeleteModel(
+ name='FileUpload',
+ ),
+ migrations.DeleteModel(
+ name='Offlinephase',
+ ),
+ ]
diff --git a/euth/offlinephases/models.py b/euth/offlinephases/models.py
index 4c07a0732..8549274d2 100644
--- a/euth/offlinephases/models.py
+++ b/euth/offlinephases/models.py
@@ -1,94 +1,3 @@
-from autoslug import AutoSlugField
-from ckeditor_uploader.fields import RichTextUploadingField
-from django.contrib.contenttypes.fields import GenericRelation
-from django.db import models
-from django.utils import functional
-from django.utils.translation import gettext_lazy as _
-
-from adhocracy4 import transforms
-from adhocracy4.comments import models as comment_models
-from adhocracy4.models import base
-from adhocracy4.phases import models as phase_models
-from adhocracy4.projects import models as project_models
-from euth.contrib import validators
-
-
-class Offlinephase(base.TimeStampedModel):
- text = RichTextUploadingField(blank=True, config_name='image-editor',)
- phase = models.OneToOneField(
- phase_models.Phase,
- on_delete=models.CASCADE,
- primary_key=True,
- related_name='offlinephase'
- )
- comments = GenericRelation(comment_models.Comment,
- related_query_name='offlinephase',
- object_id_field='object_pk')
-
- def __str__(self):
- return "{}_offlinephase_{}".format(str(self.phase), self.pk)
-
- def save(self, *args, **kwargs):
- self.text = transforms.clean_html_field(
- self.text, 'image-editor')
- super().save(*args, **kwargs)
-
- @functional.cached_property
- def project(self):
- return self.phase.module.project
-
- @functional.cached_property
- def module(self):
- return self.phase.module
-
- @functional.cached_property
- def organisation(self):
- return self.project.organisation
-
-
-class OfflineEvent(base.TimeStampedModel):
- slug = AutoSlugField(populate_from='name', unique=True)
- name = models.CharField(max_length=120, verbose_name=_('Title'))
- date = models.DateTimeField(verbose_name=_('Date'))
- description = RichTextUploadingField(
- config_name='image-editor', verbose_name=_('Description'))
- project = models.ForeignKey(
- project_models.Project, on_delete=models.CASCADE)
-
- class Meta:
- ordering = ['-date']
-
- def __str__(self):
- return self.name
-
- def save(self, *args, **kwargs):
- self.description = transforms.clean_html_field(
- self.description, 'image-editor')
- super().save(*args, **kwargs)
-
-
def document_path(instance, filename):
return 'documents/offlineevent_{}/{}'.format(
instance.offlineevent.pk, filename)
-
-
-class FileUpload(base.TimeStampedModel):
- title = models.CharField(max_length=256)
- document = models.FileField(
- upload_to=document_path,
- validators=[validators.validate_file_type_and_size])
- offlinephase = models.ForeignKey(
- Offlinephase,
- on_delete=models.CASCADE
- )
-
-
-class OfflineEventFileUpload(base.TimeStampedModel):
- title = models.CharField(max_length=256)
- document = models.FileField(
- upload_to='offlineevents/documents',
- validators=[validators.validate_file_type_and_size])
- offlineevent = models.ForeignKey(
- OfflineEvent,
- on_delete=models.CASCADE
- )
diff --git a/euth/offlinephases/signals.py b/euth/offlinephases/signals.py
deleted file mode 100644
index 4bad5420e..000000000
--- a/euth/offlinephases/signals.py
+++ /dev/null
@@ -1,23 +0,0 @@
-from django.db.models.signals import post_delete
-from django.db.models.signals import post_init
-from django.db.models.signals import post_save
-from django.dispatch import receiver
-
-from .models import OfflineEventFileUpload
-
-
-@receiver(post_init, sender=OfflineEventFileUpload)
-def backup_file_path(sender, instance, **kwargs):
- instance._current_document_file = instance.document
-
-
-@receiver(post_save, sender=OfflineEventFileUpload)
-def delete_old_fileuploads(sender, instance, **kwargs):
- if hasattr(instance, '_current_document_file'):
- if instance._current_document_file != instance.document:
- instance._current_document_file.delete(False)
-
-
-@receiver(post_delete, sender=OfflineEventFileUpload)
-def delete_documents_for_Fileupload(sender, instance, **kwargs):
- instance.document.delete(False)
diff --git a/euth/offlinephases/urls.py b/euth/offlinephases/urls.py
deleted file mode 100644
index 35f0a0d30..000000000
--- a/euth/offlinephases/urls.py
+++ /dev/null
@@ -1,8 +0,0 @@
-from django.urls import path
-
-from . import views
-
-urlpatterns = [
- path('/', views.OfflineEventDetailView.as_view(),
- name='offlineevent-detail'),
-]
diff --git a/euth/offlinephases/views.py b/euth/offlinephases/views.py
deleted file mode 100644
index 41c25ac4f..000000000
--- a/euth/offlinephases/views.py
+++ /dev/null
@@ -1,190 +0,0 @@
-from django.contrib import messages
-from django.db import transaction
-from django.shortcuts import redirect
-from django.shortcuts import render
-from django.urls import reverse
-from django.utils.translation import gettext_lazy as _
-from django.views import generic
-from rules.contrib.views import PermissionRequiredMixin
-
-from adhocracy4.dashboard import mixins
-from adhocracy4.projects.mixins import ProjectMixin
-
-from . import forms
-from . import models
-from .mixins import OfflineEventFormMixin
-
-
-class OfflineEventDetailView(PermissionRequiredMixin,
- generic.DetailView):
- model = models.OfflineEvent
- permission_required = 'euth_offlinephases.view_offlineevent'
-
- @property
- def project(self):
- return self.object.project
-
-
-class OfflineEventListView(ProjectMixin,
- mixins.DashboardBaseMixin,
- mixins.DashboardComponentMixin,
- generic.ListView):
-
- model = models.OfflineEvent
- template_name = 'euth_offlinephases/offlineevent_list.html'
- permission_required = 'a4projects.change_project'
-
- def get_queryset(self):
- return super().get_queryset().filter(project=self.project)
-
- def get_permission_object(self):
- return self.project
-
-
-class OfflineEventCreateView(
- ProjectMixin,
- mixins.DashboardBaseMixin,
- mixins.DashboardComponentMixin,
- generic.TemplateView,
- OfflineEventFormMixin
-):
- template_name = 'euth_offlinephases/offlineevent_form.html'
- permission_required = 'a4projects.change_project'
- project_url_kwarg = 'project_slug'
-
- def get_permission_object(self):
- return self.project
-
- def get_success_url(self):
- return reverse(
- 'a4dashboard:offlineevent-list',
- kwargs={'project_slug': self.project.slug})
-
- def get_context_data(self, form=None, upload_forms=None, **kwargs):
- context = super().get_context_data(**kwargs)
- if not form:
- form = forms.OfflineEventForm()
- if not upload_forms:
- upload_forms = self.empty_upload_formset()
- context['form'] = form
- context['upload_forms'] = upload_forms
- return context
-
- def _process_formdata(self, form, upload_forms):
- form.instance.project = self.project
- with transaction.atomic():
- object = form.save()
- instances = upload_forms.save(commit=False)
- for instance in instances:
- instance.offlineevent = object
- instance.save()
-
- def post(self, request, *args, **kwargs):
- form = forms.OfflineEventForm(request.POST)
- upload_forms = self.filled_upload_formset(request)
- if form.is_valid() and upload_forms.is_valid():
- self._process_formdata(form, upload_forms)
- messages.add_message(request,
- messages.SUCCESS,
- _('Offline events '
- 'have been updated'))
- response = redirect(self.get_success_url())
- else:
- response = render(request,
- self.template_name,
- self.get_context_data(form=form,
- upload_forms=upload_forms))
- return response
-
-
-class OfflineEventUpdateView(ProjectMixin,
- mixins.DashboardBaseMixin,
- mixins.DashboardComponentMixin,
- generic.detail.SingleObjectMixin,
- generic.TemplateView,
- OfflineEventFormMixin):
-
- model = models.OfflineEvent
- permission_required = 'a4projects.change_project'
- template_name = 'euth_offlinephases/offlineevent_form.html'
- get_context_from_object = True
-
- def dispatch(self, *args, **kwargs):
- self.object = self.get_object()
- return super().dispatch(*args, **kwargs)
-
- def get_context_data(self, form=None, upload_forms=None, **kwargs):
- context = super().get_context_data(**kwargs)
- if not form:
- form = forms.OfflineEventForm(instance=self.get_object())
- if not upload_forms:
- queryset = \
- models.OfflineEventFileUpload\
- .objects.filter(offlineevent=self.get_object())
- upload_forms = self.update_upload_formset(queryset)
- context['form'] = form
- context['upload_forms'] = upload_forms
- return context
-
- def get_success_url(self):
- return reverse(
- 'a4dashboard:offlineevent-list',
- kwargs={'project_slug': self.project.slug})
-
- def get_permission_object(self):
- return self.project
-
- def _process_formdata(self, form, upload_forms):
- with transaction.atomic():
- form.save()
- instances = upload_forms.save(commit=False)
- for obj in upload_forms.deleted_objects:
- obj.delete()
- for instance in instances:
- instance.offlineevent = self.object
- instance.save()
-
- def post(self, request, *args, **kwargs):
- upload_forms = self.filled_upload_formset(request)
- form = forms.OfflineEventForm(request.POST, instance=self.object)
- if upload_forms.is_valid() and form.is_valid():
- self._process_formdata(form, upload_forms)
- messages.add_message(request,
- messages.SUCCESS,
- _('Offline events '
- 'have been updated'))
- response = redirect(self.get_success_url())
- else:
- response = render(request,
- self.template_name,
- self.get_context_data(
- form=form, upload_forms=upload_forms))
- return response
-
-
-class OfflineEventDeleteView(ProjectMixin,
- mixins.DashboardBaseMixin,
- mixins.DashboardComponentMixin,
- mixins.DashboardComponentDeleteSignalMixin,
- generic.DeleteView):
- model = models.OfflineEvent
- success_message = _('The offline event has been deleted')
- permission_required = 'a4projects.change_project'
- template_name = 'euth_offlinephases/offlineevent_confirm_delete.html'
- get_context_from_object = True
-
- def delete(self, request, *args, **kwargs):
- messages.success(self.request, self.success_message)
- return super().delete(request, *args, **kwargs)
-
- def get_success_url(self):
- return reverse(
- 'a4dashboard:offlineevent-list',
- kwargs={'project_slug': self.project.slug})
-
- @property
- def organisation(self):
- return self.project.organisation
-
- def get_permission_object(self):
- return self.project
diff --git a/euth/organisations/templates/euth_organisations/organisation_list.html b/euth/organisations/templates/euth_organisations/organisation_list.html
index 6805ac593..3a5e370d4 100644
--- a/euth/organisations/templates/euth_organisations/organisation_list.html
+++ b/euth/organisations/templates/euth_organisations/organisation_list.html
@@ -9,9 +9,6 @@ {% trans 'Take part in a project' %}
-
- {% trans 'Find a project' %}
-
{% trans 'Search for organisation' %}
diff --git a/euth/projects/__init__.py b/euth/projects/__init__.py
deleted file mode 100644
index 35330ffde..000000000
--- a/euth/projects/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-default_app_config = 'euth.projects.apps.Config'
diff --git a/euth/projects/api.py b/euth/projects/api.py
deleted file mode 100644
index 99da8fc02..000000000
--- a/euth/projects/api.py
+++ /dev/null
@@ -1,34 +0,0 @@
-from django.contrib.auth import get_user_model
-from rest_framework import permissions
-from rest_framework import viewsets
-
-from adhocracy4.projects.models import Project
-from euth.contrib.api.permissions import IsInitiatorOrSuperUser
-
-from . import emails
-from .serializers import ProjectSerializer
-
-User = get_user_model()
-
-
-class ProjectViewSet(viewsets.mixins.UpdateModelMixin,
- viewsets.GenericViewSet):
- queryset = Project.objects.all()
- serializer_class = ProjectSerializer
- permission_classes = (permissions.IsAuthenticated, IsInitiatorOrSuperUser)
-
- def perform_update(self, serializer):
- moderators = serializer.instance.moderators.values_list('id',
- flat=True)
- # create a set from current moderators and moderators after ajax
- moderators = set(moderators)
- post_moderators = set(serializer.initial_data['moderators'])
- new_moderator = post_moderators - moderators
- if len(new_moderator) == 1:
- new_moderator = User.objects.get(id__exact=new_moderator.pop())
- emails.ModeratorAddedEmail.send(
- serializer.instance,
- user_id=new_moderator.id
- )
-
- serializer.save()
diff --git a/euth/projects/apps.py b/euth/projects/apps.py
deleted file mode 100644
index 0855de9c6..000000000
--- a/euth/projects/apps.py
+++ /dev/null
@@ -1,11 +0,0 @@
-from django.apps import AppConfig
-
-
-class Config(AppConfig):
- name = 'euth.projects'
- label = 'euth_projects'
-
- def ready(self):
- from . import overwrites
- from . import signals # noqa
- overwrites.overwrite_access_enum_label()
diff --git a/euth/projects/dashboard.py b/euth/projects/dashboard.py
deleted file mode 100644
index c6ee8902b..000000000
--- a/euth/projects/dashboard.py
+++ /dev/null
@@ -1,31 +0,0 @@
-from django.urls import reverse
-from django.utils.translation import gettext_lazy as _
-
-from adhocracy4.dashboard import DashboardComponent
-from adhocracy4.dashboard import components
-
-from . import views
-
-
-class ModeratorsComponent(DashboardComponent):
- identifier = 'moderators'
- weight = 10
- label = _('Moderators')
-
- def is_effective(self, project_or_module):
- return True
-
- def get_base_url(self, project):
- return reverse('a4dashboard:moderators', kwargs={
- 'project_slug': project.slug
- })
-
- def get_urls(self):
- return [
- (r'^moderators/project/(?P[-\w_]+)/$',
- views.ModeratorsDashboardView.as_view(component=self),
- 'moderators'),
- ]
-
-
-components.register_project(ModeratorsComponent())
diff --git a/euth/projects/emails.py b/euth/projects/emails.py
deleted file mode 100644
index 7c434c6b9..000000000
--- a/euth/projects/emails.py
+++ /dev/null
@@ -1,41 +0,0 @@
-from django.contrib.auth import get_user_model
-
-from adhocracy4 import emails
-from euth.projects import tasks
-
-User = get_user_model()
-
-
-class ModeratorAddedEmail(emails.Email):
- template_name = 'euth_projects/emails/notify_new_moderator'
-
- def get_receivers(self):
- return [User.objects.get(id=self.kwargs['user_id'])]
-
-
-class DeleteProjectEmail(emails.Email):
- template_name = 'euth_projects/emails/delete_project'
-
- @classmethod
- def send_no_object(cls, object, *args, **kwargs):
- organisation = object.organisation
- object_dict = {
- 'name': object.name,
- 'initiators': list(organisation.initiators.all()
- .distinct()
- .values_list('email', flat=True)),
- 'organisation': organisation.name
- }
- tasks.send_async_no_object(
- cls.__module__, cls.__name__,
- object_dict, args, kwargs)
- return []
-
- def get_receivers(self):
- return self.object['initiators']
-
- def get_context(self):
- context = super().get_context()
- context['name'] = self.object['name']
- context['organisation'] = self.object['organisation']
- return context
diff --git a/euth/projects/filters.py b/euth/projects/filters.py
deleted file mode 100644
index 9cbbcfd9a..000000000
--- a/euth/projects/filters.py
+++ /dev/null
@@ -1,42 +0,0 @@
-from django.utils.translation import gettext_lazy as _
-
-from adhocracy4.filters.filters import DefaultsFilterSet
-from adhocracy4.filters.filters import DistinctOrderingFilter
-from adhocracy4.filters.filters import FreeTextFilter
-from adhocracy4.projects.models import Project
-from euth.contrib import filters as contrib_filters
-
-ORDERING_CHOICES = [
- ('newest', _('Most Recent')),
- ('name', _('Alphabetical'))
-]
-
-
-class ProjectFilterSet(DefaultsFilterSet):
-
- defaults = {
- 'ordering': 'newest'
- }
-
- search = FreeTextFilter(
- widget=contrib_filters.FreeTextSearchFilterWidget,
- fields=['name']
- )
-
- country = contrib_filters.CountryFilter(
- field_name='organisation__country',
- )
-
- ordering = DistinctOrderingFilter(
- fields=(
- ('-created', 'newest'),
- ('name', 'name'),
- ),
- choices=ORDERING_CHOICES,
- empty_label=None,
- widget=contrib_filters.OrderingFilterWidget
- )
-
- class Meta:
- model = Project
- fields = ['search', 'country', 'ordering']
diff --git a/euth/projects/forms.py b/euth/projects/forms.py
deleted file mode 100644
index 9a70c557a..000000000
--- a/euth/projects/forms.py
+++ /dev/null
@@ -1,18 +0,0 @@
-from django import forms
-from django.utils.translation import gettext_lazy as _
-
-from adhocracy4.projects.models import Project
-from euth.users.fields import UserSearchField
-
-
-class AddModeratorForm(forms.ModelForm):
- user = UserSearchField(required=False,
- identifier='moderators',
- help_text=_('Type in the username '
- 'of a user you would '
- 'like to add as moderator.'),
- label=_('Search for username'))
-
- class Meta:
- model = Project
- fields = ('user',)
diff --git a/euth/projects/mixins.py b/euth/projects/mixins.py
deleted file mode 100644
index d9ef484d3..000000000
--- a/euth/projects/mixins.py
+++ /dev/null
@@ -1,107 +0,0 @@
-from django.http import Http404
-from django.shortcuts import get_object_or_404
-from django.views import generic
-
-from adhocracy4.projects import mixins
-from adhocracy4.projects.models import Project
-
-
-class ProjectPhaseMixin(generic.base.ContextMixin):
- """Add project, module and phase attributes to the view and the template.
-
- This is a counterpart to the Phase- / ModuleDispatcher logic and a slight
- modification of the `adhocracy4.projects.mixins.ProjectMixin`.
-
- To consider the object context from get_object() set the
- get_context_from_object attribute. Enable this only if get_object() does
- not access the project, module and phase properties.
-
- In addition to the functionality from core an optional `phase` parameter
- can be given to a `project_slug` url. This parameter will select a phase
- from within the project by weight. Since weights are not guaranteed to be
- unique with in a project, this is not a universal approach. It only works
- in OPIN because there is always only one module per project.
-
- As a difference from core module kwargs, slug or get_object context are not
- supported. The are either not needed in OPIN and not easily implementable
- without infinit recursion.
-
- Designed to be used in item-(list, form or detail) view.
-
- """
- project_lookup_field = 'slug'
- project_url_kwarg = 'project_slug'
- module_lookup_field = 'slug'
- module_url_kwarg = 'module_slug'
- get_context_from_object = False
-
- @property
- def phase(self):
- try:
- weight = self.request.GET.get('phase')
- phase = self.project.phases.filter(weight=weight).first()
- except ValueError:
- phase = None
-
- if phase:
- return phase
- else:
- return self.project.last_active_phase
-
- @property
- def module(self):
- """Get the module from the current phase."""
- if self.phase:
- return self.phase.module
-
- @property
- def project(self):
- """Get the project from the kwargs, url or current object."""
- if self.get_context_from_object:
- return self._get_object(Project, 'project')
-
- if 'project' in self.kwargs \
- and isinstance(self.kwargs['project'], Project):
- return self.kwargs['project']
-
- if self.project_url_kwarg and self.project_url_kwarg in self.kwargs:
- lookup = {
- self.project_lookup_field: self.kwargs[self.project_url_kwarg]
- }
- return get_object_or_404(Project, **lookup)
-
- def _get_object(self, cls, attr):
- # CreateView supplies a defect get_object method and has to be excluded
- if hasattr(self, 'get_object') \
- and not isinstance(self, generic.CreateView):
- try:
- object = self.get_object()
- if isinstance(object, cls):
- return object
-
- if hasattr(object, attr):
- return getattr(object, attr)
- except Http404:
- return None
- except AttributeError:
- return None
-
- return None
-
-
-class PhaseDispatchMixin(
- ProjectPhaseMixin,
- mixins.PhaseDispatchMixin
-):
- """
- Desgined to be used as mixin in project detail view.
- """
-
- def _view_by_phase(self):
- """
- Take view from phase property if present.
- """
- if self.phase:
- return self.phase.view.as_view()
- else:
- return super()._view_by_phase()
diff --git a/euth/projects/overwrites.py b/euth/projects/overwrites.py
deleted file mode 100644
index fcb65bf20..000000000
--- a/euth/projects/overwrites.py
+++ /dev/null
@@ -1,14 +0,0 @@
-from django.utils.translation import gettext_lazy as _
-
-from adhocracy4.projects.enums import Access
-
-
-def overwrite_access_enum_label():
- Access.__labels__ = {
- Access.PRIVATE: _('Only invited users can see content and can '
- 'participate (private).'),
- Access.PUBLIC: _('All users can see content and can participate '
- '(public).'),
- Access.SEMIPUBLIC: _('All users can see content, only invited users '
- 'can participate (semi-public).')
- }
diff --git a/euth/projects/rules.py b/euth/projects/rules.py
deleted file mode 100644
index f0ac03efd..000000000
--- a/euth/projects/rules.py
+++ /dev/null
@@ -1,6 +0,0 @@
-import rules
-from rules.predicates import is_superuser
-
-rules.add_perm("euth_projects.add_project", is_superuser)
-
-rules.add_perm("euth_projects.change_project", is_superuser)
diff --git a/euth/projects/serializers.py b/euth/projects/serializers.py
deleted file mode 100644
index be7d22b5e..000000000
--- a/euth/projects/serializers.py
+++ /dev/null
@@ -1,34 +0,0 @@
-from django.db.models.functions import Lower
-from rest_framework import serializers
-
-from adhocracy4.projects.models import Project
-from euth.users.serializers import UserWithMailSerializer
-
-
-class ProjectSerializer(serializers.ModelSerializer):
-
- class Meta:
- model = Project
- fields = ('id', 'participants', 'moderators')
-
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.fields['moderators'].allow_empty = True
- self.fields['participants'].allow_empty = True
-
- def to_representation(self, instance):
- data = super().to_representation(instance)
- moderators = UserWithMailSerializer(
- instance=instance.moderators.order_by(Lower('username')).all(),
- many=True,
- allow_empty=True,
- )
- participants = UserWithMailSerializer(
- instance=instance.participants.order_by(Lower('username')).all(),
- many=True,
- allow_empty=True,
- )
- data['moderators'] = moderators.data
- data['participants'] = participants.data
-
- return data
diff --git a/euth/projects/signals.py b/euth/projects/signals.py
deleted file mode 100644
index bbe96287c..000000000
--- a/euth/projects/signals.py
+++ /dev/null
@@ -1,11 +0,0 @@
-from django.db.models.signals import post_delete
-from django.dispatch import receiver
-
-from adhocracy4.projects.models import Project
-
-from .emails import DeleteProjectEmail
-
-
-@receiver(post_delete, sender=Project)
-def send_delete_project_notification(sender, instance, **kwargs):
- DeleteProjectEmail.send_no_object(instance)
diff --git a/euth/projects/tasks.py b/euth/projects/tasks.py
deleted file mode 100644
index 11779d8e1..000000000
--- a/euth/projects/tasks.py
+++ /dev/null
@@ -1,12 +0,0 @@
-import importlib
-
-from background_task import background
-
-
-@background(schedule=1)
-def send_async_no_object(email_module_name,
- email_class_name,
- object, args, kwargs):
- mod = importlib.import_module(email_module_name)
- cls = getattr(mod, email_class_name)
- return cls().dispatch(object, *args, **kwargs)
diff --git a/euth/projects/templates/euth_projects/dashboard_moderators.html b/euth/projects/templates/euth_projects/dashboard_moderators.html
deleted file mode 100644
index e36a9beea..000000000
--- a/euth/projects/templates/euth_projects/dashboard_moderators.html
+++ /dev/null
@@ -1,18 +0,0 @@
-{% extends "a4dashboard/base_dashboard_project.html" %}
-{% load i18n react_user_list widget_tweaks %}
-
-{% block dashboard_project_content %}
-{% trans 'Moderators' %}
-
-
-{% react_user_list project.moderators.all project 'moderators' %}
-{{ form.media }}
-
-{% endblock %}
diff --git a/euth/projects/templates/euth_projects/emails/delete_project.en.email b/euth/projects/templates/euth_projects/emails/delete_project.en.email
deleted file mode 100644
index 3c12ff9a8..000000000
--- a/euth/projects/templates/euth_projects/emails/delete_project.en.email
+++ /dev/null
@@ -1,10 +0,0 @@
-{% extends 'email_base.'|add:part_type %}
-{% load verbose_name %}
-
-{% block subject %}Deletion of project{% endblock %}
-
-{% block headline %}The project {{ name }} was deleted.{% endblock %}
-
-{% block content %}The project "{{ name }}" on the participation platform {{ site.name }} was deleted.{% endblock %}
-
-{% block reason %}This email was sent to {{ receiver }}. This email was sent to you because you are an initiator of the organisation '{{ organisation }}', in wich a project was deleted.{% endblock %}
diff --git a/euth/projects/templates/euth_projects/emails/notify_new_moderator.en.email b/euth/projects/templates/euth_projects/emails/notify_new_moderator.en.email
deleted file mode 100644
index 031d9fae6..000000000
--- a/euth/projects/templates/euth_projects/emails/notify_new_moderator.en.email
+++ /dev/null
@@ -1,20 +0,0 @@
-{% extends 'email_base.'|add:part_type %}
-
-{% block subject %}You were added as a moderator to a project{% endblock %}
-
-{% block headline %}You are now a moderator of “{{ project.name }}” on {{ site.name }}{% endblock %}
-
-{% block content %}
-You were added as a moderator to the project “{{ project.name}}” on {{ site.name }} . Depending on the type of project, you can now edit and delete comments, add others as moderators, help to create a text for a Text Review project or create the documentation if there are offline phases in the project. You will also receive notifications about important events in the project.
-{% endblock %}
-
-{% block cta_url %}{{ email.get_host }}{{ project.get_absolute_url }}{% endblock %}
-{% block cta_label %}Visit the project{% endblock %}
-
-{% block reason %}
-This email was sent to {{ receiver.email }}. If you think you were added by mistake or if you have any questions regarding the project, you can contact the other moderators of the project:
-
-{% for moderator in project.moderators.all %}
-{{ moderator.username }} ({{ moderator.email }}),
-{% endfor %}
-{% endblock %}
diff --git a/euth/projects/templates/euth_projects/includes/project_carousel.html b/euth/projects/templates/euth_projects/includes/project_carousel.html
deleted file mode 100644
index 331d631cc..000000000
--- a/euth/projects/templates/euth_projects/includes/project_carousel.html
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
- {% for project in projects %}
- {% include "euth_projects/includes/project_tile.html" with project=project %}
- {% endfor %}
-
-
diff --git a/euth/projects/templates/euth_projects/includes/project_hero_unit.html b/euth/projects/templates/euth_projects/includes/project_hero_unit.html
deleted file mode 100644
index c34b56233..000000000
--- a/euth/projects/templates/euth_projects/includes/project_hero_unit.html
+++ /dev/null
@@ -1,50 +0,0 @@
-{% load thumbnail react_follows project_class_tags i18n rules %}
-
-
diff --git a/euth/projects/templates/euth_projects/includes/project_list_item.html b/euth/projects/templates/euth_projects/includes/project_list_item.html
deleted file mode 100644
index c8c30a1d0..000000000
--- a/euth/projects/templates/euth_projects/includes/project_list_item.html
+++ /dev/null
@@ -1,38 +0,0 @@
-{% load i18n project_class_tags thumbnail follow_tags %}
-{% get_class project as class %}
-
-
- {% get_days project as days %}
-
- {% if project.is_private %}
- {% trans 'private' %}
- {% elif project.is_semipublic %}
- {% trans 'semi-public' %}
- {% endif %}
- {% if project.has_finished %}
- {% trans 'finished' %}
- {% elif days %}
- {{ days }}
- {% endif %}
-
-
- {% if project.active_phase %}
- {% trans 'Phase' %} {{ project.module_set.first.phases_passed|length|add:1 }}:
- {{ project.active_phase.content.name }}
- {% endif %}
-
-
- {{ project.organisation.name }}
-
-
- {% if follow_user in project.participants.all %}
-
- {% endif %}
- {% is_following follow_user project as is_follower %}
- {% if is_follower %}
-
- {% endif %}
-
-
-
diff --git a/euth/projects/templates/euth_projects/includes/project_tile.html b/euth/projects/templates/euth_projects/includes/project_tile.html
deleted file mode 100644
index ca3200437..000000000
--- a/euth/projects/templates/euth_projects/includes/project_tile.html
+++ /dev/null
@@ -1,34 +0,0 @@
-{% load i18n project_class_tags thumbnail static %}
-{% get_class project as class %}
-
-
diff --git a/euth/projects/templates/euth_projects/project_detail.html b/euth/projects/templates/euth_projects/project_detail.html
deleted file mode 100644
index 1d1603b51..000000000
--- a/euth/projects/templates/euth_projects/project_detail.html
+++ /dev/null
@@ -1,233 +0,0 @@
-{% extends "base.html" %}
-{% load i18n static tz time_delta_tags timeline_tags wagtailcore_tags ckeditor_tags %}
-
-{% block title %}{{view.project.name}}{% endblock %}
-
-{% block extra_messages %}
- {{ block.super }}
-
- {% if view.project.is_draft %}
-
-
- {% trans 'This project is in not published yet.' %}
-
-
- {% endif %}
-{% endblock %}
-
-{% block content %}
-{% include 'euth_projects/includes/project_hero_unit.html' with project=view.project %}
-{% with view.project.phases as phases %}
-
-
{% trans 'Timeline' %}
-
-
-{% endwith %}
-
-
-
-
-
-
-
-
{% trans "info" %}
-
- {{ view.project.information | transform_collapsibles | richtext }}
- {% if view.project.contact_name or view.project.contact_address_text or view.project.contact_email or view.project.contact_phone or view.project.contact_url %}
-
{% trans 'Contact for questions' %}
- {% if view.project.contact_name %}
-
{{ view.project.contact_name }}
- {% endif %}
- {% if view.project.contact_address_text %}
-
{{ view.project.contact_address_text|linebreaks }}
- {% endif %}
- {% if view.project.contact_phone %}
-
{% trans 'Telephone' %}: {{ view.project.contact_phone }}
- {% endif %}
- {% if view.project.contact_email %}
-
- {% trans 'Email' %}
-
- {% endif %}
- {% if view.project.contact_url %}
-
- {% trans 'Website' %}
-
- {% endif %}
- {% endif %}
-
-
-
-
-
- {% url 'offlineevent-detail' pk=object.pk as offlineevent_url %}
- {% if not request.get_full_path == offlineevent_url %}
- {% if not view.phase %}
- {% trans 'Welcome!' as title %}
- {% trans 'This project has not started yet!' as text %}
- {% include 'euth_contrib/includes/info_note.html' with image='images/project_info_note.png' title=title text=text %}
- {% endif %}
-
- {% if view.phase and view.phase.is_over %}
- {% if view.project.future_phases or view.project.active_phase %}
- {% trans 'Thanks for participating!' as title %}
- {% trans 'This phase is already over. Thanks a lot to everyone for taking part in the project! The next phase will start soon.' as text %}
- {% include 'euth_contrib/includes/info_note.html' with image='images/project_info_note.png' title=title text=text %}
- {% else %}
- {% trans 'Thanks for participating!' as title %}
- {% trans 'This project is already over. Thanks a lot to everyone for taking part in the project!' as text %}
- {% include 'euth_contrib/includes/info_note.html' with image='images/project_info_note.png' title=title text=text %}
- {% endif %}
- {% endif %}
-
- {% if view.phase %}
-
-
{{ view.phase.name }}
- {% if view.phase and not view.phase.is_over %}
-
- {% get_time_left view.phase.end_date as time_left %}
-
- {% trans 'This phase ends in' %}
- {{ time_left }}
-
-
-
- {{ view.phase.start_date|date:'SHORT_DATETIME_FORMAT' }}
- -
- {{ view.phase.end_date|date:'SHORT_DATETIME_FORMAT' }}
- {% trans '(Your Timezone: ' %}
- {% get_current_timezone as TIME_ZONE %}
- {{ TIME_ZONE }})
-
-
- {% endif %}
-
{{ view.phase.description }}
-
- {% endif %}
-
- {% if view.project.is_private %}
-
-
- {% trans 'This project is not publicly visible. Only invited users can see content and actively participate.' %}
-
-
-
- {% elif view.project.is_semipublic %}
-
-
- {% trans 'This project is publicly visible. Invited users can actively participate.' %}
-
-
- {% endif %}
- {% endif %}
- {% block phase_content %}{% endblock %}
-
-
-
-
-
- {% trans 'The initiator hasn’t provided information on the expected outcome of the project yet.' as no_results %}
- {{ view.project.result | default:no_results | safe | linebreaks | transform_collapsibles | richtext }}
-
-
-
-
-{% if view.project.other_projects %}
-
-
-
-
-
-
- {% trans 'other projects from this organisation'%}
-
-
-
-
-
-
- {% include "euth_projects/includes/project_carousel.html" with projects=view.project.other_projects %}
-
-
-{% endif %}
-
-
-{% endblock %}
-
-
-{% block extra_js %}
-
-{% endblock %}
diff --git a/euth/projects/templatetags/project_class_tags.py b/euth/projects/templatetags/project_class_tags.py
deleted file mode 100644
index f3eb1fea8..000000000
--- a/euth/projects/templatetags/project_class_tags.py
+++ /dev/null
@@ -1,53 +0,0 @@
-from django import template
-from django.utils import timezone
-from django.utils.translation import gettext as _
-from django.utils.translation import ngettext
-
-from adhocracy4.projects.enums import Access
-
-register = template.Library()
-
-
-def days_left(project):
- """
- Replaces the project property days_left that was removed from a4.
- Still uses the active_phase property, which is deprecated.
- """
- active_phase = project.active_phase
- if active_phase:
- today = timezone.now().replace(hour=0, minute=0, second=0)
- time_delta = active_phase.end_date - today
- return time_delta.days
- return None
-
-
-@register.simple_tag
-def get_class(project):
- if project.is_archived:
- return 'archived'
- elif (project.access == Access.PRIVATE
- or project.access == Access.SEMIPUBLIC):
- return 'private'
- elif project.has_finished:
- return 'finished'
- elif days_left(project) is not None and days_left(project) <= 5:
- return 'running-out'
- else:
- return 'public'
-
-
-@register.simple_tag
-def get_days(project):
- number = days_left(project)
- if number and number >= 1 and number <= 5:
- text = ngettext(
- '%(number)d day left',
- '%(number)d days left',
- number) % {
- 'number': number,
- }
- return text
- elif number == 0:
- return _('a few hours left')
- else:
- return ''
diff --git a/euth/projects/templatetags/time_delta_tags.py b/euth/projects/templatetags/time_delta_tags.py
deleted file mode 100644
index 84c8b02fc..000000000
--- a/euth/projects/templatetags/time_delta_tags.py
+++ /dev/null
@@ -1,42 +0,0 @@
-from django import template
-from django.utils import timezone
-from django.utils.translation import gettext_lazy as _
-
-register = template.Library()
-
-
-@register.simple_tag
-def get_time_left(time):
-
- def seconds_in_units(seconds):
- unit_totals = []
-
- unit_limits = [
- ([_('week'), _('weeks')], 7 * 24 * 3600),
- ([_('day'), _('days')], 24 * 3600),
- ([_('hour'), _('hours')], 3600),
- ([_('minute'), _('minutes')], 60),
- ([_('second'), _('seconds')], 1)
- ]
-
- for unit_name, limit in unit_limits:
- if seconds >= limit:
- amount = int(float(seconds) / limit)
- if amount > 1:
- unit_totals.append((unit_name[1], amount))
- else:
- unit_totals.append((unit_name[0], amount))
- seconds = seconds - (amount * limit)
-
- return unit_totals
-
- if time:
- time_delta = time - timezone.now()
- seconds = time_delta.total_seconds()
- time_delta_list = seconds_in_units(seconds)
- time_delta_str = (_('and') +
- (' ')).join([(str(val[1]) + ' ' +
- str(val[0]) + ' ')
- for val in time_delta_list[:2]])
-
- return time_delta_str
diff --git a/euth/projects/templatetags/timeline_tags.py b/euth/projects/templatetags/timeline_tags.py
deleted file mode 100644
index 26b2f171e..000000000
--- a/euth/projects/templatetags/timeline_tags.py
+++ /dev/null
@@ -1,20 +0,0 @@
-from django import template
-from django.db.models import F
-
-register = template.Library()
-
-
-@register.simple_tag
-def get_sorted_date_items(project):
-
- phases_with_date = project.phases.filter(start_date__isnull=False)
- phases = list(phases_with_date.annotate(date=F('start_date')).values())
- events = list(project.offlineevent_set.all().values())
-
- object_list = phases + events
- return sorted(object_list, key=lambda k: k['date'])
-
-
-@register.simple_tag
-def get_past_phases_ids(project):
- return project.past_phases.values_list('id', flat=True)
diff --git a/euth/projects/urls.py b/euth/projects/urls.py
deleted file mode 100644
index cf4ebdb1c..000000000
--- a/euth/projects/urls.py
+++ /dev/null
@@ -1,11 +0,0 @@
-from django.urls import path
-from django.urls import re_path
-
-from . import views
-
-urlpatterns = [
- re_path(r'^project-delete/(?P[-\w_]+)/$',
- views.ProjectDeleteView.as_view(),
- name='project-delete'),
- path('', views.ProjectListView.as_view(), name='project-list')
-]
diff --git a/euth/projects/views.py b/euth/projects/views.py
deleted file mode 100644
index fa46e4699..000000000
--- a/euth/projects/views.py
+++ /dev/null
@@ -1,68 +0,0 @@
-from django.contrib import messages
-from django.urls import reverse_lazy
-from django.utils.translation import gettext_lazy as _
-from django.views import generic
-from rules.contrib.views import PermissionRequiredMixin
-
-from adhocracy4.dashboard import mixins
-from adhocracy4.filters import views as filter_views
-from adhocracy4.projects import models as prj_models
-from adhocracy4.projects.mixins import ProjectMixin
-from adhocracy4.projects.models import Project
-
-from . import filters
-from . import forms
-
-
-class ProjectListView(filter_views.FilteredListView):
- model = Project
- paginate_by = 12
- filter_set = filters.ProjectFilterSet
-
- def get_queryset(self):
- return super().get_queryset().filter(is_draft=False)
-
-
-class ModeratorsDashboardView(
- ProjectMixin,
- mixins.DashboardBaseMixin,
- mixins.DashboardComponentMixin,
- generic.detail.SingleObjectMixin,
- generic.FormView
-):
- model = prj_models.Project
- fields = []
- permission_required = 'a4projects.change_project'
- project_url_kwarg = 'project_slug'
- template_name = 'euth_projects/dashboard_moderators.html'
- form_class = forms.AddModeratorForm
-
- def get_permission_object(self):
- return self.project
-
- def dispatch(self, request, *args, **kwargs):
- self.object = self.get_object()
- return super().dispatch(request, *args, **kwargs)
-
- def get_object(self, queryset=None):
- return self.project
-
-
-class ProjectDeleteView(PermissionRequiredMixin,
- generic.DeleteView):
- model = prj_models.Project
- permission_required = 'a4projects.change_project'
- http_method_names = ['post']
- success_message = _("Project '%(name)s' was deleted successfully.")
-
- def get_success_url(self):
- return reverse_lazy('a4dashboard:project-list',
- kwargs={
- 'organisation_slug':
- self.get_object().organisation.slug}
- )
-
- def delete(self, request, *args, **kwargs):
- obj = self.get_object()
- messages.success(self.request, self.success_message % obj.__dict__)
- return super().delete(request, *args, **kwargs)
diff --git a/euth/users/api.py b/euth/users/api.py
deleted file mode 100644
index ae31ed76b..000000000
--- a/euth/users/api.py
+++ /dev/null
@@ -1,28 +0,0 @@
-from django.db.models.functions import Lower
-from rest_framework import filters
-from rest_framework import permissions
-from rest_framework import serializers
-from rest_framework import viewsets
-
-from .models import User
-from .serializers import UserSerializer
-
-
-class UserViewSet(viewsets.ReadOnlyModelViewSet):
- """
- A simple ViewSet for viewing accounts.
- """
- queryset = User.objects.all().order_by(Lower('username'))
- serializer_class = UserSerializer
- permission_classes = (permissions.IsAuthenticated,)
- filter_backends = (filters.SearchFilter,)
- search_fields = ('^username',)
-
- def list(self, request, *args, **kwargs):
- is_no_search = not (hasattr(request, 'query_params') and
- 'search' in request.query_params and
- len(request.query_params['search']) > 0)
- if is_no_search:
- raise serializers.ValidationError(
- detail='Only usable with search.')
- return super().list(request, *args, **kwargs)
diff --git a/euth/users/apps.py b/euth/users/apps.py
index 4f7645f8e..45ed25ab5 100644
--- a/euth/users/apps.py
+++ b/euth/users/apps.py
@@ -4,6 +4,3 @@
class Config(AppConfig):
name = 'euth.users'
label = 'euth_users'
-
- def ready(self):
- import euth.users.signals # noqa:F401
diff --git a/euth/users/fields.py b/euth/users/fields.py
deleted file mode 100644
index 69197a8a7..000000000
--- a/euth/users/fields.py
+++ /dev/null
@@ -1,23 +0,0 @@
-from django.core.exceptions import ObjectDoesNotExist
-from django.core.exceptions import ValidationError
-from django.forms.fields import Field
-
-from euth.users.forms import User
-from euth.users.widgets import UserSearchInput
-
-
-class UserSearchField(Field):
-
- def __init__(self, identifier=None, *args, **kwargs):
- self.identifier = identifier
- self.widget = UserSearchInput(identifier=self.identifier)
-
- super().__init__(*args, **kwargs)
-
- def to_python(self, value):
- if value:
- try:
- return User.objects.get(username__exact=value)
- except ObjectDoesNotExist:
- raise ValidationError('{} doesn\'t exist.'.format(value))
- return None
diff --git a/euth/users/migrations/0019_alter_user_timezone.py b/euth/users/migrations/0019_alter_user_timezone.py
new file mode 100644
index 000000000..91b722e52
--- /dev/null
+++ b/euth/users/migrations/0019_alter_user_timezone.py
@@ -0,0 +1,18 @@
+# Generated by Django 3.2.20 on 2024-09-11 08:02
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('euth_users', '0018_alter_user_timezone'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='user',
+ name='timezone',
+ field=models.CharField(blank=True, choices=[('Africa/Abidjan', 'Africa/Abidjan'), ('Africa/Accra', 'Africa/Accra'), ('Africa/Addis_Ababa', 'Africa/Addis_Ababa'), ('Africa/Algiers', 'Africa/Algiers'), ('Africa/Asmara', 'Africa/Asmara'), ('Africa/Bamako', 'Africa/Bamako'), ('Africa/Bangui', 'Africa/Bangui'), ('Africa/Banjul', 'Africa/Banjul'), ('Africa/Bissau', 'Africa/Bissau'), ('Africa/Blantyre', 'Africa/Blantyre'), ('Africa/Brazzaville', 'Africa/Brazzaville'), ('Africa/Bujumbura', 'Africa/Bujumbura'), ('Africa/Cairo', 'Africa/Cairo'), ('Africa/Casablanca', 'Africa/Casablanca'), ('Africa/Ceuta', 'Africa/Ceuta'), ('Africa/Conakry', 'Africa/Conakry'), ('Africa/Dakar', 'Africa/Dakar'), ('Africa/Dar_es_Salaam', 'Africa/Dar_es_Salaam'), ('Africa/Djibouti', 'Africa/Djibouti'), ('Africa/Douala', 'Africa/Douala'), ('Africa/El_Aaiun', 'Africa/El_Aaiun'), ('Africa/Freetown', 'Africa/Freetown'), ('Africa/Gaborone', 'Africa/Gaborone'), ('Africa/Harare', 'Africa/Harare'), ('Africa/Johannesburg', 'Africa/Johannesburg'), ('Africa/Juba', 'Africa/Juba'), ('Africa/Kampala', 'Africa/Kampala'), ('Africa/Khartoum', 'Africa/Khartoum'), ('Africa/Kigali', 'Africa/Kigali'), ('Africa/Kinshasa', 'Africa/Kinshasa'), ('Africa/Lagos', 'Africa/Lagos'), ('Africa/Libreville', 'Africa/Libreville'), ('Africa/Lome', 'Africa/Lome'), ('Africa/Luanda', 'Africa/Luanda'), ('Africa/Lubumbashi', 'Africa/Lubumbashi'), ('Africa/Lusaka', 'Africa/Lusaka'), ('Africa/Malabo', 'Africa/Malabo'), ('Africa/Maputo', 'Africa/Maputo'), ('Africa/Maseru', 'Africa/Maseru'), ('Africa/Mbabane', 'Africa/Mbabane'), ('Africa/Mogadishu', 'Africa/Mogadishu'), ('Africa/Monrovia', 'Africa/Monrovia'), ('Africa/Nairobi', 'Africa/Nairobi'), ('Africa/Ndjamena', 'Africa/Ndjamena'), ('Africa/Niamey', 'Africa/Niamey'), ('Africa/Nouakchott', 'Africa/Nouakchott'), ('Africa/Ouagadougou', 'Africa/Ouagadougou'), ('Africa/Porto-Novo', 'Africa/Porto-Novo'), ('Africa/Sao_Tome', 'Africa/Sao_Tome'), ('Africa/Tripoli', 'Africa/Tripoli'), ('Africa/Tunis', 'Africa/Tunis'), ('Africa/Windhoek', 'Africa/Windhoek'), ('America/Adak', 'America/Adak'), ('America/Anchorage', 'America/Anchorage'), ('America/Anguilla', 'America/Anguilla'), ('America/Antigua', 'America/Antigua'), ('America/Araguaina', 'America/Araguaina'), ('America/Argentina/Buenos_Aires', 'America/Argentina/Buenos_Aires'), ('America/Argentina/Catamarca', 'America/Argentina/Catamarca'), ('America/Argentina/Cordoba', 'America/Argentina/Cordoba'), ('America/Argentina/Jujuy', 'America/Argentina/Jujuy'), ('America/Argentina/La_Rioja', 'America/Argentina/La_Rioja'), ('America/Argentina/Mendoza', 'America/Argentina/Mendoza'), ('America/Argentina/Rio_Gallegos', 'America/Argentina/Rio_Gallegos'), ('America/Argentina/Salta', 'America/Argentina/Salta'), ('America/Argentina/San_Juan', 'America/Argentina/San_Juan'), ('America/Argentina/San_Luis', 'America/Argentina/San_Luis'), ('America/Argentina/Tucuman', 'America/Argentina/Tucuman'), ('America/Argentina/Ushuaia', 'America/Argentina/Ushuaia'), ('America/Aruba', 'America/Aruba'), ('America/Asuncion', 'America/Asuncion'), ('America/Atikokan', 'America/Atikokan'), ('America/Bahia', 'America/Bahia'), ('America/Bahia_Banderas', 'America/Bahia_Banderas'), ('America/Barbados', 'America/Barbados'), ('America/Belem', 'America/Belem'), ('America/Belize', 'America/Belize'), ('America/Blanc-Sablon', 'America/Blanc-Sablon'), ('America/Boa_Vista', 'America/Boa_Vista'), ('America/Bogota', 'America/Bogota'), ('America/Boise', 'America/Boise'), ('America/Cambridge_Bay', 'America/Cambridge_Bay'), ('America/Campo_Grande', 'America/Campo_Grande'), ('America/Cancun', 'America/Cancun'), ('America/Caracas', 'America/Caracas'), ('America/Cayenne', 'America/Cayenne'), ('America/Cayman', 'America/Cayman'), ('America/Chicago', 'America/Chicago'), ('America/Chihuahua', 'America/Chihuahua'), ('America/Ciudad_Juarez', 'America/Ciudad_Juarez'), ('America/Costa_Rica', 'America/Costa_Rica'), ('America/Creston', 'America/Creston'), ('America/Cuiaba', 'America/Cuiaba'), ('America/Curacao', 'America/Curacao'), ('America/Danmarkshavn', 'America/Danmarkshavn'), ('America/Dawson', 'America/Dawson'), ('America/Dawson_Creek', 'America/Dawson_Creek'), ('America/Denver', 'America/Denver'), ('America/Detroit', 'America/Detroit'), ('America/Dominica', 'America/Dominica'), ('America/Edmonton', 'America/Edmonton'), ('America/Eirunepe', 'America/Eirunepe'), ('America/El_Salvador', 'America/El_Salvador'), ('America/Fort_Nelson', 'America/Fort_Nelson'), ('America/Fortaleza', 'America/Fortaleza'), ('America/Glace_Bay', 'America/Glace_Bay'), ('America/Goose_Bay', 'America/Goose_Bay'), ('America/Grand_Turk', 'America/Grand_Turk'), ('America/Grenada', 'America/Grenada'), ('America/Guadeloupe', 'America/Guadeloupe'), ('America/Guatemala', 'America/Guatemala'), ('America/Guayaquil', 'America/Guayaquil'), ('America/Guyana', 'America/Guyana'), ('America/Halifax', 'America/Halifax'), ('America/Havana', 'America/Havana'), ('America/Hermosillo', 'America/Hermosillo'), ('America/Indiana/Indianapolis', 'America/Indiana/Indianapolis'), ('America/Indiana/Knox', 'America/Indiana/Knox'), ('America/Indiana/Marengo', 'America/Indiana/Marengo'), ('America/Indiana/Petersburg', 'America/Indiana/Petersburg'), ('America/Indiana/Tell_City', 'America/Indiana/Tell_City'), ('America/Indiana/Vevay', 'America/Indiana/Vevay'), ('America/Indiana/Vincennes', 'America/Indiana/Vincennes'), ('America/Indiana/Winamac', 'America/Indiana/Winamac'), ('America/Inuvik', 'America/Inuvik'), ('America/Iqaluit', 'America/Iqaluit'), ('America/Jamaica', 'America/Jamaica'), ('America/Juneau', 'America/Juneau'), ('America/Kentucky/Louisville', 'America/Kentucky/Louisville'), ('America/Kentucky/Monticello', 'America/Kentucky/Monticello'), ('America/Kralendijk', 'America/Kralendijk'), ('America/La_Paz', 'America/La_Paz'), ('America/Lima', 'America/Lima'), ('America/Los_Angeles', 'America/Los_Angeles'), ('America/Lower_Princes', 'America/Lower_Princes'), ('America/Maceio', 'America/Maceio'), ('America/Managua', 'America/Managua'), ('America/Manaus', 'America/Manaus'), ('America/Marigot', 'America/Marigot'), ('America/Martinique', 'America/Martinique'), ('America/Matamoros', 'America/Matamoros'), ('America/Mazatlan', 'America/Mazatlan'), ('America/Menominee', 'America/Menominee'), ('America/Merida', 'America/Merida'), ('America/Metlakatla', 'America/Metlakatla'), ('America/Mexico_City', 'America/Mexico_City'), ('America/Miquelon', 'America/Miquelon'), ('America/Moncton', 'America/Moncton'), ('America/Monterrey', 'America/Monterrey'), ('America/Montevideo', 'America/Montevideo'), ('America/Montserrat', 'America/Montserrat'), ('America/Nassau', 'America/Nassau'), ('America/New_York', 'America/New_York'), ('America/Nome', 'America/Nome'), ('America/Noronha', 'America/Noronha'), ('America/North_Dakota/Beulah', 'America/North_Dakota/Beulah'), ('America/North_Dakota/Center', 'America/North_Dakota/Center'), ('America/North_Dakota/New_Salem', 'America/North_Dakota/New_Salem'), ('America/Nuuk', 'America/Nuuk'), ('America/Ojinaga', 'America/Ojinaga'), ('America/Panama', 'America/Panama'), ('America/Paramaribo', 'America/Paramaribo'), ('America/Phoenix', 'America/Phoenix'), ('America/Port-au-Prince', 'America/Port-au-Prince'), ('America/Port_of_Spain', 'America/Port_of_Spain'), ('America/Porto_Velho', 'America/Porto_Velho'), ('America/Puerto_Rico', 'America/Puerto_Rico'), ('America/Punta_Arenas', 'America/Punta_Arenas'), ('America/Rankin_Inlet', 'America/Rankin_Inlet'), ('America/Recife', 'America/Recife'), ('America/Regina', 'America/Regina'), ('America/Resolute', 'America/Resolute'), ('America/Rio_Branco', 'America/Rio_Branco'), ('America/Santarem', 'America/Santarem'), ('America/Santiago', 'America/Santiago'), ('America/Santo_Domingo', 'America/Santo_Domingo'), ('America/Sao_Paulo', 'America/Sao_Paulo'), ('America/Scoresbysund', 'America/Scoresbysund'), ('America/Sitka', 'America/Sitka'), ('America/St_Barthelemy', 'America/St_Barthelemy'), ('America/St_Johns', 'America/St_Johns'), ('America/St_Kitts', 'America/St_Kitts'), ('America/St_Lucia', 'America/St_Lucia'), ('America/St_Thomas', 'America/St_Thomas'), ('America/St_Vincent', 'America/St_Vincent'), ('America/Swift_Current', 'America/Swift_Current'), ('America/Tegucigalpa', 'America/Tegucigalpa'), ('America/Thule', 'America/Thule'), ('America/Tijuana', 'America/Tijuana'), ('America/Toronto', 'America/Toronto'), ('America/Tortola', 'America/Tortola'), ('America/Vancouver', 'America/Vancouver'), ('America/Whitehorse', 'America/Whitehorse'), ('America/Winnipeg', 'America/Winnipeg'), ('America/Yakutat', 'America/Yakutat'), ('Antarctica/Casey', 'Antarctica/Casey'), ('Antarctica/Davis', 'Antarctica/Davis'), ('Antarctica/DumontDUrville', 'Antarctica/DumontDUrville'), ('Antarctica/Macquarie', 'Antarctica/Macquarie'), ('Antarctica/Mawson', 'Antarctica/Mawson'), ('Antarctica/McMurdo', 'Antarctica/McMurdo'), ('Antarctica/Palmer', 'Antarctica/Palmer'), ('Antarctica/Rothera', 'Antarctica/Rothera'), ('Antarctica/Syowa', 'Antarctica/Syowa'), ('Antarctica/Troll', 'Antarctica/Troll'), ('Antarctica/Vostok', 'Antarctica/Vostok'), ('Arctic/Longyearbyen', 'Arctic/Longyearbyen'), ('Asia/Aden', 'Asia/Aden'), ('Asia/Almaty', 'Asia/Almaty'), ('Asia/Amman', 'Asia/Amman'), ('Asia/Anadyr', 'Asia/Anadyr'), ('Asia/Aqtau', 'Asia/Aqtau'), ('Asia/Aqtobe', 'Asia/Aqtobe'), ('Asia/Ashgabat', 'Asia/Ashgabat'), ('Asia/Atyrau', 'Asia/Atyrau'), ('Asia/Baghdad', 'Asia/Baghdad'), ('Asia/Bahrain', 'Asia/Bahrain'), ('Asia/Baku', 'Asia/Baku'), ('Asia/Bangkok', 'Asia/Bangkok'), ('Asia/Barnaul', 'Asia/Barnaul'), ('Asia/Beirut', 'Asia/Beirut'), ('Asia/Bishkek', 'Asia/Bishkek'), ('Asia/Brunei', 'Asia/Brunei'), ('Asia/Chita', 'Asia/Chita'), ('Asia/Colombo', 'Asia/Colombo'), ('Asia/Damascus', 'Asia/Damascus'), ('Asia/Dhaka', 'Asia/Dhaka'), ('Asia/Dili', 'Asia/Dili'), ('Asia/Dubai', 'Asia/Dubai'), ('Asia/Dushanbe', 'Asia/Dushanbe'), ('Asia/Famagusta', 'Asia/Famagusta'), ('Asia/Gaza', 'Asia/Gaza'), ('Asia/Hebron', 'Asia/Hebron'), ('Asia/Ho_Chi_Minh', 'Asia/Ho_Chi_Minh'), ('Asia/Hong_Kong', 'Asia/Hong_Kong'), ('Asia/Hovd', 'Asia/Hovd'), ('Asia/Irkutsk', 'Asia/Irkutsk'), ('Asia/Jakarta', 'Asia/Jakarta'), ('Asia/Jayapura', 'Asia/Jayapura'), ('Asia/Jerusalem', 'Asia/Jerusalem'), ('Asia/Kabul', 'Asia/Kabul'), ('Asia/Kamchatka', 'Asia/Kamchatka'), ('Asia/Karachi', 'Asia/Karachi'), ('Asia/Kathmandu', 'Asia/Kathmandu'), ('Asia/Khandyga', 'Asia/Khandyga'), ('Asia/Kolkata', 'Asia/Kolkata'), ('Asia/Krasnoyarsk', 'Asia/Krasnoyarsk'), ('Asia/Kuala_Lumpur', 'Asia/Kuala_Lumpur'), ('Asia/Kuching', 'Asia/Kuching'), ('Asia/Kuwait', 'Asia/Kuwait'), ('Asia/Macau', 'Asia/Macau'), ('Asia/Magadan', 'Asia/Magadan'), ('Asia/Makassar', 'Asia/Makassar'), ('Asia/Manila', 'Asia/Manila'), ('Asia/Muscat', 'Asia/Muscat'), ('Asia/Nicosia', 'Asia/Nicosia'), ('Asia/Novokuznetsk', 'Asia/Novokuznetsk'), ('Asia/Novosibirsk', 'Asia/Novosibirsk'), ('Asia/Omsk', 'Asia/Omsk'), ('Asia/Oral', 'Asia/Oral'), ('Asia/Phnom_Penh', 'Asia/Phnom_Penh'), ('Asia/Pontianak', 'Asia/Pontianak'), ('Asia/Pyongyang', 'Asia/Pyongyang'), ('Asia/Qatar', 'Asia/Qatar'), ('Asia/Qostanay', 'Asia/Qostanay'), ('Asia/Qyzylorda', 'Asia/Qyzylorda'), ('Asia/Riyadh', 'Asia/Riyadh'), ('Asia/Sakhalin', 'Asia/Sakhalin'), ('Asia/Samarkand', 'Asia/Samarkand'), ('Asia/Seoul', 'Asia/Seoul'), ('Asia/Shanghai', 'Asia/Shanghai'), ('Asia/Singapore', 'Asia/Singapore'), ('Asia/Srednekolymsk', 'Asia/Srednekolymsk'), ('Asia/Taipei', 'Asia/Taipei'), ('Asia/Tashkent', 'Asia/Tashkent'), ('Asia/Tbilisi', 'Asia/Tbilisi'), ('Asia/Tehran', 'Asia/Tehran'), ('Asia/Thimphu', 'Asia/Thimphu'), ('Asia/Tokyo', 'Asia/Tokyo'), ('Asia/Tomsk', 'Asia/Tomsk'), ('Asia/Ulaanbaatar', 'Asia/Ulaanbaatar'), ('Asia/Urumqi', 'Asia/Urumqi'), ('Asia/Ust-Nera', 'Asia/Ust-Nera'), ('Asia/Vientiane', 'Asia/Vientiane'), ('Asia/Vladivostok', 'Asia/Vladivostok'), ('Asia/Yakutsk', 'Asia/Yakutsk'), ('Asia/Yangon', 'Asia/Yangon'), ('Asia/Yekaterinburg', 'Asia/Yekaterinburg'), ('Asia/Yerevan', 'Asia/Yerevan'), ('Atlantic/Azores', 'Atlantic/Azores'), ('Atlantic/Bermuda', 'Atlantic/Bermuda'), ('Atlantic/Canary', 'Atlantic/Canary'), ('Atlantic/Cape_Verde', 'Atlantic/Cape_Verde'), ('Atlantic/Faroe', 'Atlantic/Faroe'), ('Atlantic/Madeira', 'Atlantic/Madeira'), ('Atlantic/Reykjavik', 'Atlantic/Reykjavik'), ('Atlantic/South_Georgia', 'Atlantic/South_Georgia'), ('Atlantic/St_Helena', 'Atlantic/St_Helena'), ('Atlantic/Stanley', 'Atlantic/Stanley'), ('Australia/Adelaide', 'Australia/Adelaide'), ('Australia/Brisbane', 'Australia/Brisbane'), ('Australia/Broken_Hill', 'Australia/Broken_Hill'), ('Australia/Darwin', 'Australia/Darwin'), ('Australia/Eucla', 'Australia/Eucla'), ('Australia/Hobart', 'Australia/Hobart'), ('Australia/Lindeman', 'Australia/Lindeman'), ('Australia/Lord_Howe', 'Australia/Lord_Howe'), ('Australia/Melbourne', 'Australia/Melbourne'), ('Australia/Perth', 'Australia/Perth'), ('Australia/Sydney', 'Australia/Sydney'), ('Canada/Atlantic', 'Canada/Atlantic'), ('Canada/Central', 'Canada/Central'), ('Canada/Eastern', 'Canada/Eastern'), ('Canada/Mountain', 'Canada/Mountain'), ('Canada/Newfoundland', 'Canada/Newfoundland'), ('Canada/Pacific', 'Canada/Pacific'), ('Europe/Amsterdam', 'Europe/Amsterdam'), ('Europe/Andorra', 'Europe/Andorra'), ('Europe/Astrakhan', 'Europe/Astrakhan'), ('Europe/Athens', 'Europe/Athens'), ('Europe/Belgrade', 'Europe/Belgrade'), ('Europe/Berlin', 'Europe/Berlin'), ('Europe/Bratislava', 'Europe/Bratislava'), ('Europe/Brussels', 'Europe/Brussels'), ('Europe/Bucharest', 'Europe/Bucharest'), ('Europe/Budapest', 'Europe/Budapest'), ('Europe/Busingen', 'Europe/Busingen'), ('Europe/Chisinau', 'Europe/Chisinau'), ('Europe/Copenhagen', 'Europe/Copenhagen'), ('Europe/Dublin', 'Europe/Dublin'), ('Europe/Gibraltar', 'Europe/Gibraltar'), ('Europe/Guernsey', 'Europe/Guernsey'), ('Europe/Helsinki', 'Europe/Helsinki'), ('Europe/Isle_of_Man', 'Europe/Isle_of_Man'), ('Europe/Istanbul', 'Europe/Istanbul'), ('Europe/Jersey', 'Europe/Jersey'), ('Europe/Kaliningrad', 'Europe/Kaliningrad'), ('Europe/Kirov', 'Europe/Kirov'), ('Europe/Kyiv', 'Europe/Kyiv'), ('Europe/Lisbon', 'Europe/Lisbon'), ('Europe/Ljubljana', 'Europe/Ljubljana'), ('Europe/London', 'Europe/London'), ('Europe/Luxembourg', 'Europe/Luxembourg'), ('Europe/Madrid', 'Europe/Madrid'), ('Europe/Malta', 'Europe/Malta'), ('Europe/Mariehamn', 'Europe/Mariehamn'), ('Europe/Minsk', 'Europe/Minsk'), ('Europe/Monaco', 'Europe/Monaco'), ('Europe/Moscow', 'Europe/Moscow'), ('Europe/Oslo', 'Europe/Oslo'), ('Europe/Paris', 'Europe/Paris'), ('Europe/Podgorica', 'Europe/Podgorica'), ('Europe/Prague', 'Europe/Prague'), ('Europe/Riga', 'Europe/Riga'), ('Europe/Rome', 'Europe/Rome'), ('Europe/Samara', 'Europe/Samara'), ('Europe/San_Marino', 'Europe/San_Marino'), ('Europe/Sarajevo', 'Europe/Sarajevo'), ('Europe/Saratov', 'Europe/Saratov'), ('Europe/Simferopol', 'Europe/Simferopol'), ('Europe/Skopje', 'Europe/Skopje'), ('Europe/Sofia', 'Europe/Sofia'), ('Europe/Stockholm', 'Europe/Stockholm'), ('Europe/Tallinn', 'Europe/Tallinn'), ('Europe/Tirane', 'Europe/Tirane'), ('Europe/Ulyanovsk', 'Europe/Ulyanovsk'), ('Europe/Vaduz', 'Europe/Vaduz'), ('Europe/Vatican', 'Europe/Vatican'), ('Europe/Vienna', 'Europe/Vienna'), ('Europe/Vilnius', 'Europe/Vilnius'), ('Europe/Volgograd', 'Europe/Volgograd'), ('Europe/Warsaw', 'Europe/Warsaw'), ('Europe/Zagreb', 'Europe/Zagreb'), ('Europe/Zurich', 'Europe/Zurich'), ('GMT', 'GMT'), ('Indian/Antananarivo', 'Indian/Antananarivo'), ('Indian/Chagos', 'Indian/Chagos'), ('Indian/Christmas', 'Indian/Christmas'), ('Indian/Cocos', 'Indian/Cocos'), ('Indian/Comoro', 'Indian/Comoro'), ('Indian/Kerguelen', 'Indian/Kerguelen'), ('Indian/Mahe', 'Indian/Mahe'), ('Indian/Maldives', 'Indian/Maldives'), ('Indian/Mauritius', 'Indian/Mauritius'), ('Indian/Mayotte', 'Indian/Mayotte'), ('Indian/Reunion', 'Indian/Reunion'), ('Pacific/Apia', 'Pacific/Apia'), ('Pacific/Auckland', 'Pacific/Auckland'), ('Pacific/Bougainville', 'Pacific/Bougainville'), ('Pacific/Chatham', 'Pacific/Chatham'), ('Pacific/Chuuk', 'Pacific/Chuuk'), ('Pacific/Easter', 'Pacific/Easter'), ('Pacific/Efate', 'Pacific/Efate'), ('Pacific/Fakaofo', 'Pacific/Fakaofo'), ('Pacific/Fiji', 'Pacific/Fiji'), ('Pacific/Funafuti', 'Pacific/Funafuti'), ('Pacific/Galapagos', 'Pacific/Galapagos'), ('Pacific/Gambier', 'Pacific/Gambier'), ('Pacific/Guadalcanal', 'Pacific/Guadalcanal'), ('Pacific/Guam', 'Pacific/Guam'), ('Pacific/Honolulu', 'Pacific/Honolulu'), ('Pacific/Kanton', 'Pacific/Kanton'), ('Pacific/Kiritimati', 'Pacific/Kiritimati'), ('Pacific/Kosrae', 'Pacific/Kosrae'), ('Pacific/Kwajalein', 'Pacific/Kwajalein'), ('Pacific/Majuro', 'Pacific/Majuro'), ('Pacific/Marquesas', 'Pacific/Marquesas'), ('Pacific/Midway', 'Pacific/Midway'), ('Pacific/Nauru', 'Pacific/Nauru'), ('Pacific/Niue', 'Pacific/Niue'), ('Pacific/Norfolk', 'Pacific/Norfolk'), ('Pacific/Noumea', 'Pacific/Noumea'), ('Pacific/Pago_Pago', 'Pacific/Pago_Pago'), ('Pacific/Palau', 'Pacific/Palau'), ('Pacific/Pitcairn', 'Pacific/Pitcairn'), ('Pacific/Pohnpei', 'Pacific/Pohnpei'), ('Pacific/Port_Moresby', 'Pacific/Port_Moresby'), ('Pacific/Rarotonga', 'Pacific/Rarotonga'), ('Pacific/Saipan', 'Pacific/Saipan'), ('Pacific/Tahiti', 'Pacific/Tahiti'), ('Pacific/Tarawa', 'Pacific/Tarawa'), ('Pacific/Tongatapu', 'Pacific/Tongatapu'), ('Pacific/Wake', 'Pacific/Wake'), ('Pacific/Wallis', 'Pacific/Wallis'), ('US/Alaska', 'US/Alaska'), ('US/Arizona', 'US/Arizona'), ('US/Central', 'US/Central'), ('US/Eastern', 'US/Eastern'), ('US/Hawaii', 'US/Hawaii'), ('US/Mountain', 'US/Mountain'), ('US/Pacific', 'US/Pacific'), ('UTC', 'UTC')], max_length=100, verbose_name='Time zone'),
+ ),
+ ]
diff --git a/euth/users/models.py b/euth/users/models.py
index f60d8edc8..d39f02495 100644
--- a/euth/users/models.py
+++ b/euth/users/models.py
@@ -146,10 +146,6 @@ class Meta:
verbose_name = _("User")
verbose_name_plural = _("Users")
- def get_absolute_url(self):
- from django.urls import reverse
- return reverse('profile', kwargs={'slug': str(self.username)})
-
def __str__(self):
return self.get_full_name()
diff --git a/euth/users/serializers.py b/euth/users/serializers.py
deleted file mode 100644
index f3e90e91a..000000000
--- a/euth/users/serializers.py
+++ /dev/null
@@ -1,26 +0,0 @@
-from easy_thumbnails.files import get_thumbnailer
-from rest_framework import serializers
-
-from .models import User
-
-
-class UserSerializer(serializers.ModelSerializer):
- avatar = serializers.SerializerMethodField()
-
- class Meta:
- model = User
- fields = ('id', 'username', 'avatar', 'avatar_fallback')
- read_only_fields = ('id', 'username', 'avatar', 'avatar_fallback')
-
- def get_avatar(self, obj):
- if obj.avatar:
- image = get_thumbnailer(obj.avatar)['avatar_small']
- return image.url
-
-
-# mails should not be exposed in API, so there is a separate one for this
-class UserWithMailSerializer(UserSerializer):
- class Meta(UserSerializer.Meta):
- fields = ('id', 'username', 'avatar', 'avatar_fallback', 'email')
- read_only_fields = ('id', 'username', 'avatar', 'avatar_fallback',
- 'email')
diff --git a/euth/users/signals.py b/euth/users/signals.py
deleted file mode 100644
index a002d703c..000000000
--- a/euth/users/signals.py
+++ /dev/null
@@ -1,24 +0,0 @@
-from django.db.models import signals
-from django.dispatch import receiver
-
-from adhocracy4.images import services
-
-from . import models
-
-
-@receiver(signals.post_init, sender=models.User)
-def backup_image_path(sender, instance, **kwargs):
- if instance.avatar:
- instance._current_image_file = instance.avatar
-
-
-@receiver(signals.post_save, sender=models.User)
-def delete_old_image(sender, instance, **kwargs):
- if hasattr(instance, '_current_image_file'):
- if instance._current_image_file != instance.avatar:
- services.delete_images([instance._current_image_file])
-
-
-@receiver(signals.post_delete, sender=models.User)
-def delete_images_for_User(sender, instance, **kwargs):
- services.delete_images([instance._avatar])
diff --git a/euth/users/urls.py b/euth/users/urls.py
deleted file mode 100644
index db6a99e3f..000000000
--- a/euth/users/urls.py
+++ /dev/null
@@ -1,8 +0,0 @@
-from django.urls import re_path
-
-from . import views
-
-urlpatterns = [
- re_path(r'^(?P[-\w _.@+-]+)/$', views.ProfileView.as_view(),
- name='profile'),
-]
diff --git a/euth/users/views.py b/euth/users/views.py
deleted file mode 100644
index 892be4d33..000000000
--- a/euth/users/views.py
+++ /dev/null
@@ -1,23 +0,0 @@
-from django.db import models as django_models
-from django.views.generic.detail import DetailView
-
-from adhocracy4.projects.models import Project
-
-from . import models
-
-
-class ProfileView(DetailView):
- model = models.User
- slug_field = 'username'
-
- @property
- def get_participated_projects(self):
- user = self.object
-
- qs = Project.objects.filter(
- django_models.Q(follow__creator=user),
- django_models.Q(follow__enabled=True) |
- django_models.Q(participants=user)
- ).distinct()
-
- return qs
diff --git a/euth/users/widgets.py b/euth/users/widgets.py
deleted file mode 100644
index 6a7e3029a..000000000
--- a/euth/users/widgets.py
+++ /dev/null
@@ -1,31 +0,0 @@
-from django.forms import TextInput
-from django.template.loader import get_template
-
-
-class UserSearchInput(TextInput):
- class Media:
- js = (
- 'user_search.js',
- )
-
- def __init__(self, identifier=None, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.identifier = identifier
-
- def render(self, name, value, attrs=None, renderer=None):
- if attrs and 'class' in attrs:
- attrs['class'] += ' typeahead'
- else:
- attrs['class'] = 'typeahead'
-
- if self.identifier:
- attrs['data-identifier'] = self.identifier
-
- input_field = super().render(name, value, attrs)
-
- template = get_template('euth_users/user_search.html')
- context = {
- 'input': input_field,
- }
-
- return template.render(context)
diff --git a/euth_wagtail/settings/base.py b/euth_wagtail/settings/base.py
index 5d71a022d..7335a3be5 100644
--- a/euth_wagtail/settings/base.py
+++ b/euth_wagtail/settings/base.py
@@ -89,17 +89,14 @@
'euth.users',
'euth.actions',
'euth.organisations',
- 'euth.projects',
'euth.ideas',
'euth.dashboard',
'euth.accounts',
'euth.memberships',
'euth.documents',
- 'euth.exports',
'euth.offlinephases',
'euth.maps',
'euth.follows',
- 'euth.blueprints',
'euth.captcha',
'euth.contrib',
'euth.communitydebate'
diff --git a/euth_wagtail/settings/test.py b/euth_wagtail/settings/test.py
index de7a2e2e0..59508aa70 100644
--- a/euth_wagtail/settings/test.py
+++ b/euth_wagtail/settings/test.py
@@ -1,11 +1,5 @@
from .dev import *
-INSTALLED_APPS += [
- 'tests.apps.blog.apps.BlogConfig',
- 'tests.apps.fakeprojects.apps.FakeProjectsConfig'
-
-]
-
A4_ORGANISATION_FACTORY = 'tests.organisations.factories.OrganisationFactory'
A4_USER_FACTORY = 'tests.factories.UserFactory'
diff --git a/euth_wagtail/templates/a4dashboard/base_dashboard.html b/euth_wagtail/templates/a4dashboard/base_dashboard.html
deleted file mode 100644
index b543382c0..000000000
--- a/euth_wagtail/templates/a4dashboard/base_dashboard.html
+++ /dev/null
@@ -1,38 +0,0 @@
-{% extends "base.html" %}
-{% load i18n thumbnail %}
-
-{% block title %}"{{ view.organisation.name }}" {% trans "Dashboard" %} — {{ block.super }}{% endblock %}
-
-{% block content %}
-
-
-
- {% block dashboard_content %}{% endblock %}
-
-
-{% endblock %}
diff --git a/euth_wagtail/templates/a4dashboard/base_project_list.html b/euth_wagtail/templates/a4dashboard/base_project_list.html
deleted file mode 100644
index e678c6343..000000000
--- a/euth_wagtail/templates/a4dashboard/base_project_list.html
+++ /dev/null
@@ -1,18 +0,0 @@
-{% extends "a4dashboard/base_dashboard.html" %}
-{% load i18n %}
-
-{% block dashboard_content %}
-
-
-
-
- {% block project_list %}{% endblock %}
-
-
-
-{% endblock %}
diff --git a/euth_wagtail/templates/a4projects/project_list.html b/euth_wagtail/templates/a4projects/project_list.html
deleted file mode 100644
index 1c621a5b8..000000000
--- a/euth_wagtail/templates/a4projects/project_list.html
+++ /dev/null
@@ -1,60 +0,0 @@
-{% extends "base.html" %}
-{% load i18n static contrib_tags %}
-
-{% block title %}{% trans 'Projects on Opin.me' %}{% endblock %}
-
-{% block content %}
-
-
- {% trans 'Take part in a project' %}
-
-
-
-
-
-
- {% for field in view.filter.form %}
- {% if field.name != 'ordering' %}
-
- {{ field }}
-
- {% else %}
-
- {{ field }}
-
- {% endif %}
-
- {{ field }}
-
- {% endfor %}
-
-
- {% if project_list.count == 0 %}
-
- {% trans 'No projects found' %}
-
- {% endif %}
-
- {% if project_list.count > 0 %}
-
- {% for project in project_list %}
-
- {% include "euth_projects/includes/project_tile.html" with project=project %}
-
- {% endfor %}
-
- {% endif %}
-
- {% if is_paginated %}
- {% include "euth_contrib/includes/pagination.html" %}
- {% endif %}
-
-
-{% endblock %}
diff --git a/euth_wagtail/urls.py b/euth_wagtail/urls.py
index 963b625a5..5abaed284 100644
--- a/euth_wagtail/urls.py
+++ b/euth_wagtail/urls.py
@@ -23,40 +23,22 @@
from adhocracy4.ratings.api import RatingViewSet
from adhocracy4.reports.api import ReportViewSet
from euth.accounts import urls as accounts_urls
-from euth.blueprints import urls as blueprints_urls
-from euth.communitydebate import urls as communitydebate_urls
from euth.contrib.sitemaps.adhocracy4_sitemap import Adhocracy4Sitemap
from euth.contrib.sitemaps.static_sitemap import StaticSitemap
from euth.dashboard import urls as dashboard_urls
-from euth.documents import urls as paragraph_urls
-from euth.documents.api import DocumentViewSet
-from euth.follows.api import FollowViewSet
-from euth.ideas import urls as ideas_urls
-from euth.maps import urls as maps_urls
-from euth.memberships import projects_urls as memberships_project_urls
-from euth.memberships import urls as memberships_urls
-from euth.offlinephases import urls as offlinephases_urls
from euth.organisations import urls as organisations_urls
-from euth.projects import urls as project_urls
-from euth.projects.api import ProjectViewSet
-from euth.users import urls as user_urls
-from euth.users.api import UserViewSet
from . import urls_accounts
router = routers.DefaultRouter()
-router.register(r'follows', FollowViewSet, basename='follows')
router.register(r'polls', PollViewSet, basename='polls')
router.register(r'reports', ReportViewSet, basename='reports')
-router.register(r'projects', ProjectViewSet, basename='projects')
-router.register(r'users', UserViewSet, basename='users')
ct_router = a4routers.ContentTypeDefaultRouter()
ct_router.register(r'comments', CommentViewSet, basename='comments')
ct_router.register(r'ratings', RatingViewSet, basename='ratings')
module_router = a4routers.ModuleDefaultRouter()
-module_router.register(r'documents', DocumentViewSet, basename='documents')
sitemaps = {
'adhocracy4': Adhocracy4Sitemap,
@@ -82,22 +64,12 @@
urlpatterns += i18n_patterns(
path('accounts/', include(accounts_urls)),
path('dashboard/', include(dashboard_urls)),
- path('profile/', include(user_urls)),
path('orgs/', include(organisations_urls)),
- path('projects/', include(project_urls)),
- path('projects/', include(memberships_project_urls)),
- path('paragraphs/', include(paragraph_urls)),
- path('offlineevents/', include(offlinephases_urls)),
- path('ideas/', include(ideas_urls)),
- path('maps/', include(maps_urls)),
- path('memberships/', include(memberships_urls)),
- path('blueprints/', include(blueprints_urls)),
path('jsi18n/', JavaScriptCatalog.as_view(), name='javascript-catalog'),
re_path(r'^sitemap\.xml$', wagtail_sitemap_views.index,
{'sitemaps': sitemaps, 'sitemap_url_name': 'sitemaps'}),
re_path(r'^sitemap-(?P.+)\.xml$', wagtail_sitemap_views.sitemap,
{'sitemaps': sitemaps}, name='sitemaps'),
- path('communitydebate/', include(communitydebate_urls)),
path('', include(wagtail_urls)),
)
diff --git a/home/migrations/0043_remove_helppages_communitydebate.py b/home/migrations/0043_remove_helppages_communitydebate.py
new file mode 100644
index 000000000..4b87ea14c
--- /dev/null
+++ b/home/migrations/0043_remove_helppages_communitydebate.py
@@ -0,0 +1,17 @@
+# Generated by Django 3.2.20 on 2024-09-09 14:09
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('home', '0042_add_use_json_field'),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name='helppages',
+ name='communitydebate',
+ ),
+ ]
diff --git a/home/migrations/0044_delete_helppages.py b/home/migrations/0044_delete_helppages.py
new file mode 100644
index 000000000..b9e36faf4
--- /dev/null
+++ b/home/migrations/0044_delete_helppages.py
@@ -0,0 +1,16 @@
+# Generated by Django 3.2.20 on 2024-09-10 09:36
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('home', '0043_remove_helppages_communitydebate'),
+ ]
+
+ operations = [
+ migrations.DeleteModel(
+ name='HelpPages',
+ ),
+ ]
diff --git a/home/models/__init__.py b/home/models/__init__.py
index 1979df745..f3b6da506 100644
--- a/home/models/__init__.py
+++ b/home/models/__init__.py
@@ -1,5 +1,4 @@
from . import base_pages # noqa:F401
from . import blocks # noqa:F401
from . import manual_pages # noqa:F401
-from . import settings # noqa:F401
from . import snippets # noqa:F401
diff --git a/home/models/settings.py b/home/models/settings.py
deleted file mode 100644
index 2b88b2188..000000000
--- a/home/models/settings.py
+++ /dev/null
@@ -1,46 +0,0 @@
-from django.db import models
-from wagtail.admin import edit_handlers
-from wagtail.contrib.settings.models import BaseSiteSetting
-from wagtail.contrib.settings.models import register_setting
-
-from euth.blueprints.names import BlueprintNames
-
-
-class BlueprintSettingsMeta(models.base.ModelBase):
- """
- Metaclass for adding a project foreign keys for each blueprint.
- """
-
- def __new__(cls, name, bases, namespace):
- panels = namespace['panels']
- blueprint_names = namespace['blueprint_names']
-
- for member in blueprint_names:
- namespace[member.name] = models.ForeignKey(
- 'a4projects.Project',
- on_delete=models.SET_NULL,
- null=True,
- blank=True,
- related_name='example_project_{}'.format(member.name),
- help_text='Please select an exemplary {} project.'.format(
- member.value
- ),
- )
- panels += [edit_handlers.FieldPanel(member.name)]
- return super().__new__(cls, name, bases, namespace)
-
-
-@register_setting
-class HelpPages(BaseSiteSetting, metaclass=BlueprintSettingsMeta):
- guidelines_page = models.ForeignKey(
- 'wagtailcore.Page',
- on_delete=models.SET_NULL,
- null=True,
- help_text="Please add a link to the guideline page."
- )
-
- panels = [
- edit_handlers.PageChooserPanel('guidelines_page'),
- ]
-
- blueprint_names = BlueprintNames
diff --git a/home/templates/home/home_page.html b/home/templates/home/home_page.html
index cdf4fcf5f..aa3ed327c 100644
--- a/home/templates/home/home_page.html
+++ b/home/templates/home/home_page.html
@@ -7,17 +7,6 @@
{% include "home/includes/wagtail_hero_unit.html" with image=page.image title=page.translated_title %}
-
-
diff --git a/package.json b/package.json
index 32b19b686..f56a808f1 100644
--- a/package.json
+++ b/package.json
@@ -4,7 +4,7 @@
"dependencies": {
"@fortawesome/fontawesome-free": "5.15.4",
"@maplibre/maplibre-gl-leaflet": "0.0.19",
- "adhocracy4": "github:liqd/adhocracy4#opin-v2302",
+ "adhocracy4": "github:liqd/adhocracy4#jd-2024-9-opin-disable-generic-signals",
"bootstrap": "5.2.3",
"css-loader": "6.7.3",
"datepicker": "git+https://github.com/liqd/datePicker.git",
diff --git a/requirements/base.txt b/requirements/base.txt
index 060d0d298..7d39ef99f 100644
--- a/requirements/base.txt
+++ b/requirements/base.txt
@@ -1,5 +1,5 @@
# A4
-git+https://github.com/liqd/adhocracy4.git@opin-v2302#egg=adhocracy4
+git+https://github.com/liqd/adhocracy4.git@jd-2024-9-opin-disable-generic-signals#egg=adhocracy4
# Additional requirements
bcrypt==4.0.1
diff --git a/tests/accounts/test_views.py b/tests/accounts/test_views.py
deleted file mode 100644
index c0b63020c..000000000
--- a/tests/accounts/test_views.py
+++ /dev/null
@@ -1,70 +0,0 @@
-import datetime
-
-import pytest
-from django.contrib import auth
-from django.urls import reverse
-
-from tests.helpers import redirect_target
-
-User = auth.get_user_model()
-
-
-@pytest.mark.django_db
-def test_anonymous_cannot_view_account_profile(client):
- url = reverse('account-profile')
- response = client.get(url)
- assert response.status_code == 302
-
-
-@pytest.mark.django_db
-def test_authenticated_user_can_view_profile(client, user, login_url):
- url = reverse('account-profile')
- client.post(login_url, {'email': user.email, 'password': 'password'})
- response = client.get(url)
- assert redirect_target(response) == 'account_login'
-
-
-@pytest.mark.django_db
-def test_authenticated_user_can_update_profile(client, user, login_url,
- smallImage):
- url = reverse('account-profile')
- client.login(username=user.email, password='password')
- response = client.get(url)
- assert response.status_code == 200
-
- birthday = datetime.date(2020, 1, 1)
- data = {'username': user.username,
- '_avatar': smallImage,
- 'description': 'This is me.',
- 'birthdate': birthday,
- 'country': '',
- 'city': '',
- 'timezone': '',
- 'gender': '',
- 'languages': '',
- 'twitter_handle': '',
- 'facebook_handle': '',
- 'instagram_handle': '',
- 'get_notifications': ''
- }
- response = client.post(url, data)
- assert response.status_code == 302
- redirect = redirect_target(response)
- assert redirect == 'account-profile'
-
- response_get = client.get(reverse(redirect))
- assert response_get.status_code == 200
- assert response_get.context['user'].description == 'This is me.'
- assert response_get.context['user'].birthdate == birthday
-
- invalid_birthday = datetime.date(3000, 1, 1)
- data['birthday'] = invalid_birthday
-
- response = client.post(url, data)
- assert response.status_code == 302
- redirect = redirect_target(response)
- assert redirect == 'account-profile'
-
- response_get = client.get(reverse(redirect))
- assert response_get.status_code == 200
- assert response_get.context['user'].birthdate == birthday
diff --git a/tests/actions/__init__.py b/tests/actions/__init__.py
deleted file mode 100644
index e69de29bb..000000000
diff --git a/tests/actions/conftest.py b/tests/actions/conftest.py
deleted file mode 100644
index 036cf943a..000000000
--- a/tests/actions/conftest.py
+++ /dev/null
@@ -1,16 +0,0 @@
-from pytest_factoryboy import register
-
-from adhocracy4.test import factories
-from tests.actions import factories as actions_factories
-from tests.documents import factories as document_factories
-from tests.follows import factories as follow_factories
-from tests.ideas import factories as idea_fatories
-from tests.ratings import factories as rating_factories
-
-register(rating_factories.RatingFactory)
-register(actions_factories.CommentFactory)
-register(idea_fatories.IdeaFactory)
-register(document_factories.DocumentFactory)
-register(document_factories.ParagraphFactory)
-register(factories.PhaseFactory)
-register(follow_factories.FollowFactory)
diff --git a/tests/actions/factories.py b/tests/actions/factories.py
deleted file mode 100644
index 436a089d7..000000000
--- a/tests/actions/factories.py
+++ /dev/null
@@ -1,14 +0,0 @@
-import factory
-
-from tests.apps.fakeprojects import factories
-from tests.factories import UserFactory
-
-
-class CommentFactory(factory.django.DjangoModelFactory):
-
- class Meta:
- model = 'a4comments.Comment'
-
- comment = factory.Faker('text')
- content_object = factory.SubFactory(factories.FakeProjectContentFactory)
- creator = factory.SubFactory(UserFactory)
diff --git a/tests/actions/test_actions.py b/tests/actions/test_actions.py
deleted file mode 100644
index cbf4c1e22..000000000
--- a/tests/actions/test_actions.py
+++ /dev/null
@@ -1,239 +0,0 @@
-from datetime import timedelta
-
-import pytest
-from dateutil.parser import parse
-from django.core import mail
-from django.core.management import call_command
-from freezegun import freeze_time
-
-from euth.actions.models import Action
-
-
-@pytest.mark.django_db
-def test_no_notification_if_flag_is_not_set(
- user_factory, idea_factory, comment_factory):
- user = user_factory(get_notifications=False)
- user2 = user_factory()
- idea = idea_factory(creator=user)
-
- action_count = Action.objects.all().count()
- assert action_count == 1
-
- comment_factory(content_object=idea, creator=user2)
-
- action_count = Action.objects.all().count()
- assert action_count == 2
- assert len(mail.outbox) == 1
-
-
-@pytest.mark.django_db
-def test_idea_creator_gets_email_after_comment(
- user_factory, idea_factory, comment_factory):
- user = user_factory()
- user2 = user_factory()
- idea = idea_factory(creator=user)
-
- comment_factory(content_object=idea, creator=user2)
-
- action_count = Action.objects.all().count()
- assert action_count == 2
- assert len(mail.outbox) == 2
- assert 'A Comment was added to your idea' in mail.outbox[1].subject
-
-
-@pytest.mark.django_db
-def test_document_creator_gets_email_after_comment(
- user_factory, document_factory, comment_factory):
- user = user_factory()
- user2 = user_factory()
- document = document_factory(creator=user)
-
- comment_factory(content_object=document, creator=user2)
-
- action_count = Action.objects.all().count()
- assert action_count == 1
- assert len(mail.outbox) == 1
- assert 'A Comment was added to your document' in mail.outbox[0].subject
-
-
-@pytest.mark.django_db
-def test_document_creator_gets_email_after_comment_on_paragraph(
- user_factory, document_factory, paragraph_factory, comment_factory):
- user = user_factory()
- user2 = user_factory()
- document = document_factory(creator=user)
- paragraph = paragraph_factory(document=document)
-
- comment_factory(content_object=paragraph, creator=user2)
-
- action_count = Action.objects.all().count()
- assert action_count == 1
- assert len(mail.outbox) == 1
- assert 'A Comment was added to your paragraph' in mail.outbox[0].subject
-
-
-@pytest.mark.django_db
-def test_comment_creator_gets_email_after_comment(
- user_factory, idea_factory, comment_factory):
- user = user_factory()
- user2 = user_factory()
- idea = idea_factory(creator=user)
-
- comment = comment_factory(content_object=idea, creator=user2)
- comment_factory(content_object=comment, creator=user)
-
- action_count = Action.objects.all().count()
-
- assert action_count == 4
- assert len(mail.outbox) == 3
- assert 'A Comment was added to your idea' in mail.outbox[1].subject
- assert mail.outbox[1].recipients() == [user.email]
- assert 'A Comment was added to your Comment' in mail.outbox[2].subject
- assert mail.outbox[2].recipients() == [user2.email]
-
-
-@pytest.mark.django_db
-def test_comment_creator_gets_no_email_after_comment_on_own_resource(
- user_factory, idea_factory, comment_factory):
- user = user_factory()
- idea = idea_factory(creator=user)
-
- comment_factory(content_object=idea, creator=user)
-
- action_count = Action.objects.all().count()
- assert action_count == 2
- assert len(mail.outbox) == 1
-
-
-@pytest.mark.django_db
-def test_moderator_self_notification(project, module, idea_factory):
- project = module.project
- idea = idea_factory(module=module, creator=project.moderators.first())
- action = Action.objects.get(project=project)
- assert action.target == project
- assert action.action_object == idea
- assert len(mail.outbox) == 0
-
-
-@pytest.mark.django_db
-def test_moderator_notification(project, user, module, idea_factory):
- project = module.project
- idea = idea_factory(module=module, creator=user)
- action = Action.objects.filter(project=project).last()
- assert action.target == project
- assert action.action_object == idea
- assert len(mail.outbox) == 1
- assert mail.outbox[0].to == [project.moderators.first().email]
-
-
-@pytest.mark.django_db
-def test_24_hour_script(
- phase_factory, user_factory, follow_factory):
- user = user_factory()
- user2 = user_factory()
- user3 = user_factory()
- user4 = user_factory(get_notifications=False)
-
- phase = phase_factory(
- start_date=parse('2013-01-01 17:00:00 UTC'),
- end_date=parse('2013-01-01 18:00:00 UTC')
- )
-
- project = phase.module.project
-
- follow_factory(project=project, creator=user)
- follow_factory(project=project, creator=user2)
- follow_factory(project=project, creator=user3, enabled=False)
- follow_factory(project=project, creator=user4)
-
- action_count = Action.objects.all().count()
- assert action_count == 0
-
- with freeze_time('2013-01-01 17:30:00 UTC'):
- call_command('notify_followers')
- action_count = Action.objects.all().count()
- assert action_count == 1
- assert len(mail.outbox) == 2
- assert mail.outbox[0].recipients() == [user.email]
- assert mail.outbox[1].recipients() == [user2.email]
-
- call_command('notify_followers')
- action_count = Action.objects.all().count()
- assert action_count == 1
- assert len(mail.outbox) == 2
-
-
-@pytest.mark.django_db
-def test_24_hour_script_adds_action_for_next_phase(
- phase_factory, user_factory, follow_factory):
- user = user_factory()
-
- phase1 = phase_factory(
- start_date=parse('2013-01-01 17:00:00 UTC'),
- end_date=parse('2013-01-01 18:00:00 UTC')
- )
-
- phase2 = phase_factory(
- module=phase1.module,
- start_date=parse('2013-02-02 17:00:00 UTC'),
- end_date=parse('2013-02-02 18:00:00 UTC')
- )
-
- phase3 = phase_factory(
- module=phase1.module,
- start_date=parse('2013-02-02 18:01:00 UTC'),
- end_date=parse('2013-02-02 19:00:00 UTC')
- )
-
- project = phase1.module.project
-
- follow_factory(project=project, creator=user)
-
- action_count = Action.objects.all().count()
- assert action_count == 0
-
- # first phase ends within 24 h
- with freeze_time(phase1.end_date - timedelta(hours=1)):
- call_command('notify_followers')
- action_count = Action.objects.all().count()
- assert action_count == 1
- assert len(mail.outbox) == 1
- assert mail.outbox[0].recipients() == [user.email]
-
- # second phase ends within 24 h
- with freeze_time(phase2.end_date - timedelta(hours=1)):
- call_command('notify_followers')
- action_count = Action.objects.all().count()
- assert action_count == 2
- assert len(mail.outbox) == 2
- assert mail.outbox[0].recipients() == [user.email]
-
- # second phase ends within 24 h but script has already run
- with freeze_time(phase2.end_date - timedelta(hours=1)):
- call_command('notify_followers')
- action_count = Action.objects.all().count()
- assert action_count == 2
- assert len(mail.outbox) == 2
- assert mail.outbox[0].recipients() == [user.email]
-
- # third phase ends within 24 h but script has already run
- with freeze_time(phase3.start_date):
- call_command('notify_followers')
- action_count = Action.objects.all().count()
- assert action_count == 3
- assert len(mail.outbox) == 3
- assert mail.outbox[0].recipients() == [user.email]
-
-
-@pytest.mark.django_db
-def test_notify_followers_after_idea_was_added(
- user_factory, idea_factory, follow_factory, module):
- user1 = user_factory()
- user2 = user_factory()
- project = module.project
- moderators = project.moderators
- follow_factory(project=module.project, creator=user1)
- idea_factory(module=module, creator=user2)
- assert len(mail.outbox) == moderators.count() + 1
- text = 'An idea in a project you follow was added'
- assert text in mail.outbox[-1].subject
diff --git a/tests/apps/__init__.py b/tests/apps/__init__.py
deleted file mode 100644
index d800ce83a..000000000
--- a/tests/apps/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-"""This package contains Django apps to be sued for testing only."""
diff --git a/tests/apps/blog/__init__.py b/tests/apps/blog/__init__.py
deleted file mode 100644
index e69de29bb..000000000
diff --git a/tests/apps/blog/apps.py b/tests/apps/blog/apps.py
deleted file mode 100644
index 67b83b576..000000000
--- a/tests/apps/blog/apps.py
+++ /dev/null
@@ -1,7 +0,0 @@
-from django.apps import AppConfig
-
-
-class BlogConfig(AppConfig):
- """A simple blog app for testing."""
- name = 'tests.apps.blog'
- label = 'blog'
diff --git a/tests/apps/blog/migrations/0001_initial.py b/tests/apps/blog/migrations/0001_initial.py
deleted file mode 100644
index 3d04c3622..000000000
--- a/tests/apps/blog/migrations/0001_initial.py
+++ /dev/null
@@ -1,27 +0,0 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
-from django.db import migrations, models
-import django.utils.timezone
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ]
-
- operations = [
- migrations.CreateModel(
- name='Post',
- fields=[
- ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
- ('created', models.DateTimeField(editable=False, default=django.utils.timezone.now)),
- ('modified', models.DateTimeField(blank=True, editable=False, null=True)),
- ('heading', models.CharField(max_length=200)),
- ('body', models.TextField()),
- ],
- options={
- 'abstract': False,
- },
- ),
- ]
diff --git a/tests/apps/blog/migrations/__init__.py b/tests/apps/blog/migrations/__init__.py
deleted file mode 100644
index e69de29bb..000000000
diff --git a/tests/apps/blog/models.py b/tests/apps/blog/models.py
deleted file mode 100644
index 1f9a4423d..000000000
--- a/tests/apps/blog/models.py
+++ /dev/null
@@ -1,9 +0,0 @@
-from django.db import models
-
-from adhocracy4.models import base
-
-
-class Post(base.TimeStampedModel):
-
- heading = models.CharField(max_length=200)
- body = models.TextField()
diff --git a/tests/apps/blog/phases.py b/tests/apps/blog/phases.py
deleted file mode 100644
index 82f863aa0..000000000
--- a/tests/apps/blog/phases.py
+++ /dev/null
@@ -1,18 +0,0 @@
-from adhocracy4 import phases
-
-from . import apps
-from . import models
-from . import views
-
-
-class BlogPhase(phases.PhaseContent):
- app = apps.BlogConfig.label
- phase = 'phase'
- view = views.PostList
-
- features = {
- 'comment': (models.Post, )
- }
-
-
-phases.content.register(BlogPhase())
diff --git a/tests/apps/blog/views.py b/tests/apps/blog/views.py
deleted file mode 100644
index 13e3e944f..000000000
--- a/tests/apps/blog/views.py
+++ /dev/null
@@ -1,7 +0,0 @@
-from django.views.generic import list
-
-from . import models
-
-
-class PostList(list.ListView):
- model = models.Post
diff --git a/tests/apps/fakeprojects/apps.py b/tests/apps/fakeprojects/apps.py
deleted file mode 100644
index a9617573a..000000000
--- a/tests/apps/fakeprojects/apps.py
+++ /dev/null
@@ -1,7 +0,0 @@
-from django.apps import AppConfig
-
-
-class FakeProjectsConfig(AppConfig):
- """Implement app that fakes the view of projects."""
- name = 'tests.apps.fakeprojects'
- label = 'fakeprojects'
diff --git a/tests/apps/fakeprojects/factories.py b/tests/apps/fakeprojects/factories.py
deleted file mode 100644
index 476718834..000000000
--- a/tests/apps/fakeprojects/factories.py
+++ /dev/null
@@ -1,10 +0,0 @@
-import factory
-
-from tests.factories import UserFactory
-
-
-class FakeProjectContentFactory(factory.django.DjangoModelFactory):
- class Meta:
- model = 'fakeprojects.FakeProjectContent'
-
- creator = factory.SubFactory(UserFactory)
diff --git a/tests/apps/fakeprojects/models.py b/tests/apps/fakeprojects/models.py
deleted file mode 100644
index 4deed3628..000000000
--- a/tests/apps/fakeprojects/models.py
+++ /dev/null
@@ -1,28 +0,0 @@
-from django.conf import settings
-from django.db import models
-
-from adhocracy4.projects.enums import Access
-
-
-class FakeProject():
- """
- Minimal set of fields expected for a project.
- """
- name = 'fake projects name'
- active_phase = None
- access = Access.PRIVATE
-
- def has_member(self, user):
- return True
-
- def has_moderator(self, user):
- return False
-
-
-class FakeProjectContent(models.Model):
- """
- Minimal set of field expected for project content.
- """
- project = FakeProject()
- creator = models.ForeignKey(settings.AUTH_USER_MODEL,
- on_delete=models.CASCADE)
diff --git a/tests/apps/fakeprojects/phases.py b/tests/apps/fakeprojects/phases.py
deleted file mode 100644
index ffa5a2314..000000000
--- a/tests/apps/fakeprojects/phases.py
+++ /dev/null
@@ -1,20 +0,0 @@
-from adhocracy4 import phases
-
-from . import apps
-from . import views
-
-
-class FakePhase0(phases.PhaseContent):
- app = apps.FakeProjectsConfig.label
- phase = 'phase0'
- view = views.FakePhase0View
-
-
-class FakePhase1(phases.PhaseContent):
- app = apps.FakeProjectsConfig.label
- phase = 'phase1'
- view = views.FakePhase1View
-
-
-phases.content.register(FakePhase0())
-phases.content.register(FakePhase1())
diff --git a/tests/apps/fakeprojects/views.py b/tests/apps/fakeprojects/views.py
deleted file mode 100644
index 01baa2926..000000000
--- a/tests/apps/fakeprojects/views.py
+++ /dev/null
@@ -1,13 +0,0 @@
-from django.views.generic import list
-
-from tests.apps.blog import models
-
-
-class FakePhase0View(list.ListView):
- model = models.Post
- template_name = 'fakephase0'
-
-
-class FakePhase1View(list.ListView):
- model = models.Post
- template_name = 'fakephase1'
diff --git a/tests/blueprints/test_algorithm.py b/tests/blueprints/test_algorithm.py
deleted file mode 100644
index bdde8aa70..000000000
--- a/tests/blueprints/test_algorithm.py
+++ /dev/null
@@ -1,173 +0,0 @@
-import itertools
-
-from euth.blueprints import blueprints as b
-from euth.blueprints.views import compute_time_needed
-from euth.blueprints.views import filter_blueprints
-from tests.blueprints import testdata
-
-test_blueprints = [
- ('brainstorming', b.Blueprint(
- title='brainstorming',
- description='desc',
- content=[],
- image='',
- settings_model=None,
- requirements=b.Requirements(
- aims=[b.Aim.collect_ideas],
- results=[b.Result.collect_ideas],
- experience=b.Experience.no_projects,
- motivation=b.Motivation.low
- ),
- complexity=b.COMPLEXITY_VECTOR_AC,
- type='brainstorming'
- )),
- ('ideacollection', b.Blueprint(
- title='ideacollection',
- description='desc',
- content=[],
- image='',
- settings_model=None,
- requirements=b.Requirements(
- aims=[b.Aim.collect_ideas, b.Aim.discuss_topic],
- results=[b.Result.collect_ideas, b.Result.both],
- experience=b.Experience.one_project,
- motivation=b.Motivation.medium
- ),
- complexity=b.COMPLEXITY_VECTOR_AC,
- type='ideacollection'
- )),
- ('fallback', b.Blueprint(
- title='fallback',
- description='desc',
- content=[],
- image='',
- settings_model=None,
- requirements=b.Requirements(
- aims=[b.Aim.collect_ideas],
- results=[b.Result.collect_ideas],
- experience=b.Experience.five_projects,
- motivation=b.Motivation.medium
- ),
- complexity=b.COMPLEXITY_VECTOR_AC,
- type='fallback'
- ))
-]
-
-
-fallbacks = {
- b.Aim.collect_ideas: 'fallback'
-}
-
-
-def test_blueprintsfilter_allmatch():
- data = {
- 'aim': b.Aim.collect_ideas,
- 'result': b.Result.collect_ideas,
- 'experience': b.Experience.five_projects,
- 'motivation': b.Motivation.medium,
- 'participants': b.Participants.some,
- 'scope': b.Scope.local,
- 'duration': b.Duration.one_weeks,
- 'accessibility': b.Accessibility.hard,
- 'options': test_blueprints,
- 'fallbacks': fallbacks
- }
-
- result = filter_blueprints(**data)
- assert len(result) == 3
- assert result[0][0] == 'brainstorming'
- assert result[1][0] == 'ideacollection'
- assert result[2][0] == 'fallback'
-
-
-def test_blueprintsfilter_matchone():
- data = {
- 'aim': b.Aim.collect_ideas,
- 'result': b.Result.both,
- 'experience': b.Experience.one_project,
- 'motivation': b.Motivation.medium,
- 'participants': b.Participants.some,
- 'scope': b.Scope.local,
- 'duration': b.Duration.one_weeks,
- 'accessibility': b.Accessibility.hard,
- 'options': test_blueprints,
- 'fallbacks': fallbacks
- }
-
- result = filter_blueprints(**data)
- assert len(result) == 1
- assert result[0][0] == 'ideacollection'
-
-
-def test_blueprintsfilter_none():
- data = {
- 'aim': b.Aim.collect_ideas,
- 'result': b.Result.collect_ideas,
- 'experience': b.Experience.no_projects,
- 'motivation': b.Motivation.not_found,
- 'participants': b.Participants.some,
- 'scope': b.Scope.local,
- 'duration': b.Duration.one_weeks,
- 'accessibility': b.Accessibility.hard,
- 'fallbacks': fallbacks
- }
-
- result = filter_blueprints(options=test_blueprints, **data)
- assert len(result) == 1
- assert result[0][0] == 'fallback'
-
-
-def test_time_needed():
- blueprint = b.Blueprint(
- title='brainstorming',
- description='desc',
- content=[],
- image='',
- settings_model=None,
- requirements=b.Requirements(
- aims=[b.Aim.collect_ideas],
- results=[b.Result.collect_ideas],
- experience=b.Experience.no_projects,
- motivation=b.Motivation.low
- ),
- complexity=None,
- type='brainstorming'
- )
-
- test_blueprints = [
- (
- 'AC', blueprint._replace(
- complexity=b.COMPLEXITY_VECTOR_AC
- )
- ),
- (
- 'BD', blueprint._replace(
- complexity=b.COMPLEXITY_VECTOR_BD
- ),
- ),
- (
- 'E', blueprint._replace(
- complexity=b.COMPLEXITY_VECTOR_E
- ),
- ),
- (
- 'F', blueprint._replace(
- complexity=b.COMPLEXITY_VECTOR_F
- )
- )
- ]
-
- speciemens = list(itertools.product(
- b.Participants,
- b.Duration,
- b.Scope,
- b.Motivation,
- b.Accessibility,
- b.Experience
- ))
-
- for (bid, blueprint) in test_blueprints:
- for args in speciemens:
- computed = compute_time_needed(blueprint, *args)
- raw_args = [arg.value for arg in args]
- assert computed == testdata.time_needed_oracle(bid, *raw_args)
diff --git a/tests/blueprints/test_views.py b/tests/blueprints/test_views.py
deleted file mode 100644
index 711be4abf..000000000
--- a/tests/blueprints/test_views.py
+++ /dev/null
@@ -1,156 +0,0 @@
-import pytest
-from django.urls import reverse
-
-from tests.helpers import templates_used
-
-
-def _verify_valid_response(response):
- """ Verifies a response of a request that is considered valid """
- assert response.status_code == 200
- assert 'euth_blueprints/result.html' in templates_used(response)
- assert response.context_data['form'].is_valid()
- assert len(response.context_data['blueprints']) > 0
-
- for b in response.context_data['blueprints']:
- # verify that for every blueprint a name, the blueprint and a
- # time is given
- assert len(b) == 3
-
-
-@pytest.mark.django_db
-def test_form_view(client, organisation):
- user = organisation.initiators.first()
- client.login(username=user.email, password='password')
-
- url = reverse('blueprints-form', kwargs={
- 'organisation_slug': organisation.slug
- })
- response = client.get(url)
-
- assert response.status_code == 200
- assert 'euth_blueprints/form.html' in templates_used(response)
- assert len(response.context_data['form'].errors) == 0
-
-
-'''
-@pytest.mark.django_db
-def test_form(client, organisation):
- user = organisation.initiators.first()
- client.login(username=user.email, password='password')
-
- url = reverse('blueprints-form', kwargs={
- 'organisation_slug': organisation.slug
- })
-
- for aim in blueprints.Aim:
- data = {
- 'aim': aim.name,
- 'result': '3',
- 'motivation': '4',
- 'experience': '4',
- 'participants': '1',
- 'scope': '1',
- 'duration': '1',
- 'accessibility': '2'
- }
- response = client.post(url, data)
- _verify_valid_response(response)
-
-
-@pytest.mark.django_db
-def test_form_error(client, organisation):
- user = organisation.initiators.first()
- client.login(username=user.email, password='password')
-
- url = reverse('blueprints-form', kwargs={
- 'organisation_slug': organisation.slug
- })
- data = {
- 'aim': 'invalid',
- 'result': 'invalid',
- 'motivation': 'invalid',
- 'experience': 'invalid',
- 'participants': 'invalid',
- 'scope': 'invalid',
- 'duration': 'invalid',
- 'accessibility': 'invalid'
- }
- response = client.post(url, data)
-
- assert response.status_code == 200
- assert 'euth_blueprints/form.html' in templates_used(response)
- assert len(response.context_data['form'].errors) == 8
-
-
-@pytest.mark.django_db
-def test_form_error_2(client, organisation):
- user = organisation.initiators.first()
- client.login(username=user.email, password='password')
-
- url = reverse('blueprints-form', kwargs={
- 'organisation_slug': organisation.slug
- })
- data = {
- 'aim': 'collect_ideas',
- 'result': '3',
- 'motivation': '500000',
- 'experience': '4',
- 'participants': '1',
- 'scope': '-1',
- 'duration': '1',
- 'accessibility': '2'
- }
- response = client.post(url, data)
-
- assert response.status_code == 200
- assert 'euth_blueprints/form.html' in templates_used(response)
- assert len(response.context_data['form'].errors) == 2
-
-
-@pytest.mark.django_db
-def test_form_regression_fallback(client, organisation):
- user = organisation.initiators.first()
- client.login(username=user.email, password='password')
-
- url = reverse('blueprints-form', kwargs={
- 'organisation_slug': organisation.slug
- })
- data = {
- 'aim': 'collect_ideas',
- 'result': '1',
- 'motivation': '2',
- 'experience': '1',
- 'participants': '2',
- 'scope': '1',
- 'duration': '0',
- 'accessibility': '3'
- }
- response = client.post(url, data)
- _verify_valid_response(response)
-
-
-@pytest.mark.django_db
-def test_form_regression_check_required_fields(client, organisation):
- user = organisation.initiators.first()
- client.login(username=user.email, password='password')
-
- url = reverse('blueprints-form', kwargs={
- 'organisation_slug': organisation.slug
- })
-
- # Sending a request without optional fields should work. In order to
- # verify this, we go over all form fields and only add data that is
- # required.
- fields = forms.GetSuggestionForm.base_fields
- data = {}
-
- for fieldname, field in fields.items():
- if not field.required:
- continue
-
- data.update({fieldname: field.choices[0][0]})
-
- response = client.post(url, data)
- _verify_valid_response(response)
-
-'''
diff --git a/tests/blueprints/testdata.py b/tests/blueprints/testdata.py
deleted file mode 100644
index c7cec4ced..000000000
--- a/tests/blueprints/testdata.py
+++ /dev/null
@@ -1,337 +0,0 @@
-"""
-Data copied from version 5.1 of the DST exel sheet.
-"""
-
-# (no. participants, duration, scope) -> complexity grade
-COMPLEXITY = {
- 'AC': [
- ((0, 0, 0), 0),
- ((0, 0, 1), 1),
- ((0, 0, 2), 1),
- ((0, 1, 0), 1),
- ((0, 1, 1), 2),
- ((0, 1, 2), 2),
- ((0, 2, 0), 2),
- ((0, 2, 1), 3),
- ((0, 2, 2), 3),
- ((1, 0, 0), 1),
- ((1, 0, 1), 1),
- ((1, 0, 2), 2),
- ((1, 1, 0), 2),
- ((1, 1, 1), 2),
- ((1, 1, 2), 3),
- ((1, 2, 0), 3),
- ((1, 2, 1), 3),
- ((1, 2, 2), 4),
- ((2, 0, 0), 1),
- ((2, 0, 1), 2),
- ((2, 0, 2), 2),
- ((2, 1, 0), 2),
- ((2, 1, 1), 3),
- ((2, 1, 2), 3),
- ((2, 2, 0), 3),
- ((2, 2, 1), 4),
- ((2, 2, 2), 4),
- ],
- 'BD': [
- ((0, 0, 0), 0),
- ((0, 0, 1), 1),
- ((0, 0, 2), 2),
- ((0, 1, 0), 1),
- ((0, 1, 1), 2),
- ((0, 1, 2), 3),
- ((0, 2, 0), 2),
- ((0, 2, 1), 3),
- ((0, 2, 2), 4),
- ((1, 0, 0), 1),
- ((1, 0, 1), 2),
- ((1, 0, 2), 3),
- ((1, 1, 0), 2),
- ((1, 1, 1), 3),
- ((1, 1, 2), 4),
- ((1, 2, 0), 3),
- ((1, 2, 1), 4),
- ((1, 2, 2), 5),
- ((2, 0, 0), 2),
- ((2, 0, 1), 3),
- ((2, 0, 2), 4),
- ((2, 1, 0), 3),
- ((2, 1, 1), 4),
- ((2, 1, 2), 5),
- ((2, 2, 0), 4),
- ((2, 2, 1), 5),
- ((2, 2, 2), 6),
- ],
- # modified E because due to error version 5.1 of exel sheet
- 'E': [
- ((0, 0, 0), 0),
- ((0, 0, 1), 0),
- ((0, 0, 2), 1),
- ((0, 1, 0), 0),
- ((0, 1, 1), 0),
- ((0, 1, 2), 1),
- ((0, 2, 0), 0),
- ((0, 2, 1), 0),
- ((0, 2, 2), 1),
- ((1, 0, 0), 0),
- ((1, 0, 1), 1),
- ((1, 0, 2), 1),
- ((1, 1, 0), 0),
- ((1, 1, 1), 1),
- ((1, 1, 2), 1),
- ((1, 2, 0), 0),
- ((1, 2, 1), 1),
- ((1, 2, 2), 1),
- ((2, 0, 0), 1),
- ((2, 0, 1), 1),
- ((2, 0, 2), 1),
- ((2, 1, 0), 1),
- ((2, 1, 1), 1),
- ((2, 1, 2), 1),
- ((2, 2, 0), 1),
- ((2, 2, 1), 1),
- ((2, 2, 2), 1),
- ],
- # original data for complexity of E
- # 'E': [
- # ((0, 0, 0), 0),
- # ((0, 0, 1), 0),
- # ((0, 0, 2), 1),
- # ((0, 1, 0), 0),
- # ((0, 1, 1), 0),
- # ((0, 1, 2), 1),
- # ((0, 2, 0), 0),
- # ((0, 2, 1), 0),
- # ((0, 2, 2), 1),
- # ((1, 0, 0), 0),
- # ((1, 0, 1), 0),
- # ((1, 0, 2), 1),
- # ((1, 1, 0), 0),
- # ((1, 1, 1), 0),
- # ((1, 1, 2), 1),
- # ((1, 2, 0), 0),
- # ((1, 2, 1), 0),
- # ((1, 2, 2), 1),
- # ((2, 0, 0), 1),
- # ((2, 0, 1), 1),
- # ((2, 0, 2), 2),
- # ((2, 1, 0), 1),
- # ((2, 1, 1), 1),
- # ((2, 1, 2), 2),
- # ((2, 2, 0), 1),
- # ((2, 2, 1), 1),
- # ((2, 2, 2), 2),
- # ],
- 'F': [
- ((0, 0, 0), 1),
- ((0, 0, 1), 1),
- ((0, 0, 2), 1),
- ((0, 1, 0), 2),
- ((0, 1, 1), 2),
- ((0, 1, 2), 2),
- ((0, 2, 0), 3),
- ((0, 2, 1), 3),
- ((0, 2, 2), 3),
- ((1, 0, 0), 2),
- ((1, 0, 1), 2),
- ((1, 0, 2), 2),
- ((1, 1, 0), 3),
- ((1, 1, 1), 3),
- ((1, 1, 2), 3),
- ((1, 2, 0), 4),
- ((1, 2, 1), 4),
- ((1, 2, 2), 4),
- ((2, 0, 0), 3),
- ((2, 0, 1), 3),
- ((2, 0, 2), 3),
- ((2, 1, 0), 4),
- ((2, 1, 1), 4),
- ((2, 1, 2), 4),
- ((2, 2, 0), 5),
- ((2, 2, 1), 5),
- ((2, 2, 2), 5),
- ],
-}
-
-# (motivation, accessibillity) -> dedication
-DEDICATION = [
- ((1, 1), 1),
- ((1, 2), 2),
- ((1, 3), 2),
- ((1, 4), 3),
- ((2, 1), 2),
- ((2, 2), 2),
- ((2, 3), 3),
- ((2, 4), 3),
- ((3, 1), 2),
- ((3, 2), 3),
- ((3, 3), 3),
- ((3, 4), 4),
- ((4, 1), 3),
- ((4, 2), 3),
- ((4, 3), 4),
- ((4, 4), 4),
-]
-
-# (grade of complexity, dedication, experience) -> time needed
-TIME_NEEDED = [
- ((0, 4, 4), 5),
- ((0, 4, 3), 5),
- ((0, 4, 2), 5),
- ((0, 4, 1), 5),
- ((0, 3, 4), 5),
- ((0, 3, 3), 5),
- ((0, 3, 2), 5),
- ((0, 3, 1), 5),
- ((0, 2, 4), 5),
- ((0, 2, 3), 5),
- ((0, 2, 2), 5),
- ((0, 2, 1), 5),
- ((0, 1, 4), 5),
- ((0, 1, 3), 5),
- ((0, 1, 2), 5),
- ((0, 1, 1), 5),
- ((1, 4, 4), 10),
- ((1, 4, 3), 10),
- ((1, 4, 2), 5),
- ((1, 4, 1), 5),
- ((1, 3, 4), 10),
- ((1, 3, 3), 5),
- ((1, 3, 2), 5),
- ((1, 3, 1), 5),
- ((1, 2, 4), 5),
- ((1, 2, 3), 5),
- ((1, 2, 2), 5),
- ((1, 2, 1), 5),
- ((1, 1, 4), 5),
- ((1, 1, 3), 5),
- ((1, 1, 2), 5),
- ((1, 1, 1), 5),
- ((2, 4, 4), 15),
- ((2, 4, 3), 15),
- ((2, 4, 2), 10),
- ((2, 4, 1), 10),
- ((2, 3, 4), 15),
- ((2, 3, 3), 10),
- ((2, 3, 2), 10),
- ((2, 3, 1), 5),
- ((2, 2, 4), 10),
- ((2, 2, 3), 10),
- ((2, 2, 2), 5),
- ((2, 2, 1), 5),
- ((2, 1, 4), 10),
- ((2, 1, 3), 5),
- ((2, 1, 2), 5),
- ((2, 1, 1), 5),
- ((3, 3, 4), 20),
- ((3, 4, 4), 20),
- ((3, 4, 3), 20),
- ((3, 4, 2), 15),
- ((3, 4, 1), 10),
- ((3, 3, 3), 15),
- ((3, 3, 2), 10),
- ((3, 3, 1), 10),
- ((3, 2, 4), 15),
- ((3, 2, 3), 10),
- ((3, 2, 2), 10),
- ((3, 2, 1), 5),
- ((3, 1, 4), 10),
- ((3, 1, 3), 10),
- ((3, 1, 2), 5),
- ((3, 1, 1), 5),
- ((4, 4, 4), 30),
- ((4, 4, 3), 25),
- ((4, 4, 2), 20),
- ((4, 4, 1), 15),
- ((4, 3, 4), 25),
- ((4, 3, 3), 20),
- ((4, 3, 2), 15),
- ((4, 3, 1), 10),
- ((4, 2, 4), 20),
- ((4, 2, 3), 15),
- ((4, 2, 2), 10),
- ((4, 2, 1), 10),
- ((4, 1, 4), 15),
- ((4, 1, 3), 10),
- ((4, 1, 2), 10),
- ((4, 1, 1), 5),
- ((5, 4, 4), 30),
- ((5, 4, 3), 30),
- ((5, 4, 2), 25),
- ((5, 4, 1), 20),
- ((5, 3, 4), 30),
- ((5, 3, 3), 25),
- ((5, 3, 2), 20),
- ((5, 3, 1), 15),
- ((5, 2, 4), 25),
- ((5, 2, 3), 20),
- ((5, 2, 2), 15),
- ((5, 2, 1), 10),
- ((5, 1, 4), 20),
- ((5, 1, 3), 15),
- ((5, 1, 2), 10),
- ((5, 1, 1), 5),
- ((6, 4, 4), 35),
- ((6, 4, 3), 30),
- ((6, 4, 2), 30),
- ((6, 4, 1), 25),
- ((6, 3, 4), 30),
- ((6, 3, 3), 30),
- ((6, 3, 2), 25),
- ((6, 3, 1), 20),
- ((6, 2, 4), 30),
- ((6, 2, 3), 25),
- ((6, 2, 2), 20),
- ((6, 2, 1), 15),
- ((6, 1, 4), 25),
- ((6, 1, 3), 20),
- ((6, 1, 2), 15),
- ((6, 1, 1), 10),
- ((7, 4, 4), 40),
- ((7, 4, 3), 35),
- ((7, 4, 2), 30),
- ((7, 4, 1), 30),
- ((7, 3, 4), 35),
- ((7, 3, 3), 30),
- ((7, 3, 2), 30),
- ((7, 3, 1), 20),
- ((7, 2, 4), 30),
- ((7, 2, 3), 30),
- ((7, 2, 2), 20),
- ((7, 2, 1), 15),
- ((7, 1, 4), 30),
- ((7, 1, 3), 20),
- ((7, 1, 2), 15),
- ((7, 1, 1), 10),
- ((8, 4, 4), 40),
- ((8, 4, 3), 40),
- ((8, 4, 2), 35),
- ((8, 4, 1), 30),
- ((8, 3, 4), 40),
- ((8, 3, 3), 35),
- ((8, 3, 2), 30),
- ((8, 3, 1), 25),
- ((8, 2, 4), 35),
- ((8, 2, 3), 30),
- ((8, 2, 2), 25),
- ((8, 2, 1), 15),
- ((8, 1, 4), 30),
- ((8, 1, 3), 25),
- ((8, 1, 2), 15),
- ((8, 1, 1), 10),
-]
-
-
-def time_needed_oracle(
- process, participants, duration, scope,
- motivation, accessibility, experience
-):
- # undo encoding change that was required to easy implementation
- motivation = 5 - motivation
- experience = 5 - experience
-
- complexity = dict(COMPLEXITY[process])[(participants, duration, scope)]
- dedication = dict(DEDICATION)[(motivation, accessibility)]
- time_needed = dict(TIME_NEEDED)[(complexity, dedication, experience)]
-
- return time_needed
diff --git a/tests/communitydebate/conftest.py b/tests/communitydebate/conftest.py
deleted file mode 100644
index 0b1a5c09f..000000000
--- a/tests/communitydebate/conftest.py
+++ /dev/null
@@ -1,17 +0,0 @@
-import factory
-import pytest
-from pytest_factoryboy import register
-
-from tests.actions import factories as comment_factories
-from tests.communitydebate import factories as communitydebate_fatories
-from tests.ratings import factories as rating_factories
-
-register(rating_factories.RatingFactory)
-register(comment_factories.CommentFactory)
-register(communitydebate_fatories.TopicFactory)
-register(communitydebate_fatories.TopicFileUploadFactory)
-
-
-@pytest.fixture
-def DocumentCSV():
- return factory.django.FileField(filename='example_doc.csv')
diff --git a/tests/communitydebate/factories.py b/tests/communitydebate/factories.py
deleted file mode 100644
index 057c68401..000000000
--- a/tests/communitydebate/factories.py
+++ /dev/null
@@ -1,28 +0,0 @@
-import factory
-
-from adhocracy4.test.factories import ModuleFactory
-from euth.communitydebate import models as communitydebate_models
-from tests.factories import UserFactory
-
-
-class TopicFactory(factory.django.DjangoModelFactory):
-
- class Meta:
- model = communitydebate_models.Topic
-
- name = factory.Faker('name')
- description = 'Description'
- creator = factory.SubFactory(UserFactory)
- module = factory.SubFactory(ModuleFactory)
-
-
-class TopicFileUploadFactory(factory.django.DjangoModelFactory):
-
- class Meta:
- model = communitydebate_models.TopicFileUpload
-
- title = factory.Faker('word')
- document = factory.django.FileField(
- filename=factory.Faker('file_name', extension='pdf'),
- data='test')
- topic = factory.SubFactory(TopicFactory)
diff --git a/tests/communitydebate/test_communitydebate_models.py b/tests/communitydebate/test_communitydebate_models.py
deleted file mode 100644
index cd3cb7ed1..000000000
--- a/tests/communitydebate/test_communitydebate_models.py
+++ /dev/null
@@ -1,165 +0,0 @@
-import os
-
-import pytest
-from django.conf import settings
-from django.urls import reverse
-
-from adhocracy4.comments import models as comments_models
-from adhocracy4.ratings import models as rating_models
-from euth.communitydebate import models as communitydebate_models
-from tests import helpers
-
-
-@pytest.mark.django_db
-def test_file_upload_deleted_after_topic_deletion(topic_factory,
- topic_file_upload_factory):
- topic = topic_factory()
- topic_file = topic_file_upload_factory(topic=topic)
- topic_file_path = os.path.join(settings.MEDIA_ROOT,
- topic_file.document.path)
- assert os.path.isfile(topic_file_path)
-
- topic.delete()
- assert not os.path.isfile(topic_file_path)
-
-
-@pytest.mark.django_db
-def test_file_upload_validation(topic_file_upload_factory, DocumentCSV):
- topic_file = topic_file_upload_factory(document=DocumentCSV)
- with pytest.raises(Exception) as e:
- topic_file.full_clean()
- assert 'Unsupported file format.' in str(e.value)
- topic_file.delete()
-
-
-@pytest.mark.django_db
-def test_absolute_url(topic):
- url = reverse('topic-detail', kwargs={'slug': topic.slug})
- assert topic.get_absolute_url() == url
-
-
-@pytest.mark.django_db
-def test_save(topic):
- assert 'text'
- weight = factory.Faker('random_number')
- document = factory.SubFactory(DocumentFactory)
diff --git a/tests/documents/test_document_api.py b/tests/documents/test_document_api.py
deleted file mode 100644
index eb193869b..000000000
--- a/tests/documents/test_document_api.py
+++ /dev/null
@@ -1,283 +0,0 @@
-import pytest
-from django.urls import reverse
-from rest_framework import status
-
-from euth.documents import models as document_models
-
-
-@pytest.mark.django_db
-def test_anonymous_user_can_not_retrieve_document_list(apiclient, module):
- url = reverse('documents-list', args=[module.pk])
- response = apiclient.get(url, format='json')
- assert response.status_code == status.HTTP_403_FORBIDDEN
-
-
-@pytest.mark.django_db
-def test_anonymous_user_can_not_retrieve_document_detail(apiclient, document):
- url = reverse('documents-detail', kwargs={
- 'module_pk': document.module.pk,
- 'pk': document.pk
- })
- response = apiclient.get(url, format='json')
- assert response.status_code == status.HTTP_403_FORBIDDEN
-
-
-@pytest.mark.django_db
-def test_anonymous_user_can_not_create_document(apiclient, module):
- url = reverse('documents-list', args=[module.pk])
- data = {
- 'name': 'This is a text',
- 'module': module.pk,
- 'paragraphs': []
- }
- response = apiclient.post(url, data, format='json')
- assert response.status_code == status.HTTP_403_FORBIDDEN
- count = document_models.Document.objects.all().count()
- assert count == 0
-
-
-@pytest.mark.django_db
-def test_moderator_can_create_document(apiclient, module):
- project = module.project
- moderator = project.moderators.first()
- apiclient.force_authenticate(user=moderator)
- url = reverse('documents-list', args=[module.pk])
- data = {
- 'name': 'This is a text',
- 'module': module.pk,
- 'paragraphs': []
- }
- response = apiclient.post(url, data, format='json')
- assert response.status_code == status.HTTP_201_CREATED
- count = document_models.Document.objects.all().count()
- assert count == 1
-
-
-@pytest.mark.django_db
-def test_moderator_can_not_create_two_documents(apiclient, document):
- project = document.module.project
- moderator = project.moderators.first()
- apiclient.force_authenticate(user=moderator)
-
- url = reverse('documents-list', args=[document.module.pk])
- data = {
- 'name': 'This is a text',
- 'module': document.module.pk,
- 'paragraphs': []
- }
- response = apiclient.post(url, data, format='json')
- assert response.status_code == status.HTTP_400_BAD_REQUEST
-
-
-@pytest.mark.django_db
-def test_paragraphs_are_correctly_sorted(apiclient, module):
- project = module.project
- moderator = project.moderators.first()
- apiclient.force_authenticate(user=moderator)
- url = reverse('documents-list', args=[module.pk])
- data = {
- 'name': 'This is a text',
- 'module': module.pk,
- 'paragraphs': [
- {
- 'name': 'paragraph 1',
- 'text': 'text for paragraph 1',
- 'weight': 3
- },
- {
- 'name': 'paragraph 2',
- 'text': 'text for paragraph 2',
- 'weight': 1
- },
- {
- 'name': 'paragraph 3',
- 'text': 'text for paragraph 3',
- 'weight': 0
- }
-
- ]
- }
- response = apiclient.post(url, data, format='json')
- assert response.status_code == status.HTTP_201_CREATED
- assert response.data['paragraphs'][0]['name'] == 'paragraph 3'
- assert response.data['paragraphs'][1]['name'] == 'paragraph 2'
- assert response.data['paragraphs'][2]['name'] == 'paragraph 1'
-
-
-@pytest.mark.django_db
-def test_moderator_can_delete_paragraph(apiclient, module):
- project = module.project
- moderator = project.moderators.first()
- apiclient.force_authenticate(user=moderator)
- url = reverse('documents-list', args=[module.pk])
- data = {
- 'name': 'This is a text',
- 'module': module.pk,
- 'paragraphs': [
- {
- 'name': 'paragraph 1',
- 'text': 'text for paragraph 1',
- 'weight': 0
- },
- {
- 'name': 'paragraph 2',
- 'text': 'text for paragraph 2',
- 'weight': 1
- }
- ]
- }
- response = apiclient.post(url, data, format='json')
- assert response.status_code == status.HTTP_201_CREATED
- documents = document_models.Document.objects.all()
- document_count = documents.count()
- assert document_count == 1
- paragraphs_count = documents.first().paragraphs.count()
- paragraphs_all_count = document_models.Paragraph.objects.all().count()
- assert paragraphs_count == 2
- assert paragraphs_all_count == 2
-
- document_pk = response.data['id']
- url = reverse('documents-detail', kwargs={
- 'module_pk': module.pk,
- 'pk': document_pk
- })
- data = {
- 'name': 'This is a text',
- 'module': module.pk,
- 'paragraphs': [
- {
- 'name': 'paragraph 2',
- 'text': 'text for paragraph 2',
- 'weight': 1
- }
- ]
- }
- response = apiclient.put(url, data, format='json')
- assert response.status_code == status.HTTP_200_OK
- paragraphs_count = documents.first().paragraphs.count()
- paragraphs_all_count = document_models.Paragraph.objects.all().count()
- assert paragraphs_count == 1
- assert paragraphs_all_count == 1
-
-
-@pytest.mark.django_db
-def test_moderator_can_update_paragraph(apiclient, module):
- project = module.project
- moderator = project.moderators.first()
- apiclient.force_authenticate(user=moderator)
- url = reverse('documents-list', args=[module.pk])
- data = {
- 'name': 'This is a text',
- 'module': module.pk,
- 'paragraphs': [
- {
- 'name': 'paragraph 1',
- 'text': 'text for paragraph 1',
- 'weight': 0
- }
- ]
- }
- response = apiclient.post(url, data, format='json')
- assert response.status_code == status.HTTP_201_CREATED
-
- paragraphs_all_count = document_models.Paragraph.objects.all().count()
- assert paragraphs_all_count == 1
-
- document_pk = response.data['id']
- paragraph_pk = response.data['paragraphs'][0]['id']
- paragraph_text = document_models.Paragraph.objects.get(
- pk=paragraph_pk).text
- assert paragraph_text == 'text for paragraph 1'
-
- url = reverse('documents-detail', kwargs={
- 'module_pk': module.pk,
- 'pk': document_pk
- })
- data = {
- 'name': 'This is a text',
- 'module': module.pk,
- 'paragraphs': [
- {
- 'name': 'paragraph 1',
- 'text': 'text for paragraph 1 updated',
- 'weight': 1,
- 'id': paragraph_pk
- }
- ]
- }
-
- response = apiclient.put(url, data, format='json')
- assert response.status_code == status.HTTP_200_OK
- paragraph_text = document_models.Paragraph.objects.get(
- pk=paragraph_pk).text
- assert paragraph_text == 'text for paragraph 1 updated'
-
- paragraphs_all_count = document_models.Paragraph.objects.all().count()
- assert paragraphs_all_count == 1
-
-
-@pytest.mark.django_db
-def test_moderator_can_update_and_create_paragraph(apiclient, module):
- project = module.project
- moderator = project.moderators.first()
- apiclient.force_authenticate(user=moderator)
- url = reverse('documents-list', args=[module.pk])
- data = {
- 'name': 'This is a text',
- 'module': module.pk,
- 'paragraphs': [
- {
- 'name': 'paragraph 1',
- 'text': 'text for paragraph 1',
- 'weight': 0
- }
- ]
- }
- response = apiclient.post(url, data, format='json')
- assert response.status_code == status.HTTP_201_CREATED
-
- paragraphs_all_count = document_models.Paragraph.objects.all().count()
- assert paragraphs_all_count == 1
-
- document_pk = response.data['id']
- paragraph_pk = response.data['paragraphs'][0]['id']
- paragraph_text = document_models.Paragraph.objects.get(
- pk=paragraph_pk).text
- assert paragraph_text == 'text for paragraph 1'
-
- url = reverse('documents-detail', kwargs={
- 'module_pk': module.pk,
- 'pk': document_pk
- })
- data = {
- 'name': 'This is a text',
- 'module': module.pk,
- 'paragraphs': [
- {
- 'name': 'paragraph 1',
- 'text': 'text for paragraph 1 updated',
- 'weight': 1,
- 'id': paragraph_pk
- },
- {
- 'name': 'paragraph 2',
- 'text': 'text for paragraph 2',
- 'weight': 2
- }
- ]
- }
-
- response = apiclient.put(url, data, format='json')
- assert response.status_code == status.HTTP_200_OK
- paragraph_0_pk = response.data['paragraphs'][0]['id']
- paragraph_0_text = document_models.Paragraph.objects.get(
- pk=paragraph_0_pk).text
- assert paragraph_0_text == 'text for paragraph 1 updated'
-
- paragraph_1_pk = response.data['paragraphs'][1]['id']
- paragraph_1_text = document_models.Paragraph.objects.get(
- pk=paragraph_1_pk).text
- assert paragraph_1_text == 'text for paragraph 2'
-
- paragraphs_all_count = document_models.Paragraph.objects.all().count()
- assert paragraphs_all_count == 2
diff --git a/tests/documents/test_document_models.py b/tests/documents/test_document_models.py
deleted file mode 100644
index 35f4c0bad..000000000
--- a/tests/documents/test_document_models.py
+++ /dev/null
@@ -1,95 +0,0 @@
-import pytest
-from django.contrib.contenttypes.models import ContentType
-from django.core.exceptions import ValidationError
-
-from adhocracy4.comments import models as comments_models
-from euth.documents.models import Document
-
-
-@pytest.mark.django_db
-def test_paragraph_save(paragraph):
- assert 'Description'
- creator = factory.SubFactory(UserFactory)
- module = factory.SubFactory(ModuleFactory)
diff --git a/tests/ideas/test_idea_models.py b/tests/ideas/test_idea_models.py
deleted file mode 100644
index 6ab0203aa..000000000
--- a/tests/ideas/test_idea_models.py
+++ /dev/null
@@ -1,142 +0,0 @@
-import os
-
-import pytest
-from django.conf import settings
-from django.urls import reverse
-
-from adhocracy4.comments import models as comments_models
-from adhocracy4.ratings import models as rating_models
-from euth.ideas import models as idea_models
-from tests import helpers
-
-
-@pytest.mark.django_db
-def test_absolute_url(idea):
- url = reverse('idea-detail', kwargs={'slug': idea.slug})
- assert idea.get_absolute_url() == url
-
-
-@pytest.mark.django_db
-def test_save(idea):
- assert '
{{ paragraph.name }}
- {{ paragraph.text|safe }} -