admin.py 9.87 KB
Newer Older
Thom Wiggers's avatar
Thom Wiggers committed
1
# -*- coding: utf-8 -*-
2
"""Registers admin interfaces for the events module"""
Thom Wiggers's avatar
Thom Wiggers committed
3
from django.contrib import admin
4
from django.db.models import Max, Min
Thom Wiggers's avatar
Thom Wiggers committed
5
from django.http import HttpResponseRedirect
6
from django.template.defaultfilters import date as _date
7
from django.urls import reverse
Thom Wiggers's avatar
Thom Wiggers committed
8
from django.utils import timezone
9
from django.utils.datetime_safe import date
Thom Wiggers's avatar
Thom Wiggers committed
10
from django.utils.html import format_html
11
from django.utils.http import is_safe_url
Thom Wiggers's avatar
Thom Wiggers committed
12
from django.utils.translation import ugettext_lazy as _
13

14
from activemembers.models import Committee
15
from events import services
Thom Wiggers's avatar
Thom Wiggers committed
16
from members.models import Member
17
from pizzas.models import PizzaEvent
18
from utils.snippets import datetime_to_lectureyear
19
from utils.translation import TranslatedModelAdmin
20
from . import forms, models
Thom Wiggers's avatar
Thom Wiggers committed
21

Sébastiaan Versteeg's avatar
Sébastiaan Versteeg committed
22

23
def _do_next(request, response):
24
    """See DoNextModelAdmin"""
25
26
27
28
29
    if 'next' in request.GET and is_safe_url(request.GET['next']):
        return HttpResponseRedirect(request.GET['next'])
    else:
        return response

Thom Wiggers's avatar
Thom Wiggers committed
30

31
32
33
34
35
36
class DoNextModelAdmin(TranslatedModelAdmin):
    """
    This class adds processing of a `next` parameter in the urls
    of the add and change admin forms. If it is set and safe this
    override will redirect the user to the provided url.
    """
37

Thom Wiggers's avatar
Thom Wiggers committed
38
39
    def response_add(self, request, obj):
        res = super().response_add(request, obj)
40
        return _do_next(request, res)
Thom Wiggers's avatar
Thom Wiggers committed
41
42
43

    def response_change(self, request, obj):
        res = super().response_change(request, obj)
44
        return _do_next(request, res)
Thom Wiggers's avatar
Thom Wiggers committed
45
46
47


class RegistrationInformationFieldInline(admin.StackedInline):
48
    """The inline for registration information fields in the Event admin"""
49
    form = forms.RegistrationInformationFieldForm
Thom Wiggers's avatar
Thom Wiggers committed
50
51
52
53
54
55
    extra = 0
    model = models.RegistrationInformationField
    ordering = ('_order',)

    radio_fields = {'type': admin.VERTICAL}

56
57
58
59
60
61
62
    def get_formset(self, request, obj=None, **kwargs):
        formset = super().get_formset(request, obj, **kwargs)
        if obj is not None:
            count = obj.registrationinformationfield_set.count()
            formset.form.declared_fields['order'].initial = count
        return formset

Thom Wiggers's avatar
Thom Wiggers committed
63

64
class PizzaEventInline(admin.StackedInline):
65
    """The inline for pizza events in the Event admin"""
66
67
68
69
70
    model = PizzaEvent
    extra = 0
    max_num = 1


71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
class LectureYearFilter(admin.SimpleListFilter):
    """Filter the events on those started or ended in a lecture year"""
    title = _('lecture year')
    parameter_name = 'lecture_year'

    def lookups(self, request, model_admin):
        objects_end = models.Event.objects.aggregate(Max('end'))
        objects_start = models.Event.objects.aggregate(Min('start'))

        if objects_end['end__max'] and objects_start['start__min']:
            year_end = datetime_to_lectureyear(objects_end['end__max'])
            year_start = datetime_to_lectureyear(objects_start['start__min'])

            return [(year, '{}-{}'.format(year, year+1))
                    for year in range(year_end, year_start-1, -1)]
        return []

    def queryset(self, request, queryset):
        if not self.value():
            return queryset

        year = int(self.value())
        year_start = date(year=year, month=9, day=1)
        year_end = date(year=year + 1, month=9, day=1)

        return queryset.filter(start__gte=year_start, end__lte=year_end)


Thom Wiggers's avatar
Thom Wiggers committed
99
100
@admin.register(models.Event)
class EventAdmin(DoNextModelAdmin):
101
    """Manage the events"""
102
    inlines = (RegistrationInformationFieldInline, PizzaEventInline,)
103
    fields = ('title', 'description', 'start', 'end', 'organiser', 'category',
104
              'registration_start', 'registration_end', 'cancel_deadline',
105
              'send_cancel_email', 'location', 'map_location', 'price', 'fine',
106
              'max_participants', 'no_registration_message', 'published')
107
    list_display = ('overview_link', 'event_date', 'registration_date',
108
109
                    'num_participants', 'organiser', 'category', 'published',
                    'edit_link')
Thom Wiggers's avatar
Thom Wiggers committed
110
    list_display_links = ('edit_link',)
111
    list_filter = (LectureYearFilter, 'start', 'published', 'category')
Thom Wiggers's avatar
Thom Wiggers committed
112
113
114
115
116
117
118
119
120
121
122
    actions = ('make_published', 'make_unpublished')
    date_hierarchy = 'start'
    search_fields = ('title', 'description')
    prepopulated_fields = {'map_location': ('location',)}

    def overview_link(self, obj):
        return format_html('<a href="{link}">{title}</a>',
                           link=reverse('events:admin-details',
                                        kwargs={'event_id': obj.pk}),
                           title=obj.title)

123
    def has_change_permission(self, request, event=None):
124
        """Only allow access to the change form if the user is an organiser"""
125
        if (event is not None and
126
                not services.is_organiser(request.member, event)):
127
            return False
128
129
        return super().has_change_permission(request, event)

130
    def event_date(self, obj):
131
        event_date = timezone.make_naive(obj.start)
132
133
134
135
        return _date(event_date, "l d b Y, G:i")
    event_date.short_description = _('Event Date')

    def registration_date(self, obj):
136
137
138
139
140
        if obj.registration_start is not None:
            start_date = timezone.make_naive(obj.registration_start)
        else:
            start_date = obj.registration_start

141
        return _date(start_date, "l d b Y, G:i")
Milan van Stiphout's avatar
Milan van Stiphout committed
142
    registration_date.short_description = _('Registration Start')
143

Thom Wiggers's avatar
Thom Wiggers committed
144
145
146
147
148
149
150
    def edit_link(self, obj):
        return _('Edit')
    edit_link.short_description = ''

    def num_participants(self, obj):
        """Pretty-print the number of participants"""
        num = (obj.registration_set
Joren Vrancken's avatar
Joren Vrancken committed
151
               .exclude(date_cancelled__lt=timezone.now()).count())
Thom Wiggers's avatar
Thom Wiggers committed
152
153
154
155
156
157
        if not obj.max_participants:
            return '{}/∞'.format(num)
        return '{}/{}'.format(num, obj.max_participants)
    num_participants.short_description = _('Number of participants')

    def make_published(self, request, queryset):
158
        """Action to change the status of the event"""
159
        self._change_published(request, queryset, True)
Thom Wiggers's avatar
Thom Wiggers committed
160
161
162
    make_published.short_description = _('Publish selected events')

    def make_unpublished(self, request, queryset):
163
        """Action to change the status of the event"""
164
        self._change_published(request, queryset, False)
Thom Wiggers's avatar
Thom Wiggers committed
165
166
    make_unpublished.short_description = _('Unpublish selected events')

167
168
    @staticmethod
    def _change_published(request, queryset, published):
169
170
171
172
        if not request.user.is_superuser:
            queryset = queryset.filter(
                organiser__in=request.member.get_committees())
        queryset.update(published=published)
173

Thom Wiggers's avatar
Thom Wiggers committed
174
175
176
177
    def save_formset(self, request, form, formset, change):
        """Save formsets with their order"""
        formset.save()

178
179
180
181
        informationfield_forms = (
            x for x in formset.forms if
            isinstance(x, forms.RegistrationInformationFieldForm)
        )
Thom Wiggers's avatar
Thom Wiggers committed
182
183
        form.instance.set_registrationinformationfield_order([
            f.instance.pk
184
            for f in sorted(informationfield_forms,
Thom Wiggers's avatar
Thom Wiggers committed
185
186
187
188
189
190
                            key=lambda x: (x.cleaned_data['order'],
                                           x.instance.pk))
        ])
        form.instance.save()

    def formfield_for_dbfield(self, db_field, request, **kwargs):
191
        """Customise formfield for organiser"""
Thom Wiggers's avatar
Thom Wiggers committed
192
193
194
195
196
197
198
199
200
        field = super().formfield_for_dbfield(db_field, request, **kwargs)
        if db_field.name == 'organiser':
            # Disable add/change/delete buttons
            field.widget.can_add_related = False
            field.widget.can_change_related = False
            field.widget.can_delete_related = False
        return field

    def formfield_for_foreignkey(self, db_field, request, **kwargs):
201
        """Customise the organiser formfield, limit the options"""
Thom Wiggers's avatar
Thom Wiggers committed
202
203
        if db_field.name == 'organiser':
            # Use custom queryset for organiser field
204
            # Only get the current active committees the user is a member of
205
206
207
            if not (request.user.is_superuser or
                    request.user.has_perm('events.override_organiser')):
                kwargs['queryset'] = request.member.get_committees()
208
209
210
211
212
213
214
215
216
217
            else:
                # Hide old boards and inactive committees for new events
                if 'add' in request.path:
                    kwargs['queryset'] = (
                        Committee.active_committees.all() |
                        Committee.unfiltered_objects
                        .exclude(board__is_board=False)
                        .exclude(until__lt=(timezone.now() -
                                 timezone.timedelta(weeks=1)))
                    )
Thom Wiggers's avatar
Thom Wiggers committed
218
219
        return super().formfield_for_foreignkey(db_field, request, **kwargs)

220
221
222
223
224
    def get_actions(self, request):
        actions = super(EventAdmin, self).get_actions(request)
        del actions['delete_selected']
        return actions

Thom Wiggers's avatar
Thom Wiggers committed
225
226
227
228
229
230

@admin.register(models.Registration)
class RegistrationAdmin(DoNextModelAdmin):
    """Custom admin for registrations"""

    def formfield_for_dbfield(self, db_field, request, **kwargs):
231
        """Customise the formfields of event and member"""
Thom Wiggers's avatar
Thom Wiggers committed
232
233
234
235
236
237
238
239
240
        field = super().formfield_for_dbfield(db_field, request, **kwargs)
        if db_field.name in ('event', 'member'):
            # Disable add/change/delete buttons
            field.widget.can_add_related = False
            field.widget.can_change_related = False
            field.widget.can_delete_related = False
        return field

    def formfield_for_foreignkey(self, db_field, request, **kwargs):
241
        """Customise the formfields of event and member"""
Thom Wiggers's avatar
Thom Wiggers committed
242
243
244
245
246
        if db_field.name == 'event':
            # allow to restrict event
            if request.GET.get('event_pk'):
                kwargs['queryset'] = models.Event.objects.filter(
                    pk=int(request.GET['event_pk']))
247
        elif db_field.name == 'member':
248
            # Filter the queryset to current members only
249
            kwargs['queryset'] = Member.current_members.all()
Thom Wiggers's avatar
Thom Wiggers committed
250
        return super().formfield_for_foreignkey(db_field, request, **kwargs)