• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

liqd / adhocracy-plus / 18908688697

29 Oct 2025 12:59PM UTC coverage: 44.622% (-44.5%) from 89.135%
18908688697

Pull #2986

github

web-flow
Merge 1dfde8ee7 into 445e1d498
Pull Request #2986: Draft: Speed up Github Ci Tests

3012 of 6750 relevant lines covered (44.62%)

0.45 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

61.43
/apps/ideas/views.py
1
from django.contrib import messages
1✔
2
from django.db import transaction
1✔
3
from django.urls import reverse
1✔
4
from django.utils.translation import gettext_lazy as _
1✔
5
from django.views import generic
1✔
6

7
from adhocracy4.categories import filters as category_filters
1✔
8
from adhocracy4.exports.views import DashboardExportView
1✔
9
from adhocracy4.filters import filters as a4_filters
1✔
10
from adhocracy4.filters import views as filter_views
1✔
11
from adhocracy4.filters.filters import FreeTextFilter
1✔
12
from adhocracy4.projects.mixins import DisplayProjectOrModuleMixin
1✔
13
from adhocracy4.projects.mixins import ProjectMixin
1✔
14
from adhocracy4.rules import mixins as rules_mixins
1✔
15
from apps.contrib import forms as contrib_forms
1✔
16
from apps.contrib.views import CanonicalURLDetailView
1✔
17
from apps.contrib.widgets import AplusOrderingWidget
1✔
18
from apps.contrib.widgets import FreeTextFilterWidget
1✔
19
from apps.moderatorfeedback.forms import ModeratorFeedbackForm
1✔
20
from apps.moderatorfeedback.models import ModeratorFeedback
1✔
21
from apps.notifications.emails import NotifyCreatorOnModeratorFeedback
1✔
22
from apps.organisations.mixins import UserFormViewMixin
1✔
23

24
from . import forms
1✔
25
from . import models
1✔
26

27

28
def get_ordering_choices(view):
1✔
29
    choices = (("-created", _("Most recent")),)
×
30
    if view.module.has_feature("rate", models.Idea):
×
31
        choices += (("-positive_rating_count", _("Most popular")),)
×
32
    choices += (("-comment_count", _("Most commented")),)
×
33
    return choices
×
34

35

36
class IdeaFilterSet(a4_filters.DefaultsFilterSet):
1✔
37
    defaults = {"ordering": "-created"}
1✔
38
    category = category_filters.CategoryFilter()
1✔
39
    ordering = a4_filters.DynamicChoicesOrderingFilter(
1✔
40
        choices=get_ordering_choices, widget=AplusOrderingWidget
41
    )
42
    search = FreeTextFilter(widget=FreeTextFilterWidget, fields=["name"])
1✔
43

44
    class Meta:
1✔
45
        model = models.Idea
1✔
46
        fields = ["search", "category"]
1✔
47

48

49
class AbstractIdeaListView(ProjectMixin, filter_views.FilteredListView):
1✔
50
    paginate_by = 15
1✔
51

52
    def get_queryset(self):
1✔
53
        qs = super().get_queryset().filter(module=self.module)
×
54
        if qs and not hasattr(qs.first(), "comment_count"):
×
55
            return self.annotate_queryset(qs)
×
56
        return qs
×
57

58
    def annotate_queryset(self, qs):
1✔
59
        qs = qs.annotate_comment_count()
×
60
        if hasattr(qs, "annotate_positive_rating_count"):
×
61
            qs = qs.annotate_positive_rating_count().annotate_negative_rating_count()
×
62
        return qs
×
63

64

65
class IdeaListView(AbstractIdeaListView, DisplayProjectOrModuleMixin):
1✔
66
    model = models.Idea
1✔
67
    filter_set = IdeaFilterSet
1✔
68

69

70
class AbstractIdeaDetailView(
1✔
71
    ProjectMixin, rules_mixins.PermissionRequiredMixin, CanonicalURLDetailView
72
):
73
    get_context_from_object = True
1✔
74

75

76
class IdeaDetailView(AbstractIdeaDetailView):
1✔
77
    model = models.Idea
1✔
78
    queryset = (
1✔
79
        models.Idea.objects.annotate_positive_rating_count().annotate_negative_rating_count()
80
    )
81
    permission_required = "a4_candy_ideas.view_idea"
1✔
82

83

84
class AbstractIdeaCreateView(
1✔
85
    ProjectMixin, rules_mixins.PermissionRequiredMixin, generic.CreateView
86
):
87
    """Create an idea in the context of a module."""
88

89
    def get_permission_object(self, *args, **kwargs):
1✔
90
        return self.module
×
91

92
    def form_valid(self, form):
1✔
93
        form.instance.creator = self.request.user
×
94
        form.instance.module = self.module
×
95
        return super().form_valid(form)
×
96

97
    def get_form_kwargs(self):
1✔
98
        kwargs = super().get_form_kwargs()
×
99
        kwargs["module"] = self.module
×
100
        if self.module.settings_instance:
×
101
            kwargs["settings_instance"] = self.module.settings_instance
×
102
        return kwargs
×
103

104

105
class IdeaCreateView(AbstractIdeaCreateView, UserFormViewMixin):
1✔
106
    model = models.Idea
1✔
107
    form_class = forms.IdeaForm
1✔
108
    permission_required = "a4_candy_ideas.add_idea"
1✔
109
    template_name = "a4_candy_ideas/idea_create_form.html"
1✔
110

111

112
class AbstractIdeaUpdateView(
1✔
113
    ProjectMixin, rules_mixins.PermissionRequiredMixin, generic.UpdateView
114
):
115
    get_context_from_object = True
1✔
116

117
    def get_form_kwargs(self):
1✔
118
        kwargs = super().get_form_kwargs()
×
119
        instance = kwargs.get("instance")
×
120
        kwargs["module"] = instance.module
×
121
        if instance.module.settings_instance:
×
122
            kwargs["settings_instance"] = instance.module.settings_instance
×
123
        return kwargs
×
124

125

126
class IdeaUpdateView(AbstractIdeaUpdateView, UserFormViewMixin):
1✔
127
    model = models.Idea
1✔
128
    form_class = forms.IdeaForm
1✔
129
    permission_required = "a4_candy_ideas.change_idea"
1✔
130
    template_name = "a4_candy_ideas/idea_update_form.html"
1✔
131

132

133
class AbstractIdeaDeleteView(
1✔
134
    ProjectMixin, rules_mixins.PermissionRequiredMixin, generic.DeleteView
135
):
136
    get_context_from_object = True
1✔
137

138
    def get_success_url(self):
1✔
139
        return reverse(
×
140
            "project-detail",
141
            kwargs={
142
                "organisation_slug": self.module.project.organisation.slug,
143
                "slug": self.project.slug,
144
            },
145
        )
146

147
    def form_valid(self, request, *args, **kwargs):
1✔
148
        messages.success(self.request, self.success_message)
×
149
        return super(AbstractIdeaDeleteView, self).form_valid(request, *args, **kwargs)
×
150

151

152
class IdeaDeleteView(AbstractIdeaDeleteView):
1✔
153
    model = models.Idea
1✔
154
    success_message = _("Your Idea has been deleted")
1✔
155
    permission_required = "a4_candy_ideas.delete_idea"
1✔
156
    template_name = "a4_candy_ideas/idea_confirm_delete.html"
1✔
157

158

159
class AbstractIdeaModerateView(
1✔
160
    ProjectMixin,
161
    rules_mixins.PermissionRequiredMixin,
162
    generic.detail.SingleObjectMixin,
163
    generic.detail.SingleObjectTemplateResponseMixin,
164
    contrib_forms.BaseMultiModelFormView,
165
):
166
    get_context_from_object = True
1✔
167

168
    def __init__(self):
1✔
169
        self.forms = {
×
170
            "moderateable": {
171
                "model": self.model,
172
                "form_class": self.moderateable_form_class,
173
            },
174
            "feedback_text": {
175
                "model": ModeratorFeedback,
176
                "form_class": ModeratorFeedbackForm,
177
            },
178
        }
179

180
    def dispatch(self, *args, **kwargs):
1✔
181
        self.object = self.get_object()
×
182
        return super().dispatch(*args, **kwargs)
×
183

184
    def get_success_url(self):
1✔
185
        return self.object.get_absolute_url()
×
186

187
    def forms_save(self, forms, commit=True):
1✔
188
        objects = super().forms_save(forms, commit=False)
×
189
        moderateable = objects["moderateable"]
×
190
        feedback_text = objects["feedback_text"]
×
191

192
        if not feedback_text.pk:
×
193
            feedback_text.creator = self.request.user
×
194

195
        with transaction.atomic():
×
196
            feedback_text.save()
×
197
            moderateable.moderator_feedback_text = feedback_text
×
198
            moderateable.save()
×
199
            NotifyCreatorOnModeratorFeedback.send(self.object)
×
200
        return objects
×
201

202
    def get_instance(self, name):
1✔
203
        if name == "moderateable":
×
204
            return self.object
×
205
        elif name == "feedback_text":
×
206
            return self.object.moderator_feedback_text
×
207

208

209
class IdeaModerateView(AbstractIdeaModerateView):
1✔
210
    model = models.Idea
1✔
211
    permission_required = "a4_candy_ideas.moderate_idea"
1✔
212
    template_name = "a4_candy_ideas/idea_moderate_form.html"
1✔
213
    moderateable_form_class = forms.IdeaModerateForm
1✔
214

215

216
class IdeaDashboardExportView(DashboardExportView):
1✔
217
    template_name = "a4exports/export_dashboard.html"
1✔
218

219
    def get_context_data(self, **kwargs):
1✔
220
        context = super().get_context_data(**kwargs)
×
221
        context["export"] = reverse(
×
222
            "a4dashboard:idea-export",
223
            kwargs={
224
                "organisation_slug": self.module.project.organisation.slug,
225
                "module_slug": self.module.slug,
226
            },
227
        )
228
        context["comment_export"] = reverse(
×
229
            "a4dashboard:idea-comment-export",
230
            kwargs={
231
                "organisation_slug": self.module.project.organisation.slug,
232
                "module_slug": self.module.slug,
233
            },
234
        )
235
        return context
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc