import logging from typing import Any from django.conf import settings from django.contrib import admin from django.contrib.admin import FieldListFilter from django.contrib.auth.admin import UserAdmin from django.contrib.contenttypes.admin import GenericTabularInline from django.contrib.gis.admin import GISModelAdmin from django.db.models import F, QuerySet from django.http import HttpRequest from django.urls import reverse from django.utils.safestring import mark_safe from django.utils.translation import gettext_lazy as _ from .models import ( ActivityLogEntry, Assembly, AssemblyLink, AssemblyMember, Badge, BadgeCategory, BadgeToken, BadgeTokenTimeConstraint, BulletinBoardEntry, Conference, ConferenceMember, ConferenceNavigationItem, ConferenceTag, ConferenceTrack, DereferrerStats, Event, EventAttachment, EventParticipant, Invitation, Link, Lock, MapFloor, MapPOI, MetaNavItem, PlatformUser, Project, Room, RoomLink, RoomShare, ScheduleSource, ScheduleSourceImport, ScheduleSourceMapping, StaticPage, StaticPageNamespace, StaticPageRevision, TagItem, UserBadge, UserCommunicationChannel, UserContact, UserDereferrerAllowlist, Voucher, VoucherEntry, ) logger = logging.getLogger(__name__) class ArrayFieldEntryFilter(FieldListFilter): """ A ModelAdmin list_filter which can be assigned to an ArrayField, i.e. `list_filter = ['conference', ('groups', ArrayFieldEntryFilter)]` """ def __init__(self, field, request, params, model, model_admin, field_path): self.model_admin = model_admin self.parameter_name = field_path super().__init__(field, request, params, model, model_admin, field_path) def expected_parameters(self): return [self.parameter_name] def value(self): """Fetch currently selected value (from query string).""" return self.used_parameters.get(self.parameter_name) def choices(self, changelist): values = set() # fetch all possible groups by merging all entries in all objects' field value for array_value in self.model_admin.model.objects.values_list(self.field_path, flat=True): values.update(array_value or []) # assemble available options v = self.value() yield { 'selected': v is None or v == '', 'query_string': changelist.get_query_string(remove=[self.parameter_name]), 'display': _('All'), } for value in sorted(values, key=lambda x: x.upper()): yield { 'selected': v == value, 'query_string': changelist.get_query_string({self.parameter_name: value}), 'display': value, } def queryset(self, request, queryset): if query := self.value(): qs_filter = { f'{self.field_path}__contains': query.split(','), } queryset = queryset.filter(**qs_filter) return queryset class UserCommunicationInline(admin.TabularInline): model = UserCommunicationChannel extra = 0 class UserConferenceInline(admin.TabularInline): model = ConferenceMember extra = 0 class UserContactInline(admin.TabularInline): model = UserContact fk_name = 'user' extra = 0 class UserBadgeInline(admin.TabularInline): model = UserBadge fk_name = 'user' extra = 0 class UserAssemblyMemberInline(admin.TabularInline): model = AssemblyMember extra = 0 fields = ['assembly', 'can_manage_assembly', 'is_representative', 'show_public'] class UserFavoriteEventInline(admin.TabularInline): model = Event.favorite_of.through extra = 0 verbose_name = _('PlatformUser__favorite_event') verbose_name_plural = _('PlatformUser__favorite_events') class UserFavoriteAssemblyInline(admin.TabularInline): model = Assembly.favorite_of.through extra = 0 verbose_name = _('PlatformUser__favorite_assembly') verbose_name_plural = _('PlatformUser__favorite_assemblies') class PlatformUserAdmin(UserAdmin): model = PlatformUser list_display = ['username', 'user_type'] list_filter = ['user_type', 'is_active', 'is_staff'] search_fields = ['username', 'display_name', 'communication_channels__address'] fieldsets = ( (None, {'fields': ('username', 'slug', 'password', 'user_type', 'timezone')}), ('Personal info', {'fields': ('first_name', 'last_name', 'display_name', 'email')}), ('Self Portrayal', {'fields': (('pronouns', 'show_name'), ('status', 'status_public'), ('avatar', 'avatar_url'))}), ('Accessibility', {'fields': ('theme', 'no_animations', 'colorblind', 'high_contrast', 'tag_ignorelist')}), ('Disturbance Settings', {'fields': ('receive_dms', 'receive_dm_images', 'receive_audio', 'receive_video', 'autoaccept_contacts')}), ('Permissions', {'fields': ('is_active', 'shadow_banned', 'is_staff', 'is_superuser', 'groups', 'user_permissions')}), ('Notifications', {'fields': ('admin_notification',)}), ('Important dates', {'fields': ('last_login', 'date_joined')}), ('Security', {'fields': ('allow_reset_non_primary',)}), ) inlines = [ UserCommunicationInline, UserConferenceInline, UserContactInline, UserBadgeInline, UserAssemblyMemberInline, UserFavoriteEventInline, UserFavoriteAssemblyInline, ] readonly_fields = ['id'] def get_inline_instances(self, request, obj=None): # do not show any inlines when creating a new user if not obj: return [] return super().get_inline_instances(request, obj) class ConferenceTrackInline(admin.TabularInline): model = ConferenceTrack fields = ['slug', 'name'] extra = 0 class ConferenceAdmin(admin.ModelAdmin): list_display = ['slug', 'name'] list_display_links = ['slug', 'name'] search_fields = ['slug', 'name'] inlines = [ConferenceTrackInline] readonly_fields = ['id'] def get_inline_instances(self, request, obj=None, **kwargs): # no inlines on new instances if obj is None: return [] return super().get_inline_instances(request, obj, **kwargs) class ConferenceMemberAdmin(admin.ModelAdmin): list_display = ['conference', 'user', 'is_staff', 'active_angel'] list_display_links = ['user'] list_filter = ['conference__slug', 'is_staff', 'active_angel'] search_fields = ['user__username', 'roles'] class ConferenceNavigationItemInline(admin.TabularInline): model = ConferenceNavigationItem fields = ['index', 'is_visible', 'icon', 'label', 'title', 'url'] def has_add_permission(self, request, obj): return False def has_change_permission(self, request, obj=None): return False def has_delete_permission(self, request, obj=None): return False class ConferenceNavigationItemAdmin(admin.ModelAdmin): list_display = ['conference', 'parent', 'index', 'is_visible', 'label'] list_display_links = ['label'] list_filter = ['conference', 'is_visible'] search_fields = ['label', 'title'] def get_queryset(self, request, *args, **kwargs): qs = super().get_queryset(request, *args, **kwargs) return qs.order_by('conference__name', F('parent__index').asc(nulls_first=True), 'index') fieldsets = ( ( 'Organisation', { 'fields': ['conference', 'parent', 'index'], }, ), ( 'Data', { 'fields': [ 'is_visible', 'icon', ('label_de', 'label_en'), ('title_de', 'title_en'), 'url', ], }, ), ) def formfield_for_foreignkey(self, db_field, request, **kwargs): if db_field.name == 'parent': kwargs['queryset'] = ConferenceNavigationItem.objects.filter(parent=None).order_by('index') return super().formfield_for_foreignkey(db_field, request, **kwargs) def get_inlines(self, request, obj): return [ConferenceNavigationItemInline] if obj is not None and obj.parent is None else [] def get_readonly_fields(self, request, obj=None, **kwargs): # once set, the conference may not be changed any more return [] if obj is None else ['conference'] class ConferenceTrackAdmin(admin.ModelAdmin): list_display = ['conference', 'is_public', 'slug', 'name'] list_display_links = ['slug', 'name'] list_filter = ['conference', 'is_public'] search_fields = ['slug', 'name'] readonly_fields = ['conference'] fieldsets = ( ( 'Organisation', { 'fields': ['conference', 'is_public'], }, ), ( 'Data', { 'fields': ['slug', 'name', 'color', 'banner_image'], }, ), ) def get_readonly_fields(self, request, obj=None, **kwargs): # conference may be selected on new tracks if obj is None: return [] return super().get_readonly_fields(request, obj, **kwargs) class ConferenceTagAdmin(admin.ModelAdmin): list_display = ['conference', 'slug', 'is_public', 'value_type'] list_display_links = ['slug'] list_filter = ['value_type', 'is_public', 'conference'] search_fields = ['slug', 'description'] class TagsInline(GenericTabularInline): model = TagItem ct_field = 'target_type' ct_fk_field = 'target_id' extra = 1 class BadgeInline(admin.TabularInline): model = Badge list_display = ['name', 'show_public', 'is_achievement'] extra = 1 class AssemblyLinkInline(admin.TabularInline): model = AssemblyLink fk_name = 'a' list_display = ['type', 'b', 'is_public'] extra = 0 class AssemblyMemberInline(admin.TabularInline): model = AssemblyMember extra = 0 fields = ['member', 'can_manage_assembly', 'is_representative', 'show_public'] class ActivityLogEntryInline(GenericTabularInline): model = ActivityLogEntry ct_field = 'entity_content_type' ct_fk_field = 'entity_object_id' extra = 0 fields = ['timestamp', 'kind', 'user', 'comment', 'changes'] readonly_fields = ['timestamp'] def has_add_permission(self, request, obj): return False def has_change_permission(self, request, obj=None): return False def has_delete_permission(self, request, obj=None): return False class AssemblyAdmin(GISModelAdmin): list_display = ['conference', 'parent', 'slug', 'name', 'state', 'is_official'] list_display_links = ['slug', 'name'] list_filter = ['conference', 'parent', 'state', 'is_official'] readonly_fields = ['id', 'conference'] search_fields = ['slug', 'name', 'description'] inlines = [TagsInline, BadgeInline, AssemblyLinkInline, AssemblyMemberInline, ActivityLogEntryInline] fieldsets = ( ( 'Organisation', { 'fields': ['id', 'conference', 'state', 'hierarchy', 'parent', 'is_official'], }, ), ( 'Data', { 'fields': ['is_physical', 'is_virtual', 'is_remote', 'slug', 'name', 'description', 'assembly_link', 'banner_image'], }, ), ( 'Registration', { 'fields': ['registration_details'], }, ), ( 'Location', { 'fields': ['assembly_location', 'location_point', 'location_boundaries'], }, ), ) def formfield_for_foreignkey(self, db_field, request, **kwargs): assembly_id = request.resolver_match.kwargs.get('object_id') if assembly_id is not None: assembly = Assembly.objects.get(id=assembly_id) # parent field may only list assemblies from the same conference if db_field.name == 'parent': kwargs['queryset'] = Assembly.objects.filter(conference=assembly.conference) return super().formfield_for_foreignkey(db_field, request, **kwargs) def get_fieldsets(self, request, obj=None, **kwargs): if obj is None: return [ ( 'Organisation', { 'fields': ['id', 'conference', 'state', 'hierarchy', 'is_official'], }, ), ( 'Data', { 'fields': ['is_physical', 'is_virtual', 'is_remote', 'slug', 'name'], }, ), ] return super().get_fieldsets(request, obj, **kwargs) def get_readonly_fields(self, request, obj=None, **kwargs): if obj is None: return ['id'] return super().get_readonly_fields(request, obj, **kwargs) def get_inline_instances(self, request, obj=None, **kwargs): if obj is None: return [] return super().get_inline_instances(request, obj, **kwargs) class ActivityLogEntryAdmin(admin.ModelAdmin): list_display = ['timestamp', 'entity', 'kind', 'user', 'comment', 'changes'] list_filter = ['kind', 'entity_content_type', 'user'] search_fields = ['entity__slug', 'entity__name', 'comment', 'changes'] readonly_fields = ['timestamp', 'conference', 'context', 'entity'] fieldsets = [ ( 'Organisation', { 'fields': ['conference', 'timestamp', 'context', 'entity'], }, ), ( 'Data', { 'fields': [ ( 'kind', 'user', ), 'comment', 'changes', ], }, ), ] class InvitationAdmin(admin.ModelAdmin): model = Invitation readonly_fields = ['id', 'type', 'sender', 'requested_at', 'updated_at', 'requester', 'requested'] def get_fields(self, request: HttpRequest, obj: Any | None = None) -> list[str]: if obj is None: return ['requester_type', 'requester_id', 'requested_type', 'requested_id', 'comment'] return ['id', 'requester', 'requester_type', 'requested', 'requested_type', 'comment', 'requested_at', 'updated_at', 'type', 'sender'] def get_readonly_fields(self, request: HttpRequest, obj: Any | None = None) -> list[str]: if obj is None: return self.readonly_fields return [*self.readonly_fields, 'requester_type', 'requested_type'] class MapFloorAdmin(admin.ModelAdmin): list_display = ['index', 'name', 'conference'] list_display_links = ['name'] list_filter = ['conference'] readonly_fields = ['id', 'conference'] ordering = ['conference', 'index'] fieldsets = ( ( 'Organisation', { 'fields': ['id', 'conference'], }, ), ( 'Data', { 'fields': ['index', ('name_de', 'name_en')], }, ), ) def get_readonly_fields(self, request, obj=None, **kwargs): result = list(super().get_readonly_fields(request, obj, **kwargs)) if obj is None: result.remove('conference') return result class MapPOIAdmin(GISModelAdmin): list_display = ['visible', 'name', 'is_official', 'conference'] list_display_links = ['name'] list_filter = ['conference', 'visible', 'is_official'] readonly_fields = ['id', 'conference'] search_fields = ['name', 'description'] fieldsets = ( ( 'Organisation', { 'fields': ['id', 'conference'], }, ), ( 'Data', { 'fields': ['visible', 'is_official', ('name_de', 'name_en'), ('description_de', 'description_en')], }, ), ( 'Location', { 'fields': ['location_floor', 'location_point'], }, ), ) def get_readonly_fields(self, request, obj=None, **kwargs): result = list(super().get_readonly_fields(request, obj, **kwargs)) if obj is None: result.remove('conference') return result class BadgeCategoryAdmin(admin.ModelAdmin): model = BadgeCategory class BadgeAdmin(admin.ModelAdmin): model = Badge fields = ['name', 'issuing_assembly', 'state', 'category', 'symbol', 'description', 'location', 'image', 'issuing_token'] readonly_fields = [ 'issuing_assembly', ] list_display = ['__str__', 'issuing_assembly', 'category'] list_display_links = ['__str__'] list_filter = ['state', 'category'] search_fields = ['name', 'description', 'issuing_token'] class BadgeTokenTimeConstraintInline(admin.TabularInline): model = BadgeTokenTimeConstraint fields = ['date_time_range'] verbose_name = _('BadgeToken__badge_token_time_constraint') verbose_name_plural = _('BadgeToken__badge_token_time_constraints') class BadgeTokenAdmin(admin.ModelAdmin): model = BadgeToken fields = ['badge', 'issued', 'active', 'comment', 'redeemable_count', 'users_list'] readonly_fields = ['issued', 'users_list'] inlines = [ BadgeTokenTimeConstraintInline, ] list_display = ['__str__', 'valid', 'active', 'comment', 'redeemable_count', 'redeemed_count', 'time_constraints_list'] list_display_links = ['__str__'] def valid(self, obj): return obj.valid valid.boolean = True def time_constraints_list(self, obj): return ','.join([str(k.date_time_range) for k in obj.time_constraints.all()]) def redeemed_count(self, obj): return obj.users.count() def users_list(self, obj): return ','.join([str(k.user) for k in obj.users.all()]) def get_readonly_fields(self, request, obj=None): if obj: # editing an existing object return [*self.readonly_fields, 'badge'] return self.readonly_fields class EventAttachmentInline(admin.TabularInline): model = EventAttachment fields = ['file', 'filename', 'mime_type', 'visibility'] extra = 1 class EventParticipantInline(admin.TabularInline): model = EventParticipant fields = ['participant', 'role', 'is_accepted', 'is_public', 'order'] extra = 1 raw_id_fields = ['participant'] class IsImportedListFilter(admin.SimpleListFilter): title = _('Event__is_imported') parameter_name = 'imported' def lookups(self, request, model_admin): return [ (True, _('Event__imported')), (False, _('Event__not_imported')), ] def queryset(self, request, queryset): """ Returns the filtered queryset based on the value provided in the query string and retrievable via `self.value()`. """ if self.value() == 'True': return queryset.filter( id__in=ScheduleSourceMapping.objects.filter( mapping_type=ScheduleSourceMapping.MappingType.EVENT, ) .exclude(skip=True) .values_list('local_id') ) elif self.value() == 'False': return queryset.exclude( id__in=ScheduleSourceMapping.objects.filter( mapping_type=ScheduleSourceMapping.MappingType.EVENT, ) .exclude(skip=True) .values_list('local_id') ) class EventAdmin(admin.ModelAdmin): list_display = [ 'conference', 'track', 'name', 'is_public', 'kind', 'room', 'schedule_start', 'schedule_duration', 'get_is_imported', ] list_display_links = ['name'] list_filter = ['conference', 'track', 'is_public', 'kind', 'room', IsImportedListFilter] search_fields = ['name', 'abstract', 'description_de', 'description_en'] inlines = [ TagsInline, EventAttachmentInline, EventParticipantInline, ] readonly_fields = ['id', 'conference', 'get_is_imported'] fieldsets = ( ( 'Organisation', {'fields': ['id', 'conference', 'track', 'assembly', 'owner', 'room', 'location', 'kind', 'is_public', 'recording', 'streaming']}, ), ( 'Schedule', {'fields': ['schedule_start', 'schedule_duration', 'get_is_imported']}, ), ( 'Data', {'fields': ['name', 'slug', 'language', 'abstract', 'description_de', 'description_en', 'banner_image', 'banner_image_url', 'additional_data']}, ), ) def get_queryset(self, request: HttpRequest) -> QuerySet[Event]: return super().get_queryset(request).select_related('conference', 'track', 'room') def formfield_for_foreignkey(self, db_field, request, **kwargs): event_id = request.resolver_match.kwargs.get('object_id') if event_id is not None: event = Event.objects.get(id=event_id) # room field may only list assemblies from the same conference if db_field.name == 'room': kwargs['queryset'] = Room.objects.filter(conference=event.conference) # track field may only list assemblies from the same conference if db_field.name == 'track': kwargs['queryset'] = ConferenceTrack.objects.filter(conference=event.conference) return super().formfield_for_foreignkey(db_field, request, **kwargs) def get_fieldsets(self, request, obj=None, **kwargs): if obj is None: return [ ( 'Organisation', {'fields': ['id', 'conference', 'assembly']}, ), ( 'Data', {'fields': ['name']}, ), ] return super().get_fieldsets(request, obj, **kwargs) def get_inline_instances(self, request, obj=None, **kwargs): # no inlines on new instances if obj is None: return [] return super().get_inline_instances(request, obj, **kwargs) def get_readonly_fields(self, request, obj=None, **kwargs): # upon creation the user may select a conference if obj is None: return ['id'] return super().get_readonly_fields(request, obj, **kwargs) def get_is_imported(self, instance): return instance.is_imported get_is_imported.boolean = True get_is_imported.short_description = _('Event__is_imported') class RoomLinkInline(admin.TabularInline): model = RoomLink list_display = ['link_type', 'name', 'link'] class RoomShareInline(admin.TabularInline): model = RoomShare list_display = ['timeframe', 'all_assemblies', 'all_self_organized_sessions', 'single_assembly'] extra = 1 class RoomShareAdmin(admin.ModelAdmin): list_display = ['room', 'timeframe', 'enabled', 'all_assemblies', 'all_self_organized_sessions', 'single_assembly'] list_filter = ['room', 'enabled', 'all_assemblies', 'all_self_organized_sessions', 'single_assembly'] class RoomAdmin(admin.ModelAdmin): list_display = ['conference', 'assembly', 'name', 'room_type', 'blocked'] list_display_links = ['name'] list_filter = ['conference', 'room_type', 'backend_status', 'blocked', 'is_official', 'is_public_fahrplan'] search_fields = ['assembly__name', 'name', 'slug'] inlines = [RoomLinkInline, RoomShareInline, TagsInline] readonly_fields = ['id', 'conference', 'occupants', 'reserve_capacity'] ordering = ('-conference__id', F('assembly__is_official').desc(nulls_last=True), 'assembly__name', F('capacity').desc(nulls_last=True), 'name') fieldsets = ( ( 'Metadata', {'fields': ['id', 'conference', 'assembly']}, ), ( 'Schedule', {'fields': ['is_official', 'official_room_order', 'is_public_fahrplan', 'recording_state', 'blocked', 'reserve_capacity']}, ), ( 'Data', {'fields': ['name', 'room_type', 'capacity', 'occupants', 'description']}, ), ( 'Backend', {'fields': ['backend_link', 'backend_link_branch', 'backend_status', 'backend_data', 'director_data']}, ), ( 'Image', {'fields': ['banner_image']}, ), ) def formfield_for_foreignkey(self, db_field, request, **kwargs): # assembly field may only list assemblies from the same conference if db_field.name == 'assembly': try: room_id = request.resolver_match.kwargs['object_id'] room = Room.objects.get(id=room_id) kwargs['queryset'] = Assembly.objects.filter(conference=room.conference) except Exception: pass return super().formfield_for_foreignkey(db_field, request, **kwargs) def get_fieldsets(self, request, obj=None, **kwargs): if obj is None: return [ ( 'Organisation', {'fields': ['id', 'conference', 'assembly', 'blocked']}, ), ( 'Data', {'fields': ['name', 'room_type', 'backend_link', 'capacity']}, ), ( 'Image', {'fields': ['banner_image']}, ), ] return super().get_fieldsets(request, obj, **kwargs) def get_inline_instances(self, request, obj=None, **kwargs): # no inlines on new instances if obj is None: return [] return super().get_inline_instances(request, obj, **kwargs) def get_readonly_fields(self, request, obj=None, **kwargs): # upon creation the user may select a conference if obj is None: return ['id'] return super().get_readonly_fields(request, obj, **kwargs) class LinkAdmin(GenericTabularInline): model = Link extra = 1 class IsSelfOrganized(admin.SimpleListFilter): # Human-readable title which will be displayed in the # right admin sidebar just above the filter options. title = _('is_self_organized') # Parameter for the filter that will be used in the URL query. parameter_name = 'is_self_organized_filter' def lookups(self, request, model_admin): return (('True', _('Self-Organized')), ('False', _('Assembly'))) def queryset(self, request, queryset): if self.value() == 'True': return queryset.filter(assembly__isnull=True) elif self.value() == 'False': return queryset.filter(assembly__isnull=False) class ProjectAdmin(admin.ModelAdmin): model = Project list_display = ['conference', 'assembly_owner', 'name', 'is_self_organized', 'is_public', 'blocked'] list_display_links = ['name'] list_filter = ['conference', 'is_public', IsSelfOrganized] search_fields = ['assembly__name', 'owner__username', 'name'] inlines = [LinkAdmin] readonly_fields = ['id', 'conference', 'is_self_organized'] ordering = ('-conference__id', 'name') fieldsets = ( ( 'Organisation', { 'fields': ['id', 'conference', 'assembly', 'owner', 'is_public', 'blocked', 'is_self_organized'], 'description': 'Assembly and owner are mutually exclusive. If the owner is set it will be a self-organized project.', }, ), ('Data', {'fields': ['name', 'description', 'location']}), ('Image', {'fields': ['banner_image']}), ) def get_queryset(self, request: HttpRequest) -> QuerySet[Project]: return super().get_queryset(request).select_related('owner', 'assembly', 'conference') def is_self_organized(self, instance): return instance.is_self_organized is_self_organized.boolean = True is_self_organized.short_description = 'is self-organized' def assembly_owner(self, obj) -> str: if obj.owner: return mark_safe(f'<a href="{reverse("admin:core_platformuser_change", args=(obj.owner.pk,))}">{obj.owner.username}</a>') return mark_safe(f'<a href="{reverse("admin:core_assembly_change", args=(obj.assembly.pk,))}">{obj.assembly.name}</a>') def get_fieldsets(self, request, obj=None, **kwargs): fielsets = super().get_fieldsets(request, obj, **kwargs) if obj is None: fielsets[0][1]['fields'].remove('is_self_organized') fielsets[0][1]['fields'].remove('blocked') return fielsets def formfield_for_foreignkey(self, db_field, request, **kwargs): # assembly field may only list assemblies from the same conference if db_field.name == 'assembly': try: room_id = request.resolver_match.kwargs['object_id'] room = Room.objects.get(id=room_id) kwargs['queryset'] = Assembly.objects.filter(conference=room.conference) except Exception: pass return super().formfield_for_foreignkey(db_field, request, **kwargs) def get_inline_instances(self, request, obj=None, **kwargs): # no inlines on new instances if obj is None: return [] return super().get_inline_instances(request, obj, **kwargs) def get_readonly_fields(self, request, obj=None, **kwargs): # upon creation the user may select a conference if obj is None: return ['id'] return super().get_readonly_fields(request, obj, **kwargs) class StaticPageRevisionInline(admin.TabularInline): model = StaticPageRevision fields = ['revision', 'timestamp', 'author', 'is_draft', 'title'] readonly_fields = fields extra = 0 show_change_link = True can_add = False can_delete = False class StaticPageAdmin(admin.ModelAdmin): list_display = ['conference', 'language', 'slug', 'protection', 'privacy', 'remove_html', 'sanitize_html', 'title'] list_display_links = ['slug'] list_filter = ['conference', 'protection', 'privacy', 'remove_html', 'sanitize_html'] search_fields = ['title', 'body'] inlines = [StaticPageRevisionInline] readonly_fields = ['id', 'conference', 'language', 'body_html', 'search_content'] fieldsets = ( ( 'Organisation', { 'fields': ['id', 'conference', 'slug', 'language'], }, ), ( 'Configuration', { 'fields': ['public_revision', 'protection', 'privacy', 'remove_html', 'sanitize_html'], }, ), ( 'Data', { 'fields': ['title', ('search_content', 'body_html')], }, ), ) def get_inline_instances(self, request, obj=None, **kwargs): # no inlines on new instances if obj is None: return [] return super().get_inline_instances(request, obj, **kwargs) def get_readonly_fields(self, request, obj=None, **kwargs): flds = super().get_readonly_fields(request, obj, **kwargs) if obj is None: # upon creation the user may select a conference flds = [x for x in flds if x not in ['conference']] return flds class StaticPageNamespaceAdmin(admin.ModelAdmin): list_display = ['prefix', 'groups', 'conference'] list_display_links = ['prefix'] list_filter = ['conference', ('groups', ArrayFieldEntryFilter)] search_fields = ['prefix'] readonly_fields = ['id', 'conference'] fieldsets = ( ( 'Organisation', { 'fields': ['id', 'conference'], }, ), ( 'Namespace', { 'fields': ['prefix', 'groups'], }, ), ( 'Upstream', { 'fields': ['upstream_url', 'upstream_image_base_url'], }, ), ) def get_readonly_fields(self, request, obj=None, **kwargs): flds = super().get_readonly_fields(request, obj, **kwargs) if obj is None: # upon creation the user may select a conference flds = [x for x in flds if x not in ['conference']] return flds class StaticPageRevisionAdmin(admin.ModelAdmin): list_display = ['conference_page_slugs', 'revision', 'timestamp', 'author', 'is_draft', 'title'] list_display_links = ['revision'] list_filter = ['page__conference', 'author', 'is_draft', 'timestamp'] search_fields = ['title', 'body'] readonly_fields = ['page', 'revision'] actions = ['set_revisions_public'] def set_revisions_public(self, request, queryset): for rev in queryset.select_related('page').all(): logger.info('Revision %s was published by %s', rev, request.user) rev.set_public() def conference_page_slugs(self, obj): return f'{obj.page.conference.slug}/{obj.page.slug}' def get_readonly_fields(self, request, obj=None, **kwargs): # upon creation the user may select a conference if obj is None: return ['page'] return super().get_readonly_fields(request, obj, **kwargs) class LockAdmin(admin.ModelAdmin): list_display = ['uuid', 'content_type', 'object_id'] search_fields = ['uuid', 'object_id'] class VoucherAdmin(admin.ModelAdmin): list_display = ['name', 'enabled', 'assignment', 'target', 'entries', 'show_always', 'hide_from_staff'] list_filter = ['enabled', 'show_always', 'hide_from_staff', 'target', 'assignment'] search_fields = ['name', 'description'] def entries(self, instance): return instance.entries.count() class VoucherEntryAdmin(admin.ModelAdmin): list_display = ['id', 'voucher', 'content', 'is_assigned', 'target'] list_filter = ['voucher', 'assigned'] search_fields = ['voucher__name', 'content'] def is_assigned(self, instance): return instance.assigned is not None is_assigned.boolean = True class UserDereferrerAllowlistAdmin(admin.ModelAdmin): list_display = ['domain', 'user', 'active'] list_display_links = ['domain'] search_fields = ['url', 'user'] readonly_fields = ['id'] class DereferrerStatsAdmin(admin.ModelAdmin): list_display = ['domain', 'hits'] list_display_links = ['domain'] search_fields = ['domain', 'hits'] readonly_fields = ['id'] class BulletinBoardEntryAdmin(admin.ModelAdmin): list_display = ['conference', 'owner', 'is_public', 'timestamp', 'title'] fields = ['conference', 'owner', 'is_public', 'title', 'text'] readonly = ['timestamp'] class ScheduleSourceAdmin(admin.ModelAdmin): list_display = ['conference', 'assembly', 'id', 'import_type', 'import_url'] list_display_links = ['id'] list_filter = ['conference', 'import_type'] def has_add_permission(self, request, *args, **kwargs): return request.user.is_superuser class ScheduleSourceImportAdmin(admin.ModelAdmin): list_display = ['id', 'schedule_source', 'state', 'summary', 'start', 'end'] list_display_links = ['id'] list_filter = ['schedule_source', 'state'] def has_add_permission(self, *args, **kwargs): return settings.DEBUG def has_change_permission(self, *args, **kwargs): return settings.DEBUG class ScheduleSourceMappingAdmin(admin.ModelAdmin): list_display = ['id', 'schedule_source', 'mapping_type', 'source_id', 'local_id', 'skip'] list_display_links = ['id'] list_filter = ['schedule_source', 'mapping_type'] search_fields = ['source_id', 'local_id'] readonly_fields = ['schedule_source', 'mapping_type', 'source_id'] def get_readonly_fields(self, request, obj=None): if obj: return super().get_readonly_fields(request, obj=obj) else: return [] class MetaNavItemAdmin(admin.ModelAdmin): list_display = ['index', 'enabled', 'slug', 'title', 'conference'] list_display_links = ['slug', 'title'] list_filter = ['enabled', 'conference'] readonly_fields = ['pk', 'conference', 'graphic_light_current', 'graphic_dark_current'] fieldsets = [ ( 'Metadata', { 'fields': ['pk', 'conference', 'slug'], }, ), ( 'Display', { 'fields': [ 'index', 'visible', 'enabled', ('title_de', 'title_en'), ], }, ), ( 'Item', { 'fields': [ 'url', ('graphic_light_current', 'graphic_light'), ('graphic_dark_current', 'graphic_dark'), ], }, ), ] def get_readonly_fields(self, request, obj=None, **kwargs): result = list(super().get_readonly_fields(request, obj, **kwargs)) if obj is None: result.remove('conference') return result def graphic_light_current(self, obj: MetaNavItem): return obj.get_graphic_light_as_html() graphic_light_current.allow_tags = True graphic_light_current.caption = _('MetaNavItem__graphic_light') def graphic_dark_current(self, obj: MetaNavItem): return obj.get_graphic_dark_as_html(default_to_light=False) graphic_dark_current.allow_tags = True graphic_dark_current.verbose_name = _('MetaNavItem__graphic_dark') admin.site.site_title = 'Hub Admin' admin.site.register(PlatformUser, PlatformUserAdmin) admin.site.register(BulletinBoardEntry, BulletinBoardEntryAdmin) admin.site.register(Conference, ConferenceAdmin) admin.site.register(ConferenceNavigationItem, ConferenceNavigationItemAdmin) admin.site.register(UserDereferrerAllowlist, UserDereferrerAllowlistAdmin) admin.site.register(DereferrerStats, DereferrerStatsAdmin) admin.site.register(ConferenceMember, ConferenceMemberAdmin) admin.site.register(ConferenceTag, ConferenceTagAdmin) admin.site.register(ConferenceTrack, ConferenceTrackAdmin) admin.site.register(Assembly, AssemblyAdmin) admin.site.register(ActivityLogEntry, ActivityLogEntryAdmin) admin.site.register(BadgeCategory, BadgeCategoryAdmin) admin.site.register(Badge, BadgeAdmin) admin.site.register(BadgeToken, BadgeTokenAdmin) admin.site.register(Event, EventAdmin) admin.site.register(Invitation, InvitationAdmin) admin.site.register(Lock, LockAdmin) admin.site.register(MapFloor, MapFloorAdmin) admin.site.register(MapPOI, MapPOIAdmin) admin.site.register(MetaNavItem, MetaNavItemAdmin) admin.site.register(Project, ProjectAdmin) admin.site.register(Room, RoomAdmin) admin.site.register(RoomShare, RoomShareAdmin) admin.site.register(ScheduleSource, ScheduleSourceAdmin) admin.site.register(ScheduleSourceImport, ScheduleSourceImportAdmin) admin.site.register(ScheduleSourceMapping, ScheduleSourceMappingAdmin) admin.site.register(StaticPage, StaticPageAdmin) admin.site.register(StaticPageNamespace, StaticPageNamespaceAdmin) admin.site.register(StaticPageRevision, StaticPageRevisionAdmin) admin.site.register(Voucher, VoucherAdmin) admin.site.register(VoucherEntry, VoucherEntryAdmin)