Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
thalia
concrexit
Commits
dba85fa5
Verified
Commit
dba85fa5
authored
Nov 03, 2018
by
Sébastiaan Versteeg
Browse files
Refactor admin urls for event admin
parent
4594727b
Changes
16
Hide whitespace changes
Inline
Side-by-side
website/events/admin.py
View file @
dba85fa5
...
...
@@ -5,7 +5,7 @@ from django.core.exceptions import DisallowedRedirect
from
django.db.models
import
Max
,
Min
from
django.http
import
HttpResponseRedirect
from
django.template.defaultfilters
import
date
as
_date
from
django.urls
import
reverse
from
django.urls
import
reverse
,
path
from
django.utils
import
timezone
from
django.utils.datetime_safe
import
date
from
django.utils.html
import
format_html
...
...
@@ -19,6 +19,7 @@ from pizzas.models import PizzaEvent
from
utils.snippets
import
datetime_to_lectureyear
from
utils.translation
import
TranslatedModelAdmin
from
.
import
forms
,
models
import
events.admin_views
as
admin_views
def
_do_next
(
request
,
response
):
...
...
@@ -122,8 +123,8 @@ class EventAdmin(DoNextModelAdmin):
def
overview_link
(
self
,
obj
):
return
format_html
(
'<a href="{link}">{title}</a>'
,
link
=
reverse
(
'
events:admin-
details'
,
kwargs
=
{
'
event_id
'
:
obj
.
pk
}),
link
=
reverse
(
'
admin:events_event_
details'
,
kwargs
=
{
'
pk
'
:
obj
.
pk
}),
title
=
obj
.
title
)
def
has_change_permission
(
self
,
request
,
event
=
None
):
...
...
@@ -243,6 +244,28 @@ class EventAdmin(DoNextModelAdmin):
if
self
.
has_change_permission
(
request
,
obj
)
or
obj
is
None
:
yield
inline
.
get_formset
(
request
,
obj
),
inline
def
get_urls
(
self
):
urls
=
super
().
get_urls
()
custom_urls
=
[
path
(
'<int:pk>/details/'
,
self
.
admin_site
.
admin_view
(
admin_views
.
EventAdminDetails
.
as_view
()),
name
=
'events_event_details'
),
path
(
'<int:pk>/export/'
,
self
.
admin_site
.
admin_view
(
admin_views
.
EventRegistrationsExport
.
as_view
()),
name
=
'events_event_export'
),
path
(
'<int:pk>/export-email/'
,
self
.
admin_site
.
admin_view
(
admin_views
.
EventRegistrationEmailsExport
.
as_view
()),
name
=
'events_event_export_email'
),
path
(
'<int:pk>/all-present/'
,
self
.
admin_site
.
admin_view
(
admin_views
.
EventRegistrationsMarkPresent
.
as_view
()),
name
=
'events_event_all_present'
),
]
return
custom_urls
+
urls
@
admin
.
register
(
models
.
Registration
)
class
RegistrationAdmin
(
DoNextModelAdmin
):
...
...
website/events/admin_views.py
View file @
dba85fa5
import
csv
import
json
from
django.contrib.admin.views.decorators
import
staff_member_required
from
django.
contrib.auth.decorators
import
permission_requ
ire
d
from
django.
http
import
HttpResponse
,
HttpResponseRedirect
,
JsonResponse
from
django.
shortcut
s
import
get_object_or_404
,
render
from
django.
http
import
HttpResponse
,
HttpResponseRed
ire
ct
from
django.
shortcuts
import
get_object_or_404
from
django.
url
s
import
reverse
from
django.utils
import
timezone
from
django.utils.decorators
import
method_decorator
from
django.utils.text
import
slugify
from
django.utils.translation
import
pgettext_lazy
from
django.utils.translation
import
ugettext_lazy
as
_
from
django.views.decorators.http
import
require_http_methods
from
django.views
import
View
from
django.views.generic
import
DetailView
,
TemplateView
from
events.decorators
import
organiser_only
from
.models
import
Event
,
Registration
@
staff_member_required
@
permission_required
(
'events.change_event'
)
@
organiser_only
def
details
(
request
,
event_id
):
@
method_decorator
([
staff_member_required
,
],
name
=
'dispatch'
)
@
method_decorator
(
organiser_only
,
name
=
'dispatch'
)
class
EventAdminDetails
(
DetailView
):
"""
Renders an overview of registration for the specified event
:param request: the request object
:param event_id: the primary key of the event
:return: HttpResponse 200 with the page HTML
Renders an overview of registrations for the specified event
"""
event
=
get_object_or_404
(
Event
,
pk
=
event_id
)
return
render
(
request
,
'events/admin/details.html'
,
{
'event'
:
event
,
})
template_name
=
'events/admin/details.html'
model
=
Event
queryset
=
Event
.
objects
.
filter
(
published
=
True
)
context_object_name
=
'event'
@
staff_member_required
@
permission_required
(
'events.change_event'
)
@
organiser_only
@
require_http_methods
([
"POST"
])
def
change_registration
(
request
,
event_id
,
action
=
None
):
"""
JSON call to change the status of a registration
:param request: the request object
:param event_id: the primary key of the event
:param action: specifies what should be changed
:return: JsonResponse with a success status
"""
data
=
{
'success'
:
True
}
try
:
id
=
request
.
POST
.
get
(
"id"
,
-
1
)
obj
=
Registration
.
objects
.
get
(
event
=
event_id
,
pk
=
id
)
if
action
==
'present'
:
checked
=
json
.
loads
(
request
.
POST
.
get
(
"checked"
))
if
checked
is
not
None
:
obj
.
present
=
checked
obj
.
save
()
elif
action
==
'payment'
:
value
=
request
.
POST
.
get
(
"value"
)
if
value
is
not
None
:
obj
.
payment
=
value
obj
.
save
()
except
Registration
.
DoesNotExist
:
data
[
'success'
]
=
False
return
JsonResponse
(
data
)
@
staff_member_required
@
permission_required
(
'events.change_event'
)
def
export
(
request
,
event_id
):
@
method_decorator
([
staff_member_required
,
],
name
=
'dispatch'
)
@
method_decorator
(
organiser_only
,
name
=
'dispatch'
)
class
EventRegistrationsExport
(
View
):
"""
Export the registration of a specified event
:param request: the request object
:param event_id: the primary key of the event
:return: A CSV containing all registrations for the event
View to export registrations
"""
event
=
get_object_or_404
(
Event
,
pk
=
event_id
)
extra_fields
=
event
.
registrationinformationfield_set
.
all
()
registrations
=
event
.
registration_set
.
all
()
header_fields
=
(
[
_
(
'Name'
),
_
(
'Email'
),
_
(
'Paid'
),
_
(
'Present'
),
_
(
'Status'
),
_
(
'Phone number'
)]
+
[
field
.
name
for
field
in
extra_fields
]
+
[
_
(
'Date'
),
_
(
'Date cancelled'
)])
rows
=
[]
if
event
.
price
==
0
:
header_fields
.
remove
(
_
(
'Paid'
))
for
i
,
registration
in
enumerate
(
registrations
):
if
registration
.
member
:
name
=
registration
.
member
.
get_full_name
()
else
:
name
=
registration
.
name
status
=
pgettext_lazy
(
'registration status'
,
'registered'
).
capitalize
()
cancelled
=
None
if
registration
.
date_cancelled
:
if
registration
.
is_late_cancellation
():
status
=
pgettext_lazy
(
'registration status'
,
'late cancellation'
).
capitalize
()
else
:
status
=
pgettext_lazy
(
'registration status'
,
'cancelled'
).
capitalize
()
cancelled
=
timezone
.
localtime
(
registration
.
date_cancelled
)
elif
registration
.
queue_position
:
status
=
pgettext_lazy
(
'registration status'
,
'waiting'
)
data
=
{
_
(
'Name'
):
name
,
_
(
'Date'
):
timezone
.
localtime
(
registration
.
date
),
_
(
'Present'
):
_
(
'Yes'
)
if
registration
.
present
else
''
,
_
(
'Phone number'
):
(
registration
.
member
.
profile
.
phone_number
if
registration
.
member
else
''
),
_
(
'Email'
):
(
registration
.
member
.
email
if
registration
.
member
else
''
),
_
(
'Status'
):
status
,
_
(
'Date cancelled'
):
cancelled
,
}
if
event
.
price
>
0
:
if
registration
.
payment
==
registration
.
PAYMENT_CASH
:
data
[
_
(
'Paid'
)]
=
_
(
'Cash'
)
elif
registration
.
payment
==
registration
.
PAYMENT_CARD
:
data
[
_
(
'Paid'
)]
=
_
(
'Pin'
)
template_name
=
'events/admin/details.html'
def
get
(
self
,
request
,
pk
):
"""
Export the registration of a specified event
:param request: the request object
:param pk: the primary key of the event
:return: A CSV containing all registrations for the event
"""
event
=
get_object_or_404
(
Event
,
pk
=
pk
)
extra_fields
=
event
.
registrationinformationfield_set
.
all
()
registrations
=
event
.
registration_set
.
all
()
header_fields
=
(
[
_
(
'Name'
),
_
(
'Email'
),
_
(
'Paid'
),
_
(
'Present'
),
_
(
'Status'
),
_
(
'Phone number'
)]
+
[
field
.
name
for
field
in
extra_fields
]
+
[
_
(
'Date'
),
_
(
'Date cancelled'
)])
rows
=
[]
if
event
.
price
==
0
:
header_fields
.
remove
(
_
(
'Paid'
))
for
i
,
registration
in
enumerate
(
registrations
):
if
registration
.
member
:
name
=
registration
.
member
.
get_full_name
()
else
:
data
[
_
(
'Paid'
)]
=
_
(
'No'
)
data
.
update
({
field
[
'field'
].
name
:
field
[
'value'
]
for
field
in
registration
.
information_fields
})
rows
.
append
(
data
)
response
=
HttpResponse
(
content_type
=
'text/csv'
)
writer
=
csv
.
DictWriter
(
response
,
header_fields
)
writer
.
writeheader
()
rows
=
sorted
(
rows
,
key
=
lambda
row
:
(
row
[
_
(
'Status'
)]
==
pgettext_lazy
(
'registration status'
,
'late cancellation'
).
capitalize
(),
row
[
_
(
'Date'
)]),
reverse
=
True
,
)
for
row
in
rows
:
writer
.
writerow
(
row
)
response
[
'Content-Disposition'
]
=
(
'attachment; filename="{}.csv"'
.
format
(
slugify
(
event
.
title
)))
return
response
@
staff_member_required
@
permission_required
(
'events.change_event'
)
def
export_email
(
request
,
event_id
):
name
=
registration
.
name
status
=
pgettext_lazy
(
'registration status'
,
'registered'
).
capitalize
()
cancelled
=
None
if
registration
.
date_cancelled
:
if
registration
.
is_late_cancellation
():
status
=
pgettext_lazy
(
'registration status'
,
'late cancellation'
).
capitalize
()
else
:
status
=
pgettext_lazy
(
'registration status'
,
'cancelled'
).
capitalize
()
cancelled
=
timezone
.
localtime
(
registration
.
date_cancelled
)
elif
registration
.
queue_position
:
status
=
pgettext_lazy
(
'registration status'
,
'waiting'
)
data
=
{
_
(
'Name'
):
name
,
_
(
'Date'
):
timezone
.
localtime
(
registration
.
date
),
_
(
'Present'
):
_
(
'Yes'
)
if
registration
.
present
else
''
,
_
(
'Phone number'
):
(
registration
.
member
.
profile
.
phone_number
if
registration
.
member
else
''
),
_
(
'Email'
):
(
registration
.
member
.
email
if
registration
.
member
else
''
),
_
(
'Status'
):
status
,
_
(
'Date cancelled'
):
cancelled
,
}
if
event
.
price
>
0
:
if
registration
.
payment
==
registration
.
PAYMENT_CASH
:
data
[
_
(
'Paid'
)]
=
_
(
'Cash'
)
elif
registration
.
payment
==
registration
.
PAYMENT_CARD
:
data
[
_
(
'Paid'
)]
=
_
(
'Pin'
)
else
:
data
[
_
(
'Paid'
)]
=
_
(
'No'
)
data
.
update
({
field
[
'field'
].
name
:
field
[
'value'
]
for
field
in
registration
.
information_fields
})
rows
.
append
(
data
)
response
=
HttpResponse
(
content_type
=
'text/csv'
)
writer
=
csv
.
DictWriter
(
response
,
header_fields
)
writer
.
writeheader
()
rows
=
sorted
(
rows
,
key
=
lambda
row
:
(
row
[
_
(
'Status'
)]
==
pgettext_lazy
(
'registration status'
,
'late cancellation'
).
capitalize
(),
row
[
_
(
'Date'
)]),
reverse
=
True
,
)
for
row
in
rows
:
writer
.
writerow
(
row
)
response
[
'Content-Disposition'
]
=
(
'attachment; filename="{}.csv"'
.
format
(
slugify
(
event
.
title
)))
return
response
@
method_decorator
([
staff_member_required
,
],
name
=
'dispatch'
)
@
method_decorator
(
organiser_only
,
name
=
'dispatch'
)
class
EventRegistrationEmailsExport
(
TemplateView
):
"""
Renders a page that outputs all email addresses of registered members
for an event
"""
template_name
=
'events/admin/email_export.html'
:param request: the request object
:param event_id: the primary key of the event
:return: HttpResponse 200 with the HTML of the page
def
get_context_data
(
self
,
**
kwargs
):
context
=
super
().
get_context_data
(
**
kwargs
)
event
=
get_object_or_404
(
Event
,
pk
=
kwargs
[
'pk'
])
registrations
=
event
.
registration_set
.
filter
(
date_cancelled
=
None
)
registrations
=
registrations
[:
event
.
max_participants
]
addresses
=
[
r
.
member
.
email
for
r
in
registrations
if
r
.
member
]
no_addresses
=
[
r
.
name
for
r
in
registrations
if
not
r
.
member
]
context
[
'event'
]
=
event
context
[
'addresses'
]
=
addresses
context
[
'no_addresses'
]
=
no_addresses
return
context
@
method_decorator
([
staff_member_required
,
],
name
=
'dispatch'
)
@
method_decorator
(
organiser_only
,
name
=
'dispatch'
)
class
EventRegistrationsMarkPresent
(
View
):
"""
event
=
get_object_or_404
(
Event
,
pk
=
event_id
)
registrations
=
event
.
registration_set
.
filter
(
date_cancelled
=
None
)
registrations
=
registrations
[:
event
.
max_participants
]
addresses
=
[
r
.
member
.
email
for
r
in
registrations
if
r
.
member
]
no_addresses
=
[
r
.
name
for
r
in
registrations
if
not
r
.
member
]
return
render
(
request
,
'events/admin/email_export.html'
,
{
'event'
:
event
,
'addresses'
:
addresses
,
'no_addresses'
:
no_addresses
})
@
staff_member_required
@
permission_required
(
'events.change_event'
)
@
organiser_only
def
all_present
(
request
,
event_id
):
Renders a page that outputs all email addresses of registered members
for an event
"""
Mark all registrations of an event as present
template_name
=
'events/admin/email_export.html'
:param request: the request object
:param event_id: the primary key of the event
:return: HttpResponse 302 to the event admin page
"""
event
=
get_object_or_404
(
Event
,
pk
=
event_id
)
def
get
(
self
,
request
,
pk
):
"""
Mark all registrations of an event as present
if
event
.
max_participants
is
None
:
registrations_query
=
event
.
registration_set
.
filter
(
date_cancelled
=
None
)
else
:
registrations_query
=
(
event
.
registration_set
.
filter
(
date_cancelled
=
None
)
.
order_by
(
'date'
)[:
event
.
max_participants
])
:param request: the request object
:param pk: the primary key of the event
:return: HttpResponse 302 to the event admin page
"""
event
=
get_object_or_404
(
Event
,
pk
=
pk
)
if
event
.
max_participants
is
None
:
registrations_query
=
event
.
registration_set
.
filter
(
date_cancelled
=
None
)
else
:
registrations_query
=
(
event
.
registration_set
.
filter
(
date_cancelled
=
None
)
.
order_by
(
'date'
)[:
event
.
max_participants
])
event
.
registration_set
.
filter
(
pk__in
=
registrations_query
).
update
(
present
=
True
,
payment
=
Registration
.
PAYMENT_CASH
)
event
.
registration_set
.
filter
(
pk__in
=
registrations_query
).
update
(
present
=
True
,
payment
=
Registration
.
PAYMENT_CASH
)
return
HttpResponseRedirect
(
'/events/admin/{}'
.
format
(
event_id
))
return
HttpResponseRedirect
(
reverse
(
'admin:events_event_details'
,
args
=
[
str
(
event
.
pk
)]))
website/events/api/serializers.py
View file @
dba85fa5
...
...
@@ -100,8 +100,8 @@ class UnpublishedEventSerializer(CalenderJSSerializer):
return
"black"
def
_url
(
self
,
instance
):
return
reverse
(
'
events:admin-
details'
,
kwargs
=
{
'
event_id
'
:
instance
.
id
})
return
reverse
(
'
admin:events_events_
details'
,
kwargs
=
{
'
pk
'
:
instance
.
id
})
class
EventRetrieveSerializer
(
serializers
.
ModelSerializer
):
...
...
@@ -222,7 +222,8 @@ class RegistrationListSerializer(serializers.ModelSerializer):
class
Meta
:
model
=
Registration
fields
=
(
'pk'
,
'member'
,
'name'
,
'photo'
,
'avatar'
,
'registered_on'
,
'is_late_cancellation'
,
'is_cancelled'
,
'queue_position'
)
'is_late_cancellation'
,
'is_cancelled'
,
'queue_position'
,
'payment'
,
'present'
)
name
=
serializers
.
SerializerMethodField
(
'_name'
)
photo
=
serializers
.
SerializerMethodField
(
'_photo'
)
...
...
@@ -282,7 +283,8 @@ class RegistrationSerializer(serializers.ModelSerializer):
model
=
Registration
fields
=
(
'pk'
,
'member'
,
'name'
,
'photo'
,
'avatar'
,
'registered_on'
,
'is_late_cancellation'
,
'is_cancelled'
,
'queue_position'
,
'fields'
)
'queue_position'
,
'fields'
,
'payment'
,
'present'
)
name
=
serializers
.
SerializerMethodField
(
'_name'
)
photo
=
serializers
.
SerializerMethodField
(
'_photo'
)
...
...
@@ -340,6 +342,7 @@ class RegistrationSerializer(serializers.ModelSerializer):
try
:
if
instance
:
self
.
information_fields
=
services
.
registration_fields
(
kwargs
[
'context'
][
'request'
],
instance
.
member
,
instance
.
event
)
except
RegistrationError
:
pass
...
...
website/events/api/viewsets.py
View file @
dba85fa5
...
...
@@ -193,6 +193,7 @@ class RegistrationViewSet(GenericViewSet, RetrieveModelMixin,
registration
.
event
,
serializer
.
field_values
())
serializer
.
information_fields
=
services
.
registration_fields
(
serializer
.
context
[
'request'
],
registration
.
member
,
registration
.
event
)
def
destroy
(
self
,
request
,
pk
=
None
,
**
kwargs
):
...
...
website/events/decorators.py
View file @
dba85fa5
...
...
@@ -13,7 +13,7 @@ def organiser_only(view_function):
class
OrganiserOnly
(
object
):
"""
Decorator that denies access to the page if:
1. There is no `
event_id
` in the request
1. There is no `
pk
` in the request
2. The specified event does not exist
3. The user is no organiser of the specified event
"""
...
...
@@ -21,11 +21,11 @@ class OrganiserOnly(object):
self
.
view_function
=
view_function
def
__call__
(
self
,
request
,
*
args
,
**
kwargs
):
event_id
=
kwargs
.
get
(
'
event_id
'
)
if
event_id
:
pk
=
kwargs
.
get
(
'
pk
'
)
if
pk
:
event
=
None
try
:
event
=
Event
.
objects
.
get
(
pk
=
event_id
)
event
=
Event
.
objects
.
get
(
pk
=
pk
)
except
Event
.
DoesNotExist
:
pass
...
...
website/events/services.py
View file @
dba85fa5
...
...
@@ -193,7 +193,7 @@ def update_registration(member, event, field_values):
field
.
set_value_for
(
registration
,
field_value
)
def
registration_fields
(
member
,
event
):
def
registration_fields
(
request
,
member
,
event
):
"""
Returns information about the registration fields of a registration
...
...
@@ -210,8 +210,9 @@ def registration_fields(member, event):
raise
RegistrationError
(
_
(
"You are not registered for this event."
))
from
error
if
(
event_permissions
(
member
,
event
)[
"update_registration"
]
and
registration
):
perms
=
(
event_permissions
(
member
,
event
)[
"update_registration"
]
or
is_organiser
(
request
.
member
,
event
))
if
perms
and
registration
:
information_fields
=
registration
.
information_fields
fields
=
OrderedDict
()
...
...
website/events/static/events/css/admin.scss
View file @
dba85fa5
.dashboard
#content
{
width
:
auto
;
}
.dashboard
#content
.results
{
overflow-y
:
auto
;
}
.dashboard
{
#content
{
width
:
auto
;
.results
{
overflow-y
:
auto
;
}
}
.dashboard
.module
table
td
a
{
display
:
inline-block
;
vertical-align
:
middle
;
.module
table
td
a
{
display
:
inline-block
;
vertical-align
:
middle
;
&
.member-phone
,
&
.member-email
{
width
:
16px
;
height
:
16px
;
}
&
.member-phone
,
&
.member-email
{
width
:
16px
;
height
:
16px
;
}
&
.member-phone
{
background
:
url('../images/phone-square.svg')
no-repeat
;
padding-right
:
2px
;
}
&
.member-phone
{
background
:
url('../images/phone-square.svg')
no-repeat
;
padding-right
:
2px
;
}
&
.member-email
{
background
:
url('../images/envelope-square.svg')
no-repeat
;
&
.member-email
{
background
:
url('../images/envelope-square.svg')
no-repeat
;
}
}
}
...
...
website/events/static/events/js/admin.js
View file @
dba85fa5
django
.
jQuery
(
function
()
{
var
$
=
django
.
jQuery
;
var
url
=
$
(
"
#content-main
"
).
attr
(
"
data-url
"
);
var
payment_url
=
url
+
"
payment/
"
;
var
present_url
=
url
+
"
present/
"
;
$
(
"
.present-check
"
).
change
(
function
()
{
var
checkbox
=
$
(
this
);
var
id
=
checkbox
.
attr
(
"
data-id
"
);
var
url
=
checkbox
.
parent
().
parent
().
data
(
"
url
"
);
var
checked
=
checkbox
.
prop
(
'
checked
'
);
post
(
present_url
,
{
checked
:
checked
,
id
:
id
},
function
(
result
)
{
if
(
!
result
.
success
)
{
checkbox
.
prop
(
'
checked
'
,
!
checked
);
}
patch
(
url
,
{
present
:
checked
},
function
(
result
)
{
checkbox
.
prop
(
'
checked
'
,
result
.
present
);
$
(
"
table
"
).
trigger
(
"
update
"
);
},
function
()
{
checkbox
.
prop
(
'
checked
'
,
!
checked
);
...
...
@@ -21,13 +15,11 @@ django.jQuery(function () {
$
(
"
.payment-radio
"
).
change
(
function
()
{
var
radiobutton
=
$
(
this
);
var
id
=
radiobutton
.
attr
(
"
data-id
"
);
var
value
=
radiobutton
.
at
tr
(
"
data-
value
"
);
var
url
=
radiobutton
.
parent
().
parent
().
data
(
"
url
"
);
var
value
=
radiobutton
.
d
at
a
(
"
value
"
);
if
(
radiobutton
.
prop
(
'
checked
'
))
{
post
(
payment_url
,
{
value
:
value
,
id
:
id
},
function
(
result
)
{
if
(
!
result
.
success
)
{
radiobutton
.
prop
(
'
checked
'
,
!
checked
);
}
patch
(
url
,
{
payment
:
value
},
function
(
result
)
{
radiobutton
.
prop
(
'
checked
'
,
value
===
result
.
payment
);
$
(
"
table
"
).
trigger
(
"
update
"
);
},
function
()
{
radiobutton
.
prop
(
'
checked
'
,
!
checked
);
...
...
@@ -74,30 +66,14 @@ django.jQuery(function () {
});
});