from datetime import date from crispy_forms.utils import flatatt from django.db import models import django.db.models.options as options from django.utils.html import format_html, format_html_join from wagtail.embeds.blocks import EmbedBlock options.DEFAULT_NAMES = options.DEFAULT_NAMES + ('description',) from django.urls import reverse from django.contrib import messages from wagtailcaptcha.models import WagtailCaptchaEmailForm from crispy_forms.helper import FormHelper from django_extensions.db.models import TimeStampedModel from modelcluster.fields import ParentalKey from wagtail import blocks from wagtail.documents import get_document_model from wagtail.images import get_image_model from wagtail.models import Page from wagtail.fields import RichTextField, StreamField from wagtail.admin.panels import ( MultiFieldPanel, PageChooserPanel, InlinePanel, FieldPanel) from wagtail.images.blocks import ImageChooserBlock from wagtail.documents.blocks import DocumentChooserBlock from wagtail.contrib.forms.models import AbstractEmailForm, AbstractFormField from wagtailmedia.edit_handlers import MediaChooserPanel from wagtailmedia.blocks import AbstractMediaChooserBlock class MediaBlock(AbstractMediaChooserBlock): def render_basic(self, value, context=None): if not value: return '' if value.type == 'video': player_code = '''
''' else: player_code = '''
''' return format_html(player_code, format_html_join( '\n', "", [[flatatt(s)] for s in value.sources] )) class HomePage(Page): block1 = RichTextField(blank=True) # revolution slider slide1_img = models.ForeignKey( get_image_model(), null=True, blank=True, on_delete=models.SET_NULL, related_name='+' ) slide1_headline = models.CharField(max_length=512, blank=True) slide1_subline = models.CharField(max_length=512, blank=True) slide1_link_url = models.URLField(blank=True) slide1_link_text = models.CharField(max_length=64, blank=True) slide2_img = models.ForeignKey( get_image_model(), null=True, blank=True, on_delete=models.SET_NULL, related_name='+' ) slide2_headline = models.CharField(max_length=512, blank=True) slide2_subline = models.CharField(max_length=512, blank=True) slide2_link_url = models.URLField(blank=True) slide2_link_text = models.CharField(max_length=64, blank=True) slide3_img = models.ForeignKey( get_image_model(), null=True, blank=True, on_delete=models.SET_NULL, related_name='+' ) slide3_headline = models.CharField(max_length=512, blank=True) slide3_subline = models.CharField(max_length=512, blank=True) slide3_link_url = models.URLField(blank=True) slide3_link_text = models.CharField(max_length=64, blank=True) thumbnail1_img = models.ForeignKey( get_image_model(), null=True, blank=True, on_delete=models.SET_NULL, related_name='+' ) thumbnail1_more_page = models.ForeignKey( 'wagtailcore.Page', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', ) thumbnail1_more_text = models.CharField(max_length=64, blank=True) thumbnail1_title = models.CharField(max_length=128, blank=True) thumbnail1_text = RichTextField(blank=True) thumbnail2_img = models.ForeignKey( get_image_model(), null=True, blank=True, on_delete=models.SET_NULL, related_name='+' ) thumbnail2_more_page = models.ForeignKey( 'wagtailcore.Page', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', ) thumbnail2_more_text = models.CharField(max_length=64, blank=True) thumbnail2_title = models.CharField(max_length=128, blank=True) thumbnail2_text = RichTextField(blank=True) thumbnail3_img = models.ForeignKey( get_image_model(), null=True, blank=True, on_delete=models.SET_NULL, related_name='+' ) thumbnail3_more_page = models.ForeignKey( 'wagtailcore.Page', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', ) thumbnail3_more_text = models.CharField(max_length=64, blank=True) thumbnail3_title = models.CharField(max_length=128, blank=True) thumbnail3_text = RichTextField(blank=True) REVOLUTION_SLIDER_FIELDS = [ MultiFieldPanel([ FieldPanel('slide1_img'), FieldPanel('slide1_headline'), FieldPanel('slide1_subline'), FieldPanel('slide1_link_url'), FieldPanel('slide1_link_text'), ], heading='Slide 1', classname="collapsible"), MultiFieldPanel([ FieldPanel('slide2_img'), FieldPanel('slide2_headline'), FieldPanel('slide2_subline'), FieldPanel('slide2_link_url'), FieldPanel('slide2_link_text'), ], heading='Slide 2', classname="collapsible"), MultiFieldPanel([ FieldPanel('slide3_img'), FieldPanel('slide3_headline'), FieldPanel('slide3_subline'), FieldPanel('slide3_link_url'), FieldPanel('slide3_link_text'), ], heading='Slide 3'), ] HOME_THUMBNAIL_FIELDS = [ MultiFieldPanel([ FieldPanel('thumbnail1_img'), PageChooserPanel('thumbnail1_more_page'), FieldPanel('thumbnail1_more_text'), FieldPanel('thumbnail1_title'), FieldPanel('thumbnail1_text'), ], heading='Thumbnail 1', classname="collapsible"), MultiFieldPanel([ FieldPanel('thumbnail2_img'), PageChooserPanel('thumbnail2_more_page'), FieldPanel('thumbnail2_more_text'), FieldPanel('thumbnail2_title'), FieldPanel('thumbnail2_text'), ], heading='Thumbnail 2', classname="collapsible"), MultiFieldPanel([ FieldPanel('thumbnail3_img'), PageChooserPanel('thumbnail3_more_page'), FieldPanel('thumbnail3_more_text'), FieldPanel('thumbnail3_title'), FieldPanel('thumbnail3_text'), ], heading='Thumbnail 3', classname="collapsible"), ] HomePage.content_panels = [ FieldPanel('title', classname="full title"), MultiFieldPanel(REVOLUTION_SLIDER_FIELDS, heading='Slider elements', classname="collapsible collapsed"), FieldPanel('block1'), MultiFieldPanel(HOME_THUMBNAIL_FIELDS, heading='Thumbnail elements', classname="collapsible collapsed"), ] class SimpleOneColumnPage(Page): featured_media = models.ForeignKey( 'wagtailmedia.Media', null=True, blank=True, on_delete=models.SET_NULL, related_name='+' ) content = RichTextField(blank=True) SimpleOneColumnPage.content_panels = [ FieldPanel('title', classname="full title"), FieldPanel('featured_media'), FieldPanel('content'), ] class BlogPage(Page): subtitle = models.CharField( max_length=512, default="Aktuelle Neuigkeiten, Einblicke und Ankündigungen", blank=True ) posts = StreamField([ ('post', blocks.StructBlock([ ('images', blocks.ListBlock(ImageChooserBlock())), ('title', blocks.CharBlock(max_length=200)), ('author', blocks.CharBlock(max_length=64, default="Marion Schauf", required=False)), ('date', blocks.DateBlock()), ('text', blocks.RichTextBlock()), ('url', blocks.URLBlock(required=False)), ])), ], null=True, blank=True, use_json_field=True) BlogPage.content_panels = [ FieldPanel('title', classname="full title"), FieldPanel('subtitle'), FieldPanel('posts'), ] class EventIndexPage(Page): subpage_types = ['core.EventPage'] def get_events(self): return EventPage.objects.live().filter(show_in_event_calendar=True, path__startswith=self.path).order_by('start_date') class Meta: description = "Displays all events on EventPages below in an overview. Use this as parent page for events." class EventPage(Page): parent_page_types = ['core.EventIndexPage', 'core.EventHistoryPage'] subtitle = models.CharField(max_length=512, null=True, blank=True) description = RichTextField(blank=True) # revolution slider slide1_img = models.ForeignKey( get_image_model(), null=True, blank=True, on_delete=models.SET_NULL, related_name='+' ) slide1_headline = models.CharField(max_length=512, blank=True) slide1_subline = models.CharField(max_length=512, blank=True) slide1_link_url = models.URLField(blank=True) slide1_link_text = models.CharField(max_length=64, blank=True) slide2_img = models.ForeignKey( get_image_model(), null=True, blank=True, on_delete=models.SET_NULL, related_name='+' ) slide2_headline = models.CharField(max_length=512, blank=True) slide2_subline = models.CharField(max_length=512, blank=True) slide2_link_url = models.URLField(blank=True) slide2_link_text = models.CharField(max_length=64, blank=True) slide3_img = models.ForeignKey( get_image_model(), null=True, blank=True, on_delete=models.SET_NULL, related_name='+' ) slide3_headline = models.CharField(max_length=512, blank=True) slide3_subline = models.CharField(max_length=512, blank=True) slide3_link_url = models.URLField(blank=True) slide3_link_text = models.CharField(max_length=64, blank=True) img1_img = models.ForeignKey( get_image_model(), null=True, blank=True, on_delete=models.SET_NULL, related_name='+' ) img1_caption = models.CharField(max_length=64, blank=True) img2_img = models.ForeignKey( get_image_model(), null=True, blank=True, on_delete=models.SET_NULL, related_name='+' ) img2_caption = models.CharField(max_length=64, blank=True) start_date = models.DateField() end_date = models.DateField(null=True, blank=True) location_name = models.CharField( max_length=256, help_text="Name of the location (first line of address)", blank=True) location_street = models.CharField(max_length=256, help_text="Street and house number", blank=True) location_city = models.CharField(max_length=256, help_text="Zip code and city", blank=True) flyer = models.ForeignKey( get_document_model(), null=True, blank=True, on_delete=models.SET_NULL, related_name='+' ) downloads = StreamField([ ('download', blocks.StructBlock([ ('title', blocks.CharBlock(max_length=128, label='Name (Button-Beschriftung)')), ('file', DocumentChooserBlock(label='Datei')), ])), ], null=True, blank=True, use_json_field=True) tabs = StreamField([ ('sponsor_tab', blocks.StructBlock([ ('title', blocks.CharBlock(max_length=64)), ('sponsors', blocks.StreamBlock([ ('sponsor', blocks.StructBlock([ ('name', blocks.CharBlock(max_length=64)), ('logo', ImageChooserBlock(required=False)), ('url', blocks.URLBlock(required=False)), ])), ('headline', blocks.CharBlock(max_length=64)), ])), ])), ('richtext_tab', blocks.StructBlock([ ('title', blocks.CharBlock(max_length=64)), ('content', blocks.RichTextBlock()), ])), ('media_tab', blocks.StructBlock([ ('title', blocks.CharBlock(max_length=64)), ('pre_content', blocks.RichTextBlock(required=False)), ('media_file', MediaBlock(template='core/blocks/media_block.html')), ('autoplay', blocks.BooleanBlock(label='Automatisch abspielen', required=False)), ('muted', blocks.BooleanBlock(label='Stummgeschaltet starten', required=False)), ('post_content', blocks.RichTextBlock(required=False)), ])), ('image_tab', blocks.StructBlock([ ('title', blocks.CharBlock(max_length=64)), ('image', ImageChooserBlock()), ('caption', blocks.CharBlock(max_length=256, required=False)), ('size', blocks.ChoiceBlock(choices=[ ('small', 'Small (25% width)'), ('medium', 'Medium (50% width)'), ('large', 'Large (75% width)'), ('full', 'Full width (100%)'), ], default='large')), ('alignment', blocks.ChoiceBlock(choices=[ ('left', 'Left'), ('center', 'Center'), ('right', 'Right'), ], default='center')), ])), ], null=True, blank=True, use_json_field=True) registration_url = models.URLField(max_length=512, blank=True, help_text="URL der Anmeldeseite (leer lassen um klassische Anmeldung zu verwenden)") pretix_slug = models.CharField(max_length=512, blank=True, help_text="Slug von Pretix (leer lassen, um klassische Anmeldung zu verwenden)") registration_start_date = models.DateField( null=True, blank=True, help_text="When does the registration open? Leave empty to disable registration") registration_end_date = models.DateField( null=True, blank=True, help_text="Last day where the registration will be available. Leave empty to enable registration permanently" "after start date") show_in_event_calendar = models.BooleanField(default=True, help_text='Event in Eventliste und im Kalender anzeigen?') is_external = models.BooleanField(default=False, help_text='Externes Event? Externe Events werden nicht im Upcoming Events Banner auf der Startseite angezeigt.') is_free = models.BooleanField(default=False, help_text='If this is checked the send button under the registration' 'form will be labeled "verbindlich anmelden" instead of "kostenpflichtig anmelden"') vimeo_id = models.TextField(blank=True, help_text='Vimeo Event ID (aus Vimeo-Link: https://vimeo.com/ )') def is_registration_active(self): return self.registration_start_date and self.registration_start_date <= date.today() and\ (not self.registration_end_date or self.registration_end_date >= date.today()) @property def pretix_url(self): if self.pretix_slug: return 'https://tickets.feo.gmbh/' + self.pretix_slug + '/' return None def get_form_helper(self): helper = FormHelper() helper.label_class = 'col-lg-3' helper.field_class = 'col-lg-9' helper.form_tag = False return helper class Meta: ordering = ['-start_date'] class EventRegistrationField(AbstractFormField): page = ParentalKey('EventPage', related_name='form_fields') EventPage.content_panels = [ MultiFieldPanel(REVOLUTION_SLIDER_FIELDS, heading='Slider elements', classname="collapsible collapsed"), FieldPanel('title', classname="full title"), FieldPanel('subtitle'), FieldPanel('description'), FieldPanel('img1_img'), FieldPanel('img1_caption'), FieldPanel('img2_img'), FieldPanel('img2_caption'), FieldPanel('start_date'), FieldPanel('end_date'), FieldPanel('tabs'), FieldPanel('location_name'), FieldPanel('location_street'), FieldPanel('location_city'), FieldPanel('flyer'), FieldPanel('downloads'), MultiFieldPanel([ FieldPanel('show_in_event_calendar'), FieldPanel('is_free'), FieldPanel('is_external'), FieldPanel('pretix_slug'), FieldPanel('registration_url'), FieldPanel('vimeo_id'), ], heading="Settings", classname="collapsible collapsed"), MultiFieldPanel([ FieldPanel('registration_start_date'), FieldPanel('registration_end_date'), ], heading="Registration Settings", classname="collapsible collapsed"), ] class EventHistoryPage(Page): events = StreamField([ ('event', blocks.StructBlock([ ('title', blocks.CharBlock(max_length=512, required=False)), ('subtitle', blocks.CharBlock(max_length=512, required=False)), ('start_date', blocks.DateBlock()), ('end_date', blocks.DateBlock(required=False, help_text="Leave empty for single day events")), ('location_name', blocks.CharBlock( max_length=256, help_text="Name of the location (first line of address)", required=False)), ('location_city', blocks.CharBlock(max_length=256, help_text="Zip code and city", required=False)), ('image', ImageChooserBlock(required=False)), ('flyer', DocumentChooserBlock(required=False)), ('additional_info_btn_label', blocks.CharBlock(max_length=128, required=False, label='Zusatzinfo Button Text')), ('additional_info_target', blocks.PageChooserBlock(required=False, label='Zusatzinfo Zielseite')), ])), ], null=True, blank=True, use_json_field=True) def get_all_events(self): """ Returns a list of all events, including both manual entries from the StreamField and child EventPage objects, sorted by start_date. """ # Get manual entries from StreamField manual_events = [] for event_block in self.events: manual_events.append({ 'type': 'manual', 'value': event_block.value }) # Get child EventPage objects from core.models import EventPage child_events = [] for child in self.get_children().specific(): if isinstance(child, EventPage): child_events.append({ 'type': 'page', 'value': { 'title': child.title, 'subtitle': child.subtitle, 'start_date': child.start_date, 'end_date': child.end_date, 'location_name': child.location_name, 'location_city': child.location_city, 'image': child.img1_img, 'flyer': child.flyer, 'additional_info_btn_label': '', 'additional_info_target': None, }, 'page': child }) # Combine and sort all events by start_date all_events = manual_events + child_events return sorted(all_events, key=lambda x: x['value']['start_date'], reverse=True) EventHistoryPage.content_panels = [ FieldPanel('title', classname="full title"), FieldPanel('events'), ] class ContactFormField(AbstractFormField): page = ParentalKey('ContactFormPage', related_name='form_fields') class ContactFormPage(WagtailCaptchaEmailForm): form_title = models.CharField(max_length=128, blank=True) person_img = models.ForeignKey( get_image_model(), null=True, blank=True, on_delete=models.SET_NULL, related_name='+' ) person_name = models.CharField(max_length=128, blank=True) person_position = models.CharField(max_length=128, blank=True) address = models.CharField(max_length=128, blank=True) mail_address = models.CharField(max_length=128, blank=True) phone = models.CharField(max_length=128, blank=True) web_address = models.URLField(blank=True) thank_you_text = RichTextField(blank=True) ContactFormPage.content_panels = [ FieldPanel('title', classname="full title"), FieldPanel('form_title'), FieldPanel('person_img'), FieldPanel('person_name'), FieldPanel('person_position'), FieldPanel('address'), FieldPanel('mail_address'), FieldPanel('phone'), FieldPanel('web_address'), InlinePanel('form_fields', label="Form fields"), FieldPanel('thank_you_text'), MultiFieldPanel([ FieldPanel('to_address'), FieldPanel('from_address'), FieldPanel('subject'), ], "Email") ] class UploadedScript(TimeStampedModel): name = models.CharField(max_length=512, verbose_name='Ihr Name') script = models.FileField(upload_to='scripts', verbose_name='Skript') def get_delete_url(self): return reverse('delete_script', kwargs={'pk': self.pk}) def delete(self, *args, **kwargs): storage, path = self.script.storage, self.script.path out = super().delete(*args, **kwargs) storage.delete(path) return out class AboutUsPage(Page): """ A page for displaying company information, team members, mission statements, etc. """ # Company information company_intro = RichTextField( blank=True, help_text="Introductory text about the company" ) # Mission and vision mission_title = models.CharField(max_length=255, blank=True) mission_text = RichTextField(blank=True) vision_title = models.CharField(max_length=255, blank=True) vision_text = RichTextField(blank=True) # Company history history_title = models.CharField(max_length=255, blank=True) history_text = RichTextField(blank=True) # Team members section team_title = models.CharField(max_length=255, blank=True) team_intro = RichTextField(blank=True) # Team members as a StreamField to allow adding multiple team members team_members = StreamField([ ('team_member', blocks.StructBlock([ ('name', blocks.CharBlock(max_length=255)), ('position', blocks.CharBlock(max_length=255)), ('photo', ImageChooserBlock(required=False)), ('bio', blocks.RichTextBlock(required=False)), ('email', blocks.EmailBlock(required=False)), ('linkedin', blocks.URLBlock(required=False)), ('xing', blocks.URLBlock(required=False)), ])), ], null=True, blank=True, use_json_field=True) # Additional content additional_content = StreamField([ ('heading', blocks.CharBlock(form_classname="full title")), ('paragraph', blocks.RichTextBlock()), ('image', ImageChooserBlock()), ('quote', blocks.StructBlock([ ('quote', blocks.TextBlock()), ('attribution', blocks.CharBlock(required=False)), ])), ], null=True, blank=True, use_json_field=True) AboutUsPage.content_panels = [ FieldPanel('title', classname="full title"), FieldPanel('company_intro'), MultiFieldPanel([ FieldPanel('mission_title'), FieldPanel('mission_text'), FieldPanel('vision_title'), FieldPanel('vision_text'), ], heading="Mission and Vision", classname="collapsible"), MultiFieldPanel([ FieldPanel('history_title'), FieldPanel('history_text'), ], heading="Company History", classname="collapsible"), MultiFieldPanel([ FieldPanel('team_title'), FieldPanel('team_intro'), FieldPanel('team_members'), ], heading="Team Members", classname="collapsible"), FieldPanel('additional_content'), ]