import datetime import re import unicodedata import os import uuid from django import forms from django.forms import fields from django.forms import ValidationError from django.core.files.storage import DefaultStorage from django.core.urlresolvers import reverse from django.utils.safestring import mark_safe from django.utils.translation import ugettext_lazy as _ from django.utils.encoding import force_text from django.conf import settings from django.template.loader import render_to_string from taggit.forms import TagWidget from .models import (Emission, Episode, Diffusion, Schedule, SoundFile, NewsItem, Absence, PlaylistElement) from .utils import get_duration from .widgets import DateTimeWidget, DateWidget def slugify(s): s = unicodedata.normalize('NFKD', s).encode('ascii', 'ignore').lower() return re.sub(r'\W+', '-', force_text(s)) class DayAndHourWidget(forms.MultiWidget): def __init__(self, attrs=None): WEEKDAYS = [_('Monday'), _('Tuesday'), _('Wednesday'), _('Thursday'), _('Friday'), _('Saturday'), _('Sunday')] widgets = ( forms.Select(attrs=attrs, choices=([(weekday, WEEKDAYS[weekday]) for weekday in range(7)])), forms.Select(attrs=attrs, choices=([(hour, hour) for hour in range(24)])), forms.Select(attrs=attrs, choices=([(minute, str(minute).zfill(2)) for minute in range(60)])), ) super(DayAndHourWidget, self).__init__(widgets, attrs) def decompress(self, value): if value: return [value.weekday(), value.hour, value.minute] return [None, None, None] def value_from_datadict(self, data, files, name): # we only care about day/hour/minutes, but we conveniently use a # datetime value to store that; we pick 2007 as reference year as # it had its January 1st on a Monday. data_list = [ widget.value_from_datadict(data, files, name + '_%s' % i) for i, widget in enumerate(self.widgets)] if data_list: return datetime.datetime(2007, 1, int(data_list[0])+1, int(data_list[1]), int(data_list[2])) return None class JqueryFileUploadFileInput(forms.FileInput): template_name = 'emissions/upload.html' def get_context(self, name, value, attrs): context = super(JqueryFileUploadFileInput, self).get_context(name, value, attrs) context['widget'].update({ 'upload_url': self.url, 'files': self.files, 'STATIC_URL': settings.STATIC_URL} ) return context def render(self, name, value, attrs=None): output = render_to_string('emissions/upload.html', {'widget': { 'upload_url': self.url, 'files': self.files, 'name': name, 'STATIC_URL': settings.STATIC_URL}}) return mark_safe(output) class JqueryFileUploadInput(forms.MultiWidget): needs_multipart_form = True upload_id_re = re.compile(r'^[a-z0-9A-Z-]+$') upload_id = None def __init__(self, attrs=None, choices=[], max_filename_length=None): self.max_filename_length = max_filename_length widget_list = (forms.HiddenInput(attrs=attrs), JqueryFileUploadFileInput(attrs=attrs)) super(JqueryFileUploadInput, self).__init__(widget_list, attrs) def decompress(self, value): # map python value to widget contents if self.upload_id: pass elif isinstance(value, (list, tuple)) and value and value[0] is not None: self.upload_id = str(value[0]) else: self.upload_id = str(uuid.uuid4()) return [self.upload_id, None] def get_files_for_id(self, upload_id): storage = DefaultStorage() path = os.path.join('upload', upload_id) if not storage.exists(path): return for filepath in storage.listdir(path)[1]: name = os.path.basename(filepath) yield storage.open(os.path.join(path, name)) def value_from_datadict(self, data, files, name): ''' If some file was submitted, that's the value, If a regular hidden_id is present, use it to find uploaded files, otherwise return an empty list ''' upload_id, file_input = super(JqueryFileUploadInput, self).value_from_datadict(data, files, name) if file_input: pass elif JqueryFileUploadInput.upload_id_re.match(upload_id): file_input = list(self.get_files_for_id(upload_id)) else: file_input = [] try: return file_input[0] except IndexError: return None def render(self, name, value, attrs=None): self.decompress(value) self.widgets[1].url = '/upload/%s/' % self.upload_id self.widgets[1].url = reverse('upload', kwargs={'transaction_id': self.upload_id}) if self.max_filename_length: self.widgets[1].url += '?max_filename_length=%d' % self.max_filename_length self.widgets[1].files = '/upload/%s/' % self.get_files_for_id(self.upload_id) output = super(JqueryFileUploadInput, self).render(name, value, attrs) fileinput_id = '%s_%s' % (attrs['id'], '1') return output class EmissionForm(forms.ModelForm): class Meta: model = Emission exclude = ('slug', 'colours', 'has_focus', 'got_focus', 'chat_open', 'podcast_sound_quality') def save(self, commit=True): if not self.instance.slug: self.instance.slug = slugify(self.instance.title) return super(EmissionForm, self).save(commit=commit) class EpisodeForm(forms.ModelForm): class Meta: model = Episode exclude = ('slug', 'has_focus', 'got_focus', 'effective_start', 'effective_end') widgets = {'emission': forms.HiddenInput(), 'tags': TagWidget()} def save(self, commit=True): if not self.instance.slug: base_slug = slugify(self.instance.title) slug = base_slug i = 1 while True: try: Episode.objects.get(slug=slug) except Episode.DoesNotExist: break i += 1 slug = '%s-%s' % (base_slug, i) self.instance.slug = slug return super(EpisodeForm, self).save(commit=commit) class EpisodeNewForm(EpisodeForm): first_diffusion = forms.DateTimeField(label=_('First Diffusion'), widget=DateTimeWidget) second_diffusion = forms.DateTimeField(label=_('Second Diffusion'), widget=DateTimeWidget) third_diffusion = forms.DateTimeField(label=_('Third Diffusion'), widget=DateTimeWidget) fourth_diffusion = forms.DateTimeField(label=_('Fourth Diffusion'), widget=DateTimeWidget) def get_diffusion_fields(self, emission): if emission: schedules = list(Schedule.objects.filter(emission=emission).order_by('datetime')) if len(schedules) > 1 and ( schedules[0].weeks == schedules[1].weeks and schedules[0].datetime.date() == schedules[1].datetime.date()): # special case for daily program with same-day rerun schedules = 2 else: schedules = len([x for x in schedules if x.rerun]) + 1 else: schedules = 1 fields = ['first_diffusion'] if schedules > 1: fields.append('second_diffusion') if schedules > 2: fields.append('third_diffusion') if schedules > 3: fields.append('fourth_diffusion') return fields def __init__(self, *args, **kwargs): super(EpisodeNewForm, self).__init__(*args, **kwargs) emission = kwargs.get('initial', {}).get('emission') diffusion_fields = self.get_diffusion_fields(emission) for field_name in list(self.fields.keys()): if field_name.endswith('_diffusion') and not field_name in diffusion_fields: del self.fields[field_name] def save(self, commit=True): episode = super(EpisodeNewForm, self).save(commit=commit) for field_name in self.get_diffusion_fields(episode.emission): diffusion = Diffusion() diffusion.episode_id = episode.id diffusion.datetime = self.cleaned_data.get(field_name) diffusion.save() return episode class ScheduleForm(forms.ModelForm): class Meta: model = Schedule exclude = () widgets = { 'emission': forms.HiddenInput(), 'datetime': DayAndHourWidget(), } class SoundFileForm(forms.ModelForm): class Meta: model = SoundFile exclude = ('has_focus', 'got_focus', 'duration') widgets = { 'episode': forms.HiddenInput(), 'file': JqueryFileUploadInput(), } def clean(self): super().clean() if self.cleaned_data.get('file'): duration = get_duration(self.cleaned_data['file'].file.name) if not duration: raise ValidationError(_('Invalid file, could not get duration.')) class SoundFileEditForm(forms.ModelForm): class Meta: model = SoundFile exclude = ('has_focus', 'got_focus', 'file', 'duration') widgets = { 'episode': forms.HiddenInput(), } def __init__(self, *args, **kwargs): super(SoundFileEditForm, self).__init__(*args, **kwargs) self.fields.keyOrder = ['title', 'format', 'podcastable', 'fragment', 'license'] class DiffusionForm(forms.ModelForm): class Meta: model = Diffusion exclude = () widgets = { 'episode': forms.HiddenInput(), 'datetime': DateTimeWidget(), } class NewsItemForm(forms.ModelForm): class Meta: model = NewsItem exclude = ('slug', 'has_focus', 'got_focus') widgets = {'tags': TagWidget(), 'date': DateWidget(), 'expiration_date': DateWidget(), 'event_date': DateWidget(), } def __init__(self, *args, **kwargs): super(NewsItemForm, self).__init__(*args, **kwargs) user = kwargs.get('initial', {}).get('user') if not user.is_staff: self.fields['emission'].widget = forms.HiddenInput() def save(self, commit=True): if not self.instance.slug: base_slug = slugify(self.instance.title) slug = base_slug i = 1 while True: try: NewsItem.objects.get(slug=slug) except NewsItem.DoesNotExist: break i += 1 slug = '%s-%s' % (base_slug, i) self.instance.slug = slug return super(NewsItemForm, self).save(commit=commit) class AbsenceDateTimeWidget(forms.Select): def __init__(self, emission, *args, **kwargs): super().__init__(*args, **kwargs) self.choices = list(self.options(emission)) def options(self, emission): dates = [] for i in range(4): if dates: since = dates[-1] + datetime.timedelta(minutes=10) else: since = None dates.append(emission.get_next_planned_date(since)) for date in dates: yield (date, date.strftime('%d/%m/%Y %H:%M')) class AbsenceForm(forms.ModelForm): class Meta: model = Absence exclude = () widgets = { 'emission': forms.HiddenInput(), } def __init__(self, *args, **kwargs): super(AbsenceForm, self).__init__(*args, **kwargs) emission = kwargs.get('initial', {}).get('emission') user = kwargs.get('initial', {}).get('user') self.fields['datetime'].widget.emission = emission if user and user.is_staff: date = emission.get_next_planned_date() self.fields['datetime'].initial = date self.fields['datetime'].widget = DateTimeWidget() else: self.fields['datetime'].widget = AbsenceDateTimeWidget(emission) def save(self, commit=True): t = super(AbsenceForm, self).save(commit=commit) for i, schedule in enumerate( Schedule.objects.filter(emission=self.instance.emission, rerun=True).order_by('datetime')): rerun_date = schedule.get_next_planned_date(self.instance.datetime) rerun_absence = Absence() rerun_absence.emission = self.instance.emission rerun_absence.datetime = rerun_date rerun_absence.save() return t class PlaylistElementForm(forms.ModelForm): class Meta: model = PlaylistElement exclude = ('title', 'notes', 'order') widgets = { 'episode': forms.HiddenInput(), 'sound': JqueryFileUploadInput(), }