Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move 'Grant summary' under the Conference admin #3715

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions backend/conferences/admin/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from .conference import (
ConferenceAdmin, # noqa
KeynoteAdmin, # noqa
DeadlineAdmin, # noqa
TopicAdmin, # noqa
AudienceLevelAdmin, # noqa
) # noqa
69 changes: 69 additions & 0 deletions backend/conferences/admin/actions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
from django.contrib import admin, messages
from conferences.models import SpeakerVoucher
from pretix import create_voucher
from schedule.tasks import send_speaker_voucher_email

Check warning on line 4 in backend/conferences/admin/actions.py

View check run for this annotation

Codecov / codecov/patch

backend/conferences/admin/actions.py#L1-L4

Added lines #L1 - L4 were not covered by tests


@admin.action(description="Send voucher via email")

Check warning on line 7 in backend/conferences/admin/actions.py

View check run for this annotation

Codecov / codecov/patch

backend/conferences/admin/actions.py#L7

Added line #L7 was not covered by tests
def send_voucher_via_email(modeladmin, request, queryset):
is_filtered_by_conference = (

Check warning on line 9 in backend/conferences/admin/actions.py

View check run for this annotation

Codecov / codecov/patch

backend/conferences/admin/actions.py#L9

Added line #L9 was not covered by tests
queryset.values_list("conference_id").distinct().count() == 1
)

if not is_filtered_by_conference:
messages.error(request, "Please select only one conference")
return

Check warning on line 15 in backend/conferences/admin/actions.py

View check run for this annotation

Codecov / codecov/patch

backend/conferences/admin/actions.py#L14-L15

Added lines #L14 - L15 were not covered by tests

count = 0

Check warning on line 17 in backend/conferences/admin/actions.py

View check run for this annotation

Codecov / codecov/patch

backend/conferences/admin/actions.py#L17

Added line #L17 was not covered by tests
for speaker_voucher in queryset.filter(pretix_voucher_id__isnull=False):
send_speaker_voucher_email.delay(speaker_voucher_id=speaker_voucher.id)
count = count + 1

Check warning on line 20 in backend/conferences/admin/actions.py

View check run for this annotation

Codecov / codecov/patch

backend/conferences/admin/actions.py#L19-L20

Added lines #L19 - L20 were not covered by tests

messages.success(request, f"{count} Voucher emails scheduled!")

Check warning on line 22 in backend/conferences/admin/actions.py

View check run for this annotation

Codecov / codecov/patch

backend/conferences/admin/actions.py#L22

Added line #L22 was not covered by tests


@admin.action(description="Create speaker vouchers on Pretix")

Check warning on line 25 in backend/conferences/admin/actions.py

View check run for this annotation

Codecov / codecov/patch

backend/conferences/admin/actions.py#L25

Added line #L25 was not covered by tests
def create_speaker_vouchers_on_pretix(modeladmin, request, queryset):
is_filtered_by_conference = (

Check warning on line 27 in backend/conferences/admin/actions.py

View check run for this annotation

Codecov / codecov/patch

backend/conferences/admin/actions.py#L27

Added line #L27 was not covered by tests
queryset.values_list("conference_id").distinct().count() == 1
)

if not is_filtered_by_conference:
messages.error(request, "Please select only one conference")
return

Check warning on line 33 in backend/conferences/admin/actions.py

View check run for this annotation

Codecov / codecov/patch

backend/conferences/admin/actions.py#L32-L33

Added lines #L32 - L33 were not covered by tests

conference = queryset.only("conference_id").first().conference

Check warning on line 35 in backend/conferences/admin/actions.py

View check run for this annotation

Codecov / codecov/patch

backend/conferences/admin/actions.py#L35

Added line #L35 was not covered by tests

if not conference.pretix_speaker_voucher_quota_id:
messages.error(

Check warning on line 38 in backend/conferences/admin/actions.py

View check run for this annotation

Codecov / codecov/patch

backend/conferences/admin/actions.py#L38

Added line #L38 was not covered by tests
request,
"Please configure the speaker voucher quota ID in the conference settings",
)
return

Check warning on line 42 in backend/conferences/admin/actions.py

View check run for this annotation

Codecov / codecov/patch

backend/conferences/admin/actions.py#L42

Added line #L42 was not covered by tests

count = 0

Check warning on line 44 in backend/conferences/admin/actions.py

View check run for this annotation

Codecov / codecov/patch

backend/conferences/admin/actions.py#L44

Added line #L44 was not covered by tests

for speaker_voucher in queryset.filter(pretix_voucher_id__isnull=True):
if speaker_voucher.voucher_type == SpeakerVoucher.VoucherType.SPEAKER:
price_mode = "set"
value = "0.00"

Check warning on line 49 in backend/conferences/admin/actions.py

View check run for this annotation

Codecov / codecov/patch

backend/conferences/admin/actions.py#L48-L49

Added lines #L48 - L49 were not covered by tests
elif speaker_voucher.voucher_type == SpeakerVoucher.VoucherType.CO_SPEAKER:
price_mode = "percent"
value = "25.00"

Check warning on line 52 in backend/conferences/admin/actions.py

View check run for this annotation

Codecov / codecov/patch

backend/conferences/admin/actions.py#L51-L52

Added lines #L51 - L52 were not covered by tests

pretix_voucher = create_voucher(

Check warning on line 54 in backend/conferences/admin/actions.py

View check run for this annotation

Codecov / codecov/patch

backend/conferences/admin/actions.py#L54

Added line #L54 was not covered by tests
conference=speaker_voucher.conference,
code=speaker_voucher.voucher_code,
comment=f"Voucher for user_id={speaker_voucher.user_id}",
tag="speakers",
quota_id=speaker_voucher.conference.pretix_speaker_voucher_quota_id,
price_mode=price_mode,
value=value,
)

pretix_voucher_id = pretix_voucher["id"]
speaker_voucher.pretix_voucher_id = pretix_voucher_id
speaker_voucher.save()
count = count + 1

Check warning on line 67 in backend/conferences/admin/actions.py

View check run for this annotation

Codecov / codecov/patch

backend/conferences/admin/actions.py#L64-L67

Added lines #L64 - L67 were not covered by tests

messages.success(request, f"{count} Vouchers created on Pretix!")

Check warning on line 69 in backend/conferences/admin/actions.py

View check run for this annotation

Codecov / codecov/patch

backend/conferences/admin/actions.py#L69

Added line #L69 was not covered by tests
265 changes: 38 additions & 227 deletions backend/conferences/admin.py → backend/conferences/admin/conference.py
Original file line number Diff line number Diff line change
@@ -1,101 +1,48 @@
from pathlib import Path
from django.core.files.storage import storages
from django import forms
from django.contrib import admin, messages
from django.core import exceptions
from django.core.cache import cache
from django.forms import BaseInlineFormSet
from django.forms.models import ModelForm
from django.shortcuts import redirect, render
from django.urls import path, reverse
from django.utils.translation import gettext_lazy as _
from ordered_model.admin import (
OrderedInlineModelAdminMixin,
OrderedModelAdmin,
OrderedStackedInline,
OrderedTabularInline,
)
from itertools import permutations
from unicodedata import normalize
from conferences.models import SpeakerVoucher
from pretix import create_voucher
from schedule.models import ScheduleItem
from schedule.tasks import send_speaker_voucher_email
from sponsors.models import SponsorLevel
from voting.models import IncludedEvent
import re
from .models import (
from conferences.models import (
AudienceLevel,
Conference,
Deadline,
Duration,
Keynote,
KeynoteSpeaker,
Topic,
)
from .inlines import (
DeadlineInline,
DurationInline,
SponsorLevelInline,
IncludedEventInline,
)
from .utils import cleanup_string
from .forms import KeynoteSpeakerForm
from grants.admin.views import summary_view


def validate_deadlines_form(forms):
existing_types = set()
for form in forms:
if not form.cleaned_data:
return

start = form.cleaned_data["start"]
end = form.cleaned_data["end"]
delete = form.cleaned_data["DELETE"]

if start > end:
raise exceptions.ValidationError(_("Start date cannot be after end"))

type = form.cleaned_data["type"]
def walk_conference_videos_folder(storage, base_path):
folders, files = storage.listdir(base_path)

Check warning on line 35 in backend/conferences/admin/conference.py

View check run for this annotation

Codecov / codecov/patch

backend/conferences/admin/conference.py#L35

Added line #L35 was not covered by tests
all_files = [f"{base_path}{file_}" for file_ in files]

if type == Deadline.TYPES.custom or delete:
for folder in folders:
if not folder:
continue

if type in existing_types:
raise exceptions.ValidationError(
_("You can only have one deadline of type %(type)s") % {"type": type}
)

existing_types.add(type)


class DeadlineForm(ModelForm):
class Meta:
model = Deadline
fields = ["start", "end", "name", "description", "type", "conference"]


class DeadlineFormSet(BaseInlineFormSet):
def clean(self):
validate_deadlines_form(self.forms)


class DeadlineInline(admin.TabularInline):
model = Deadline
form = DeadlineForm
formset = DeadlineFormSet


class DurationInline(admin.StackedInline):
model = Duration
filter_horizontal = ("allowed_submission_types",)


class SponsorLevelInline(OrderedTabularInline):
model = SponsorLevel
fields = ("name", "conference", "sponsors", "order", "move_up_down_links")
readonly_fields = (
"order",
"move_up_down_links",
)
ordering = ("order",)
extra = 1

new_path = str(Path(base_path, folder)) + "/"
all_files.extend(walk_conference_videos_folder(storage, new_path))

Check warning on line 43 in backend/conferences/admin/conference.py

View check run for this annotation

Codecov / codecov/patch

backend/conferences/admin/conference.py#L42-L43

Added lines #L42 - L43 were not covered by tests

class IncludedEventInline(admin.TabularInline):
model = IncludedEvent
return all_files

Check warning on line 45 in backend/conferences/admin/conference.py

View check run for this annotation

Codecov / codecov/patch

backend/conferences/admin/conference.py#L45

Added line #L45 was not covered by tests


@admin.register(Conference)
Expand Down Expand Up @@ -188,7 +135,12 @@
"<int:object_id>/video-upload/map-videos",
self.admin_site.admin_view(self.map_videos),
name="map_videos",
)
),
path(
"<int:object_id>/grants/summary",
self.admin_site.admin_view(self.grants_summary),
name="grants-summary",
),
]

def map_videos(self, request, object_id):
Expand Down Expand Up @@ -350,46 +302,10 @@

return ""

def grants_summary(self, request, object_id):
context = self.admin_site.each_context(request)

Check warning on line 306 in backend/conferences/admin/conference.py

View check run for this annotation

Codecov / codecov/patch

backend/conferences/admin/conference.py#L306

Added line #L306 was not covered by tests

def walk_conference_videos_folder(storage, base_path):
folders, files = storage.listdir(base_path)
all_files = [f"{base_path}{file_}" for file_ in files]

for folder in folders:
if not folder:
continue

new_path = str(Path(base_path, folder)) + "/"
all_files.extend(walk_conference_videos_folder(storage, new_path))

return all_files


@admin.register(Topic)
class TopicAdmin(admin.ModelAdmin):
pass


@admin.register(AudienceLevel)
class AudienceLevelAdmin(admin.ModelAdmin):
pass


@admin.register(Deadline)
class DeadlineAdmin(admin.ModelAdmin):
fieldsets = (
("Info", {"fields": ("name", "description", "type", "conference")}),
("Dates", {"fields": ("start", "end")}),
)


class KeynoteSpeakerForm(forms.ModelForm):
class Meta:
model = KeynoteSpeaker
fields = (
"keynote",
"user",
)
return summary_view(request, object_id, context)

Check warning on line 308 in backend/conferences/admin/conference.py

View check run for this annotation

Codecov / codecov/patch

backend/conferences/admin/conference.py#L308

Added line #L308 was not covered by tests


class KeynoteSpeakerInline(OrderedStackedInline):
Expand Down Expand Up @@ -442,124 +358,19 @@
return Keynote.all_objects.all()


@admin.action(description="Send voucher via email")
def send_voucher_via_email(modeladmin, request, queryset):
is_filtered_by_conference = (
queryset.values_list("conference_id").distinct().count() == 1
)

if not is_filtered_by_conference:
messages.error(request, "Please select only one conference")
return

count = 0
for speaker_voucher in queryset.filter(pretix_voucher_id__isnull=False):
send_speaker_voucher_email.delay(speaker_voucher_id=speaker_voucher.id)
count = count + 1

messages.success(request, f"{count} Voucher emails scheduled!")


@admin.action(description="Create speaker vouchers on Pretix")
def create_speaker_vouchers_on_pretix(modeladmin, request, queryset):
is_filtered_by_conference = (
queryset.values_list("conference_id").distinct().count() == 1
)

if not is_filtered_by_conference:
messages.error(request, "Please select only one conference")
return

conference = queryset.only("conference_id").first().conference

if not conference.pretix_speaker_voucher_quota_id:
messages.error(
request,
"Please configure the speaker voucher quota ID in the conference settings",
)
return

count = 0

for speaker_voucher in queryset.filter(pretix_voucher_id__isnull=True):
if speaker_voucher.voucher_type == SpeakerVoucher.VoucherType.SPEAKER:
price_mode = "set"
value = "0.00"
elif speaker_voucher.voucher_type == SpeakerVoucher.VoucherType.CO_SPEAKER:
price_mode = "percent"
value = "25.00"

pretix_voucher = create_voucher(
conference=speaker_voucher.conference,
code=speaker_voucher.voucher_code,
comment=f"Voucher for user_id={speaker_voucher.user_id}",
tag="speakers",
quota_id=speaker_voucher.conference.pretix_speaker_voucher_quota_id,
price_mode=price_mode,
value=value,
)

pretix_voucher_id = pretix_voucher["id"]
speaker_voucher.pretix_voucher_id = pretix_voucher_id
speaker_voucher.save()
count = count + 1

messages.success(request, f"{count} Vouchers created on Pretix!")

@admin.register(Topic)
class TopicAdmin(admin.ModelAdmin):
pass

class SpeakerVoucherForm(forms.ModelForm):
class Meta:
model = SpeakerVoucher
fields = [
"conference",
"user",
"voucher_type",
"voucher_code",
"pretix_voucher_id",
"voucher_email_sent_at",
]

@admin.register(AudienceLevel)
class AudienceLevelAdmin(admin.ModelAdmin):
pass

@admin.register(SpeakerVoucher)
class SpeakerVoucherAdmin(admin.ModelAdmin):
form = SpeakerVoucherForm
search_fields = ("voucher_code", "user__name", "user__full_name")
autocomplete_fields = ("user",)
list_filter = (
"conference",
"voucher_type",
("pretix_voucher_id", admin.EmptyFieldListFilter),
)
list_display = (
"conference",
"user_display_name",
"voucher_type",
"voucher_code",
"created_on_pretix",
"voucher_email_sent_at",
"created",
)
actions = [
create_speaker_vouchers_on_pretix,
send_voucher_via_email,
]

@admin.display(
boolean=True,
@admin.register(Deadline)
class DeadlineAdmin(admin.ModelAdmin):
fieldsets = (
("Info", {"fields": ("name", "description", "type", "conference")}),
("Dates", {"fields": ("start", "end")}),
)
def created_on_pretix(self, obj):
return obj.pretix_voucher_id is not None

def get_changeform_initial_data(self, request):
return {"voucher_code": SpeakerVoucher.generate_code()}

def user_display_name(self, obj):
return obj.user.display_name


def cleanup_string(string: str) -> str:
new_string = normalize(
"NFKD", "".join(char for char in string if char.isprintable())
).lower()
new_string = re.sub(r"\s+", " ", new_string)
return new_string.strip()
Loading
Loading