11import datetime as dt
2- import socket
32from decimal import Decimal
4- from urllib .parse import urlparse
53
64from django import forms
75from django .conf import settings
8- from django .core .exceptions import ValidationError
96from django .core .files .base import ContentFile
107from django .core .validators import RegexValidator
11- from django .db .models import F , Q
128from django .forms import inlineformset_factory
139from django .utils .text import format_lazy
1410from django .utils .translation import gettext_lazy as _
2723from eventyay .common .forms .widgets import (
2824 EnhancedSelect ,
2925 EnhancedSelectMultiple ,
30- HtmlDateInput ,
3126 HtmlDateTimeInput ,
32- TextInputWithAddon ,
3327)
3428from eventyay .common .text .css import validate_css
3529from eventyay .common .text .phrases import phrases
3630from eventyay .base .models import Event , EventExtraLink
3731from eventyay .orga .forms .widgets import HeaderSelect , MultipleLanguagesWidget
38- from eventyay .base .models import Availability , TalkSlot
3932from eventyay .base .models import ReviewPhase , ReviewScore , ReviewScoreCategory
4033
4134ENCRYPTED_PASSWORD_PLACEHOLDER = '*' * 24
4639)
4740
4841
49- def make_naive (moment ):
50- return dt .datetime (
51- year = moment .year ,
52- month = moment .month ,
53- day = moment .day ,
54- hour = moment .hour ,
55- minute = moment .minute ,
56- )
57-
58-
5942class EventForm (ReadOnlyFlag , I18nHelpText , JsonSubfieldMixin , I18nModelForm ):
60- locales = forms .MultipleChoiceField (
61- label = _ ('Active languages' ),
62- choices = [],
63- widget = MultipleLanguagesWidget ,
64- help_text = _ (
65- 'Users will be able to use eventyay in these languages, and you will be able to provide all texts in these'
66- ' languages. If you don’t provide a text in the language a user selects, it will be shown in your event’s'
67- ' default language instead.'
68- ),
69- )
70- content_locales = forms .MultipleChoiceField (
71- label = _ ('Content languages' ),
72- choices = [],
73- widget = EnhancedSelectMultiple ,
74- help_text = _ ('Users will be able to submit proposals in these languages.' ),
75- )
7643 custom_css_text = forms .CharField (
7744 required = False ,
7845 widget = forms .Textarea (),
@@ -138,57 +105,12 @@ class EventForm(ReadOnlyFlag, I18nHelpText, JsonSubfieldMixin, I18nModelForm):
138105 def __init__ (self , * args , ** kwargs ):
139106 self .is_administrator = kwargs .pop ('is_administrator' , False )
140107 super ().__init__ (* args , ** kwargs )
141- site_url = settings .SITE_URL .split ('://' )[- 1 ]
142- site_url = f'<code>{ site_url } </code>'
143- self .fields ['custom_domain' ].help_text += '. ' + _ (
144- 'Make sure to point a CNAME record from your domain to {site_url}.'
145- ).format (site_url = site_url )
146- self .initial ['locales' ] = self .instance .locale_array .split (',' )
147- self .initial ['content_locales' ] = self .instance .content_locale_array .split (',' )
148108 self .initial ['custom_css_text' ] = self .instance .custom_css .read ().decode () if self .instance .custom_css else ''
149109 self .fields ['show_featured' ].help_text = (
150110 str (self .fields ['show_featured' ].help_text )
151111 + ' '
152112 + str (_ ('You can find the page <a {href}>here</a>.' )).format (href = f'href="{ self .instance .urls .featured } "' )
153113 )
154- if self .instance .custom_domain :
155- self .fields ['slug' ].widget .addon_before = f'{ self .instance .custom_domain } /'
156- if not self .is_administrator :
157- self .fields ['slug' ].disabled = True
158- self .fields ['slug' ].help_text = _ (
159- 'Please contact your administrator if you need to change the short name of your event.'
160- )
161- self .fields ['date_to' ].help_text = _ (
162- 'Any sessions you have scheduled already will be moved if you change the event dates. You will have to release a new schedule version to notify all speakers.'
163- )
164- self .fields ['locales' ].choices = [
165- choice
166- for choice in settings .LANGUAGES
167- if settings .LANGUAGES_INFORMATION [choice [0 ]].get ('visible' , True )
168- or choice [0 ] in self .instance .plugin_locales
169- ]
170- self .fields ['content_locales' ].choices = self .instance .available_content_locales
171-
172- def clean_custom_domain (self ):
173- data = self .cleaned_data ['custom_domain' ]
174- if not data :
175- return data
176- data = data .lower ()
177- if data in (urlparse (settings .SITE_URL ).hostname , settings .SITE_URL ):
178- raise ValidationError (_ ('Please do not choose the default domain as custom event domain.' ))
179- if not data .startswith ('https://' ):
180- data = data [len ('http://' ) :] if data .startswith ('http://' ) else data
181- data = 'https://' + data
182- data = data .rstrip ('/' )
183- try :
184- socket .gethostbyname (data [len ('https://' ) :])
185- except OSError :
186- raise forms .ValidationError (
187- _ (
188- 'The domain “{domain}” does not have a name server entry at this time. Please make sure the domain is working before configuring it here.'
189- ).format (domain = data )
190- )
191- return data
192114
193115 def clean_custom_css (self ):
194116 if self .cleaned_data .get ('custom_css' ) or self .files .get ('custom_css' ):
@@ -215,103 +137,23 @@ def clean_custom_css_text(self):
215137
216138 def clean (self ):
217139 data = super ().clean ()
218- date_from = data .get ('date_from' )
219- date_to = data .get ('date_to' )
220- if date_from and date_to and date_from > date_to :
221- error = forms .ValidationError (phrases .orga .event_date_start_invalid )
222- self .add_error ('date_from' , error )
223- if data .get ('locale' ) not in data .get ('locales' , []):
224- error = forms .ValidationError (
225- _ ('Your default language needs to be one of your active languages.' ),
226- )
227- self .add_error ('locale' , error )
228140 return data
229141
230142 def save (self , * args , ** kwargs ):
231- self .instance .locale_array = ',' .join (self .cleaned_data ['locales' ])
232- self .instance .content_locale_array = ',' .join (self .cleaned_data ['content_locales' ])
233- if any (key in self .changed_data for key in ('date_from' , 'date_to' )):
234- self .change_dates ()
235- if 'timezone' in self .changed_data :
236- self .change_timezone ()
237143 result = super ().save (* args , ** kwargs )
238- css_text = self .cleaned_data [ 'custom_css_text' ]
144+ css_text = self .cleaned_data . get ( 'custom_css_text' , '' )
239145 for image_field in ('logo' , 'header_image' ):
240146 if image_field in self .changed_data :
241147 self .instance .process_image (image_field )
242148 if css_text and 'custom_css_text' in self .changed_data :
243149 self .instance .custom_css .save (self .instance .slug + '.css' , ContentFile (css_text ))
244150 return result
245151
246- def change_dates (self ):
247- """Changes dates of current WIP slots, or deschedules them."""
248-
249- old_instance = Event .objects .get (pk = self .instance .pk )
250- if not self .instance .wip_schedule .talks .filter (start__isnull = False ).exists ():
251- return
252- new_date_from = self .cleaned_data ['date_from' ]
253- new_date_to = self .cleaned_data ['date_to' ]
254- start_delta = new_date_from - old_instance .date_from
255- end_delta = new_date_to - old_instance .date_to
256- shortened = (new_date_to - new_date_from ) < (old_instance .date_to - old_instance .date_from )
257-
258- if start_delta and end_delta :
259- # The event was moved, and we will move all talks with it.
260- self ._move_by (start_delta )
261-
262- # Otherwise, the event got longer, no need to do anything.
263- # We *could* move all talks towards the new start date, but I'm
264- # not convinced that this is the actual use case.
265- # I think it's more likely that people add a new day to the start.
266- if shortened :
267- # The event was shortened, de-schedule all talks outside the range
268- self .instance .wip_schedule .talks .filter (
269- Q (start__date__gt = new_date_to ) | Q (start__date__lt = new_date_from ),
270- ).update (start = None , end = None , room = None )
271- Availability .objects .filter (
272- Q (end__date__gt = new_date_to ) | Q (start__date__lt = new_date_from ),
273- event = self .instance .event ,
274- ).delete ()
275-
276- def change_timezone (self ):
277- """Changes times of all current wip slots, on the assumption that a
278- change in timezone is usually not intentional, and people would like to
279- keep the apparent time rather the absolute one."""
280-
281- old_instance = Event .objects .get (pk = self .instance .pk )
282- first_slot = self .instance .wip_schedule .talks .filter (start__isnull = False ).first ()
283- if not first_slot :
284- return
285-
286- old_start = make_naive (first_slot .start .astimezone (old_instance .tz ))
287- new_start = make_naive (first_slot .start .astimezone (self .instance .tz ))
288-
289- delta = old_start - new_start
290- if delta :
291- self ._move_by (delta , past = True )
292-
293- def _move_by (self , delta , past = False ):
294- if past :
295- talk_queryset = TalkSlot .objects .filter (schedule__event = self .instance )
296- else :
297- talk_queryset = self .instance .wip_schedule .talks
298- for key in ('start' , 'end' ):
299- filt = {f'{ key } __isnull' : False }
300- update = {key : F (key ) + delta }
301- talk_queryset .filter (** filt ).update (** update )
302- Availability .objects .filter (event = self .instance ).filter (** filt ).update (** update )
303152
304153 class Meta :
305154 model = Event
306155 fields = [
307- 'name' ,
308- 'slug' ,
309- 'date_from' ,
310- 'date_to' ,
311- 'timezone' ,
312156 'email' ,
313- 'locale' ,
314- 'custom_domain' ,
315157 'primary_color' ,
316158 'custom_css' ,
317159 'logo' ,
@@ -324,13 +166,6 @@ class Meta:
324166 'header_image' : ImageField ,
325167 'primary_color' : ColorField ,
326168 }
327- widgets = {
328- 'date_from' : HtmlDateInput (attrs = {'data-date-before' : '#id_date_to' }),
329- 'date_to' : HtmlDateInput (attrs = {'data-date-after' : '#id_date_from' }),
330- 'locale' : EnhancedSelect ,
331- 'timezone' : EnhancedSelect ,
332- 'slug' : TextInputWithAddon (addon_before = settings .SITE_URL + '/' ),
333- }
334169 json_fields = {
335170 'imprint_url' : 'display_settings' ,
336171 'show_schedule' : 'feature_flags' ,
0 commit comments