admin.py 8.7 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
4
from django.contrib import admin
from django.http import HttpResponseRedirect
5
from django.template.defaultfilters import date as _date
6
from django.urls import reverse
Thom Wiggers's avatar
Thom Wiggers committed
7
8
from django.utils import timezone
from django.utils.html import format_html
9
from django.utils.http import is_safe_url
Thom Wiggers's avatar
Thom Wiggers committed
10
from django.utils.translation import ugettext_lazy as _
11

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

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

20
def _do_next(request, response):
21
    """See DoNextModelAdmin"""
22
23
24
25
26
    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
27

28
29
30
31
32
33
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.
    """
34

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

    def response_change(self, request, obj):
        res = super().response_change(request, obj)
41
        return _do_next(request, res)
Thom Wiggers's avatar
Thom Wiggers committed
42
43
44


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

    radio_fields = {'type': admin.VERTICAL}

53
54
55
56
57
58
59
    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
60

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


Thom Wiggers's avatar
Thom Wiggers committed
68
69
@admin.register(models.Event)
class EventAdmin(DoNextModelAdmin):
70
    """Manage the events"""
71
    inlines = (RegistrationInformationFieldInline, PizzaEventInline,)
72
    fields = ('title', 'description', 'start', 'end', 'organiser', 'category',
73
              'registration_start', 'registration_end', 'cancel_deadline',
74
              'send_cancel_email', 'location', 'map_location', 'price', 'fine',
75
              'max_participants', 'no_registration_message', 'published')
76
    list_display = ('overview_link', 'event_date', 'registration_date',
77
78
                    'num_participants', 'organiser', 'category', 'published',
                    'edit_link')
Thom Wiggers's avatar
Thom Wiggers committed
79
    list_display_links = ('edit_link',)
80
    list_filter = ('start', 'published', 'category')
Thom Wiggers's avatar
Thom Wiggers committed
81
82
83
84
85
86
87
88
89
90
91
    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)

92
    def has_change_permission(self, request, event=None):
93
        """Only allow access to the change form if the user is an organiser"""
94
        if (event is not None and
95
                not services.is_organiser(request.member, event)):
96
            return False
97
98
        return super().has_change_permission(request, event)

99
    def event_date(self, obj):
100
        event_date = timezone.make_naive(obj.start)
101
102
103
104
        return _date(event_date, "l d b Y, G:i")
    event_date.short_description = _('Event Date')

    def registration_date(self, obj):
105
106
107
108
109
        if obj.registration_start is not None:
            start_date = timezone.make_naive(obj.registration_start)
        else:
            start_date = obj.registration_start

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

Thom Wiggers's avatar
Thom Wiggers committed
113
114
115
116
117
118
119
    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
120
               .exclude(date_cancelled__lt=timezone.now()).count())
Thom Wiggers's avatar
Thom Wiggers committed
121
122
123
124
125
126
        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):
127
        """Action to change the status of the event"""
128
        self._change_published(request, queryset, True)
Thom Wiggers's avatar
Thom Wiggers committed
129
130
131
    make_published.short_description = _('Publish selected events')

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

136
137
    @staticmethod
    def _change_published(request, queryset, published):
138
139
140
141
        if not request.user.is_superuser:
            queryset = queryset.filter(
                organiser__in=request.member.get_committees())
        queryset.update(published=published)
142

Thom Wiggers's avatar
Thom Wiggers committed
143
144
145
146
    def save_formset(self, request, form, formset, change):
        """Save formsets with their order"""
        formset.save()

147
148
149
150
        informationfield_forms = (
            x for x in formset.forms if
            isinstance(x, forms.RegistrationInformationFieldForm)
        )
Thom Wiggers's avatar
Thom Wiggers committed
151
152
        form.instance.set_registrationinformationfield_order([
            f.instance.pk
153
            for f in sorted(informationfield_forms,
Thom Wiggers's avatar
Thom Wiggers committed
154
155
156
157
158
159
                            key=lambda x: (x.cleaned_data['order'],
                                           x.instance.pk))
        ])
        form.instance.save()

    def formfield_for_dbfield(self, db_field, request, **kwargs):
160
        """Customise formfield for organiser"""
Thom Wiggers's avatar
Thom Wiggers committed
161
162
163
164
165
166
167
168
169
        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):
170
        """Customise the organiser formfield, limit the options"""
Thom Wiggers's avatar
Thom Wiggers committed
171
172
        if db_field.name == 'organiser':
            # Use custom queryset for organiser field
173
            # Only get the current active committees the user is a member of
174
175
176
            if not (request.user.is_superuser or
                    request.user.has_perm('events.override_organiser')):
                kwargs['queryset'] = request.member.get_committees()
177
178
179
180
181
182
183
184
185
186
            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
187
188
        return super().formfield_for_foreignkey(db_field, request, **kwargs)

189
190
191
192
193
    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
194
195
196
197
198
199

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

    def formfield_for_dbfield(self, db_field, request, **kwargs):
200
        """Customise the formfields of event and member"""
Thom Wiggers's avatar
Thom Wiggers committed
201
202
203
204
205
206
207
208
209
        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):
210
        """Customise the formfields of event and member"""
Thom Wiggers's avatar
Thom Wiggers committed
211
212
213
214
215
        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']))
216
        elif db_field.name == 'member':
217
            # Filter the queryset to current members only
218
            kwargs['queryset'] = Member.current_members.all()
Thom Wiggers's avatar
Thom Wiggers committed
219
        return super().formfield_for_foreignkey(db_field, request, **kwargs)