views.py 12.8 KB
Newer Older
1
2
# -*- coding:utf-8 -*
#
Skia's avatar
Skia committed
3
# Copyright 2016,2017,2018
4
# - Skia <skia@libskia.so>
5
# - Sli <antoine@bartuccio.fr>
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#
# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# http://ae.utbm.fr.
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License a published by the Free Software
# Foundation; either version 3 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple
# Place - Suite 330, Boston, MA 02111-1307, USA.
#
#

Krophil's avatar
Krophil committed
26
from django.shortcuts import get_object_or_404
Skia's avatar
Skia committed
27
28
from django.views.generic import ListView, DetailView, RedirectView
from django.views.generic.edit import UpdateView, CreateView, DeleteView
Skia's avatar
Skia committed
29
from django.views.generic.detail import SingleObjectMixin
Skia's avatar
Skia committed
30
from django.utils.translation import ugettext_lazy as _
Krophil's avatar
Krophil committed
31
from django.core.urlresolvers import reverse_lazy
Skia's avatar
Skia committed
32
33
34
35
from django.utils import timezone
from django.conf import settings
from django import forms
from django.core.exceptions import PermissionDenied
36
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
Skia's avatar
Skia committed
37

Krophil's avatar
Krophil committed
38
from ajax_select import make_ajax_field
Skia's avatar
Skia committed
39

40
41
42
43
44
from core.views import (
    CanViewMixin,
    CanEditMixin,
    CanEditPropMixin,
    CanCreateMixin,
45
    can_view,
46
)
47
from core.views.forms import MarkdownInput
Skia's avatar
Skia committed
48
from forum.models import Forum, ForumMessage, ForumTopic, ForumMessageMeta
49
50
51
from haystack.query import SearchQuerySet


52
class ForumSearchView(ListView):
53
54
55
56
    template_name = "forum/search.jinja"

    def get_queryset(self):
        query = self.request.GET.get("query", "")
57
58
59
60
        queryset = SearchQuerySet().models(ForumMessage).autocomplete(auto=query)[:100]
        return [
            r.object for r in queryset if can_view(r.object.topic, self.request.user)
        ][:30]
Skia's avatar
Skia committed
61

Krophil's avatar
Krophil committed
62

Skia's avatar
Skia committed
63
class ForumMainView(ListView):
Sli's avatar
Sli committed
64
65
66
    queryset = Forum.objects.filter(parent=None).prefetch_related(
        "children___last_message__author", "children___last_message__topic"
    )
Skia's avatar
Skia committed
67
68
    template_name = "forum/main.jinja"

Krophil's avatar
Krophil committed
69

Skia's avatar
Skia committed
70
71
class ForumMarkAllAsRead(RedirectView):
    permanent = False
Sli's avatar
Sli committed
72
    url = reverse_lazy("forum:last_unread")
Skia's avatar
Skia committed
73
74
75
76
77
78

    def get(self, request, *args, **kwargs):
        try:
            fi = request.user.forum_infos
            fi.last_read_date = timezone.now()
            fi.save()
79
            for m in request.user.read_messages.filter(date__lt=fi.last_read_date):
Krophil's avatar
Krophil committed
80
81
82
                m.readers.remove(request.user)  # Clean up to keep table low in data
        except:
            pass
Skia's avatar
Skia committed
83
84
        return super(ForumMarkAllAsRead, self).get(request, *args, **kwargs)

Krophil's avatar
Krophil committed
85

Skia's avatar
Skia committed
86
87
88
89
90
91
92
93
94
class ForumFavoriteTopics(ListView):
    model = ForumTopic
    template_name = "forum/favorite_topics.jinja"
    paginate_by = settings.SITH_FORUM_PAGE_LENGTH / 2

    def get_queryset(self):
        topic_list = self.request.user.favorite_topics.all()
        return topic_list

Sli's avatar
Sli committed
95

Skia's avatar
Skia committed
96
97
98
class ForumLastUnread(ListView):
    model = ForumTopic
    template_name = "forum/last_unread.jinja"
Skia's avatar
Skia committed
99
    paginate_by = settings.SITH_FORUM_PAGE_LENGTH / 2
Skia's avatar
Skia committed
100
101

    def get_queryset(self):
Sli's avatar
Sli committed
102
103
104
105
106
107
108
109
110
        topic_list = (
            self.model.objects.filter(
                _last_message__date__gt=self.request.user.forum_infos.last_read_date
            )
            .exclude(_last_message__readers=self.request.user)
            .order_by("-_last_message__date")
            .select_related("_last_message__author", "author")
            .prefetch_related("forum__edit_groups")
        )
111
        return topic_list
Skia's avatar
Skia committed
112

Krophil's avatar
Krophil committed
113

Sli's avatar
Sli committed
114
115
116
117
118
class ForumNameField(forms.ModelChoiceField):
    def label_from_instance(self, obj):
        return obj.get_full_name()


119
120
121
class ForumForm(forms.ModelForm):
    class Meta:
        model = Forum
Sli's avatar
Sli committed
122
123
124
125
126
127
128
129
130
131
132
133
        fields = [
            "name",
            "parent",
            "number",
            "owner_club",
            "is_category",
            "edit_groups",
            "view_groups",
        ]

    edit_groups = make_ajax_field(Forum, "edit_groups", "groups", help_text="")
    view_groups = make_ajax_field(Forum, "view_groups", "groups", help_text="")
Sli's avatar
Sli committed
134
    parent = ForumNameField(Forum.objects.all())
135

Krophil's avatar
Krophil committed
136

137
class ForumCreateView(CanCreateMixin, CreateView):
Skia's avatar
Skia committed
138
    model = Forum
139
    form_class = ForumForm
Skia's avatar
Skia committed
140
141
142
143
    template_name = "core/create.jinja"

    def get_initial(self):
        init = super(ForumCreateView, self).get_initial()
Skia's avatar
Skia committed
144
        try:
Sli's avatar
Sli committed
145
146
147
148
149
            parent = Forum.objects.filter(id=self.request.GET["parent"]).first()
            init["parent"] = parent
            init["owner_club"] = parent.owner_club
            init["edit_groups"] = parent.edit_groups.all()
            init["view_groups"] = parent.view_groups.all()
Krophil's avatar
Krophil committed
150
151
        except:
            pass
Skia's avatar
Skia committed
152
153
        return init

Krophil's avatar
Krophil committed
154

155
class ForumEditForm(ForumForm):
Sli's avatar
Sli committed
156
157
158
    recursive = forms.BooleanField(
        label=_("Apply rights and club owner recursively"), required=False
    )
159

Krophil's avatar
Krophil committed
160

161
class ForumEditView(CanEditPropMixin, UpdateView):
Skia's avatar
Skia committed
162
163
    model = Forum
    pk_url_kwarg = "forum_id"
164
    form_class = ForumEditForm
Skia's avatar
Skia committed
165
    template_name = "core/edit.jinja"
Sli's avatar
Sli committed
166
    success_url = reverse_lazy("forum:main")
Skia's avatar
Skia committed
167

168
169
    def form_valid(self, form):
        ret = super(ForumEditView, self).form_valid(form)
Sli's avatar
Sli committed
170
        if form.cleaned_data["recursive"]:
171
172
173
            self.object.apply_rights_recursively()
        return ret

Krophil's avatar
Krophil committed
174

175
176
177
178
class ForumDeleteView(CanEditPropMixin, DeleteView):
    model = Forum
    pk_url_kwarg = "forum_id"
    template_name = "core/delete_confirm.jinja"
Sli's avatar
Sli committed
179
    success_url = reverse_lazy("forum:main")
180

Krophil's avatar
Krophil committed
181

182
class ForumDetailView(CanViewMixin, DetailView):
Skia's avatar
Skia committed
183
184
185
186
    model = Forum
    template_name = "forum/forum.jinja"
    pk_url_kwarg = "forum_id"

Skia's avatar
Skia committed
187
188
    def get_context_data(self, **kwargs):
        kwargs = super(ForumDetailView, self).get_context_data(**kwargs)
Sli's avatar
Sli committed
189
190
191
        qs = (
            self.object.topics.order_by("-_last_message__date")
            .select_related("_last_message__author", "author")
Krophil's avatar
Krophil committed
192
            .prefetch_related("forum__edit_groups")
Sli's avatar
Sli committed
193
194
195
        )
        paginator = Paginator(qs, settings.SITH_FORUM_PAGE_LENGTH)
        page = self.request.GET.get("topic_page")
Skia's avatar
Skia committed
196
197
198
199
200
201
        try:
            kwargs["topics"] = paginator.page(page)
        except PageNotAnInteger:
            kwargs["topics"] = paginator.page(1)
        except EmptyPage:
            kwargs["topics"] = paginator.page(paginator.num_pages)
Skia's avatar
Skia committed
202
203
        return kwargs

Krophil's avatar
Krophil committed
204

205
206
207
class TopicForm(forms.ModelForm):
    class Meta:
        model = ForumMessage
Sli's avatar
Sli committed
208
209
210
        fields = ["title", "message"]
        widgets = {"message": MarkdownInput}

Skia's avatar
Skia committed
211
    title = forms.CharField(required=True, label=_("Title"))
212

Krophil's avatar
Krophil committed
213

214
class ForumTopicCreateView(CanCreateMixin, CreateView):
Skia's avatar
Skia committed
215
    model = ForumMessage
216
    form_class = TopicForm
Skia's avatar
Skia committed
217
    template_name = "forum/reply.jinja"
Skia's avatar
Skia committed
218
219

    def dispatch(self, request, *args, **kwargs):
Sli's avatar
Sli committed
220
221
222
        self.forum = get_object_or_404(
            Forum, id=self.kwargs["forum_id"], is_category=False
        )
Skia's avatar
Skia committed
223
224
225
226
227
        if not request.user.can_view(self.forum):
            raise PermissionDenied
        return super(ForumTopicCreateView, self).dispatch(request, *args, **kwargs)

    def form_valid(self, form):
Sli's avatar
Sli committed
228
229
230
        topic = ForumTopic(
            _title=form.instance.title, author=self.request.user, forum=self.forum
        )
Skia's avatar
Skia committed
231
232
        topic.save()
        form.instance.topic = topic
Skia's avatar
Skia committed
233
234
235
        form.instance.author = self.request.user
        return super(ForumTopicCreateView, self).form_valid(form)

Krophil's avatar
Krophil committed
236

Skia's avatar
Skia committed
237
class ForumTopicEditView(CanEditMixin, UpdateView):
Skia's avatar
Skia committed
238
    model = ForumTopic
Sli's avatar
Sli committed
239
    fields = ["forum"]
Skia's avatar
Skia committed
240
241
242
    pk_url_kwarg = "topic_id"
    template_name = "core/edit.jinja"

Sli's avatar
Sli committed
243

Skia's avatar
Skia committed
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
class ForumTopicSubscribeView(CanViewMixin, SingleObjectMixin, RedirectView):
    model = ForumTopic
    pk_url_kwarg = "topic_id"
    permanent = False

    def get(self, request, *args, **kwargs):
        self.object = self.get_object()
        if request.user in self.object.subscribed_users.all():
            self.object.subscribed_users.remove(request.user)
        else:
            self.object.subscribed_users.add(request.user)
        return super().get(request, *args, **kwargs)

    def get_redirect_url(self, *args, **kwargs):
        return self.object.get_absolute_url()

Krophil's avatar
Krophil committed
260

261
class ForumTopicDetailView(CanViewMixin, DetailView):
Skia's avatar
Skia committed
262
263
264
265
    model = ForumTopic
    pk_url_kwarg = "topic_id"
    template_name = "forum/topic.jinja"
    context_object_name = "topic"
Sli's avatar
Sli committed
266
    queryset = ForumTopic.objects.select_related("forum__parent")
Skia's avatar
Skia committed
267

Skia's avatar
Skia committed
268
269
270
    def get_context_data(self, **kwargs):
        kwargs = super(ForumTopicDetailView, self).get_context_data(**kwargs)
        try:
Skia's avatar
Skia committed
271
            msg = self.object.get_first_unread_message(self.request.user)
Sli's avatar
Sli committed
272
            kwargs["first_unread_message_id"] = msg.id
Skia's avatar
Skia committed
273
        except:
Sli's avatar
Sli committed
274
275
276
277
278
279
280
281
            kwargs["first_unread_message_id"] = float("inf")
        paginator = Paginator(
            self.object.messages.select_related("author__avatar_pict")
            .prefetch_related("topic__forum__edit_groups", "readers")
            .order_by("date"),
            settings.SITH_FORUM_PAGE_LENGTH,
        )
        page = self.request.GET.get("page")
Skia's avatar
Skia committed
282
        try:
283
284
285
286
287
            kwargs["msgs"] = paginator.page(page)
        except PageNotAnInteger:
            kwargs["msgs"] = paginator.page(1)
        except EmptyPage:
            kwargs["msgs"] = paginator.page(paginator.num_pages)
Skia's avatar
Skia committed
288
289
        return kwargs

Krophil's avatar
Krophil committed
290

291
292
293
294
295
296
297
298
299
class ForumMessageView(SingleObjectMixin, RedirectView):
    model = ForumMessage
    pk_url_kwarg = "message_id"
    permanent = False

    def get_redirect_url(self, *args, **kwargs):
        self.object = self.get_object()
        return self.object.get_url()

Krophil's avatar
Krophil committed
300

Skia's avatar
Skia committed
301
302
class ForumMessageEditView(CanEditMixin, UpdateView):
    model = ForumMessage
Sli's avatar
Sli committed
303
304
305
306
307
    form_class = forms.modelform_factory(
        model=ForumMessage,
        fields=["title", "message"],
        widgets={"message": MarkdownInput},
    )
308
    template_name = "forum/reply.jinja"
Skia's avatar
Skia committed
309
310
    pk_url_kwarg = "message_id"

Skia's avatar
Skia committed
311
    def form_valid(self, form):
Sli's avatar
Sli committed
312
313
314
        ForumMessageMeta(
            message=self.object, user=self.request.user, action="EDIT"
        ).save()
Skia's avatar
Skia committed
315
316
        return super(ForumMessageEditView, self).form_valid(form)

317
318
    def get_context_data(self, **kwargs):
        kwargs = super(ForumMessageEditView, self).get_context_data(**kwargs)
Sli's avatar
Sli committed
319
        kwargs["topic"] = self.object.topic
320
321
        return kwargs

Krophil's avatar
Krophil committed
322

Skia's avatar
Skia committed
323
324
325
326
327
328
class ForumMessageDeleteView(SingleObjectMixin, RedirectView):
    model = ForumMessage
    pk_url_kwarg = "message_id"
    permanent = False

    def get_redirect_url(self, *args, **kwargs):
Skia's avatar
Skia committed
329
        self.object = self.get_object()
Skia's avatar
Skia committed
330
        if self.object.can_be_moderated_by(self.request.user):
Sli's avatar
Sli committed
331
332
333
            ForumMessageMeta(
                message=self.object, user=self.request.user, action="DELETE"
            ).save()
Skia's avatar
Skia committed
334
335
        return self.object.get_absolute_url()

Krophil's avatar
Krophil committed
336

Skia's avatar
Skia committed
337
338
339
340
341
342
class ForumMessageUndeleteView(SingleObjectMixin, RedirectView):
    model = ForumMessage
    pk_url_kwarg = "message_id"
    permanent = False

    def get_redirect_url(self, *args, **kwargs):
Skia's avatar
Skia committed
343
        self.object = self.get_object()
Skia's avatar
Skia committed
344
        if self.object.can_be_moderated_by(self.request.user):
Sli's avatar
Sli committed
345
346
347
            ForumMessageMeta(
                message=self.object, user=self.request.user, action="UNDELETE"
            ).save()
Skia's avatar
Skia committed
348
349
        return self.object.get_absolute_url()

Krophil's avatar
Krophil committed
350

351
class ForumMessageCreateView(CanCreateMixin, CreateView):
Skia's avatar
Skia committed
352
    model = ForumMessage
Sli's avatar
Sli committed
353
354
355
356
357
    form_class = forms.modelform_factory(
        model=ForumMessage,
        fields=["title", "message"],
        widgets={"message": MarkdownInput},
    )
Skia's avatar
Skia committed
358
    template_name = "forum/reply.jinja"
Skia's avatar
Skia committed
359
360

    def dispatch(self, request, *args, **kwargs):
Sli's avatar
Sli committed
361
        self.topic = get_object_or_404(ForumTopic, id=self.kwargs["topic_id"])
Skia's avatar
Skia committed
362
363
364
365
366
367
368
        if not request.user.can_view(self.topic):
            raise PermissionDenied
        return super(ForumMessageCreateView, self).dispatch(request, *args, **kwargs)

    def get_initial(self):
        init = super(ForumMessageCreateView, self).get_initial()
        try:
Sli's avatar
Sli committed
369
370
371
372
373
374
375
376
377
378
379
380
            message = (
                ForumMessage.objects.select_related("author")
                .filter(id=self.request.GET["quote_id"])
                .first()
            )
            init["message"] = "> ##### %s\n" % (
                _("%(author)s said") % {"author": message.author.get_short_name()}
            )
            init["message"] += "\n".join(
                ["> " + line for line in message.message.split("\n")]
            )
            init["message"] += "\n\n"
Skia's avatar
Skia committed
381
382
        except Exception as e:
            print(repr(e))
Skia's avatar
Skia committed
383
384
385
386
387
388
389
        return init

    def form_valid(self, form):
        form.instance.topic = self.topic
        form.instance.author = self.request.user
        return super(ForumMessageCreateView, self).form_valid(form)

Skia's avatar
Skia committed
390
391
    def get_context_data(self, **kwargs):
        kwargs = super(ForumMessageCreateView, self).get_context_data(**kwargs)
Sli's avatar
Sli committed
392
        kwargs["topic"] = self.topic
Skia's avatar
Skia committed
393
        return kwargs