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 = '''
{0}
Your browser does not support the video tag.
'''
else:
player_code = '''
{0}
Your browser does not support the audio element.
'''
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'),
]