import re
from django import forms
from django.forms.utils import ErrorList
from collections import OrderedDict
from coredata.models import Member
from coredata.widgets import CalendarWidget
from ta.models import TUG, TAApplication,TAContract, CoursePreference, TACourse, TAPosting, Skill, \
        CourseDescription, CATEGORY_CHOICES, STATUS_CHOICES, TAContractEmailText
from ta.util import table_row__Form
import itertools, decimal, datetime
from django.forms.formsets import formset_factory
from django.forms.models import BaseInlineFormSet
from pages.forms import WikiField
from coredata.widgets import NotClearableFileInput
from ra.models import Account

class LabelledHidden(forms.HiddenInput):
    A hidden input where the field is displayed, but without any way to edit.
    Used to make fixed fields on TUG non-editable.
    input_type = 'hidden'
    is_hidden = False
    def render(self, name, value, attrs=None, renderer=None):
        res = super(LabelledHidden, self).render(name, value, attrs=attrs, renderer=renderer)
        if value:
            res += str(value)
        return res

class TUGDutyForm(forms.Form):
    label_editable = False
    def __init__(self, *args, **kwargs):
        label = kwargs.pop('label', '')
        super(TUGDutyForm, self).__init__(*args, **kwargs)
        self.label = label
    weekly = forms.DecimalField(label="Weekly hours", required=False)
    weekly.widget.attrs['class'] = 'weekly'
    weekly.manual_css_classes = ['weekly']
    total = forms.DecimalField(label="Total hours", 
            error_messages={'required':'Number of hours is required.'})
    total.widget.attrs['class'] = 'total'
    total.manual_css_classes = ['total']
    comment = forms.CharField(label="Comment", required=False)
    comment.widget.attrs['class'] = 'comment'
    comment.manual_css_classes = ['comment']

class TUGDutyLabelForm(forms.Form):
    label = forms.CharField(label="Other:", 
            error_messages={'required': 'Please specify'})
    label.widget.attrs['class'] = 'label-field'

# doesn't simply subclass TUGDutyForm so that the label will be listed first
class TUGDutyOtherForm(TUGDutyLabelForm, TUGDutyForm):
    label_editable = True
    def __init__(self, *args, **kwargs):
        super(TUGDutyOtherForm, self).__init__(*args, **kwargs)
        self.fields['label'].required = False
        self.fields['total'].required = False
    def as_table_row(self):
        label = self.fields.pop('label')
        html = TUGDutyForm.as_table_row(self)
        #self.fields.insert(0, 'label', label)
        return html
    def clean(self):
        data = self.cleaned_data
        if (data.get('total', None) or data.get('weekly', None)) and not data.get('label', None):
            e = forms.ValidationError('A label is required.')
            self._errors['label'] = self.error_class(e.messages)
            raise forms.ValidationError([])
        if (data.get('label') and not (data.get('total', None))):
            e = forms.ValidationError(self.fields['total'].error_messages['required'])
            self._errors['total'] = self.error_class(e.messages)
            raise forms.ValidationError([])
        return super(TUGDutyOtherForm, self).clean()

class TUGForm(forms.ModelForm):
    userid and offering must be defined or instance must be defined.
    base_units = forms.DecimalField(min_value=0, 
            error_messages={"min_value":"Base units must be positive.",
                            "invalid":"Base units must be a number.",
                            "required":"Base units are required."})
    class Meta:
        model = TUG
        exclude = ('config',)
    def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
                 initial=None, error_class=ErrorList, label_suffix=':',
                 empty_permitted=False, instance=None,
                 offering=None, userid=None, enforced_prep_min=0):
        super(TUGForm, self).__init__(data, files, auto_id, prefix, initial,
                 error_class, label_suffix, empty_permitted, instance)
        # see old revisions (git id 1d1d2f9) for a dropdown
        if userid is not None and offering is not None:
            member = Member.objects.exclude(role='DROP').get(person__userid=userid, offering=offering)
        elif instance is not None:
            member = instance.member
            assert False

        self.enforced_prep_min = enforced_prep_min

        self.initial['member'] = member
        self.fields['member'].widget = forms.widgets.HiddenInput()
        self.subforms = self.__construct_subforms(data, initial, instance)
    def __construct_subforms(self, data, initial, instance):
        # this function is a simplification/clarification of this one liner:
        # return OrderedDict((field, klass(prefix=field, data=data,
        #  initial=(instance.config[field] if instance and field in instance.config 
        #  else initial[field] if initial and field in initial else None), 
        #  label=TUG.config_meta[field]['label'] if field in TUG.config_meta else '')) 
        #  for field, klass in itertools.chain(((f, TUGDutyForm) for f in TUG.regular_fields), 
        #  ((f, TUGDutyOtherForm) for f in TUG.other_fields)))
        field_names_and_formclasses = itertools.chain(
                ((f, TUGDutyForm) for f in TUG.regular_fields),
                ((f, TUGDutyOtherForm) for f in TUG.other_fields))
        get_label = lambda field: TUG.config_meta[field]['label'] if field in TUG.config_meta else ''
        get_initial = lambda field: None
        if instance:
            if initial:
                get_initial = lambda field:(instance.config[field] 
                        if field in instance.config else 
                        initial.get(field, None))
                get_initial = lambda field:instance.config.get(field, None)
        elif initial:
            get_initial = lambda field:initial.get(field, None)
        return OrderedDict(
                 klass(prefix=field, data=data, 
                    for field, klass in field_names_and_formclasses)
    def clean_member(self):
        if self.cleaned_data['member'] != self.initial['member']:
            raise forms.ValidationError("Wrong member")
        return self.cleaned_data['member']
    def is_valid(self):
        return (all(form.is_valid() for form in self.subforms.values())
                and super(TUGForm, self).is_valid())
    def full_clean(self):
        for form in self.subforms.values():
        return super(TUGForm, self).full_clean()
    def clean(self):
        data = super(TUGForm, self).clean()
        get_data = lambda subform: subform.cleaned_data if subform.cleaned_data else subform.initial
        try: data['config'] = OrderedDict((field, get_data(self.subforms[field]))
                for field in TUG.all_fields)
        except AttributeError:
            raise forms.ValidationError([])

        # this can't possibly be the business logic, right?
        #prep_hours = data['config']['prep']['total']
        #if not prep_hours or prep_hours < self.enforced_prep_min:
        #    raise forms.ValidationError('Because this TA has labs or tutorials, you must assign at least %s base units to "Preparation".' % (self.enforced_prep_min))

        return data

    def save(self, *args, **kwargs):
        self.instance.config = self.cleaned_data['config']
        return super(TUGForm, self).save(*args, **kwargs)

class TAApplicationForm(forms.ModelForm):
    sin_default = '000000000'
    class Meta:
        model = TAApplication
        exclude = ('posting', 'course_load', 'person','skills','campus_preferences','rank','late','admin_created', 'config')
        widgets = {'base_units': forms.TextInput(attrs={'size': 5}),
                   'experience': forms.Textarea(attrs={'cols': 50, 'rows': 3}),
                   'course_load': forms.Textarea(attrs={'cols': 50, 'rows': 2}),
                   'other_support': forms.Textarea(attrs={'cols': 50, 'rows': 2}),
                   'comments': forms.Textarea(attrs={'cols': 50, 'rows': 3}),
                   'resume': NotClearableFileInput(),
                   'transcript': NotClearableFileInput(),

    def __init__(self, *args, **kwargs):
        super(TAApplicationForm, self).__init__(*args, **kwargs)
        self.fields['sin'].help_text = 'Social insurance number (required for receiving payments: if you don\'t have a SIN yet, please enter "000000000".)'
        self.fields['sin'].required = True
        self.fields['current_program'].required = True
        self.fields['resume'].required = True
        self.fields['transcript'].required = True

    def add_extra_questions(self, posting):
        if 'extra_questions' in posting.config and len(posting.config['extra_questions']) > 0:
            for question in posting.config['extra_questions']:
                if 'extra_questions' in self.instance.config and question in self.instance.config['extra_questions']:
                    self.fields[question] = forms.CharField(label="Question", help_text=question, widget=forms.Textarea, initial=self.instance.config['extra_questions'][question])
                    self.fields[question] = forms.CharField(label="Question", help_text=question, widget=forms.Textarea)

    def clean_sin(self):
        sin = self.cleaned_data['sin']
        if sin.strip() == '':
            sin = self.sin_default
            sin = re.sub('[ -]+','',str(sin))
            if not re.match('\d{9}$',sin):
                raise forms.ValidationError("Invalid SIN")
        return sin

    def clean_base_units(self):
        bu = self.cleaned_data['base_units']
        if bu > 5 or bu < 1:
            raise forms.ValidationError("BU amount must be in the range 1-5")
        return bu

    #def clean_new_workers_training(self):
    #    training = self.cleaned_data['new_workers_training']
    #    if not training:
    #        raise forms.ValidationError("You must have attended New Workers Training before we can process your application.")
    #    return training

class CoursePreferenceForm(forms.ModelForm):

    class Meta:
        model = CoursePreference
        exclude = ('app', 'rank', 'taken', 'exper')

    def __init__(self, *args, **kwargs):
        super(CoursePreferenceForm, self).__init__(*args, **kwargs)
        crs_field = self.fields['course']
        crs_field.required = False

class TAAcceptanceForm(forms.ModelForm):
    sin = forms.CharField(label="SIN", help_text="Social insurance number")
    class Meta:
        model = TAContract
        fields = ['sin']
class NewTAContractForm(forms.Form):
    application = forms.ModelChoiceField(queryset=TAApplication.objects.none())

class TAContractForm(forms.ModelForm):
    #pay_per_bu = forms.DecimalField(max_digits=8, decimal_places=2)
    #scholarship_per_bu = forms.DecimalField(max_digits=8, decimal_places=2) 
    def __init__(self, *args, **kwargs):
        super(TAContractForm, self).__init__(*args, **kwargs)
        instance = getattr(self, 'instance', None)
        if and instance.sin == '000000000':
            self.fields['sin'].help_text = "Valid SIN is required for receiving payments"

    class Meta:
        model = TAContract
        exclude = ['posting', 'application', 'created_by']
        widgets = {'remarks': forms.Textarea(attrs={'rows': 3, 'cols': 60}),
                   'appointment_start': CalendarWidget,
                   'appointment_end': CalendarWidget}
    def clean_pay_per_bu(self):
        pay = self.cleaned_data['pay_per_bu']
            pay = decimal.Decimal(pay).quantize(decimal.Decimal('1.00'))
        except decimal.InvalidOperation:
            raise forms.ValidationError("Pay per BU values must be numbers")
        return pay
    def clean_scholarship_per_bu(self):
        schol = self.cleaned_data['scholarship_per_bu']
            schol = decimal.Decimal(schol).quantize(decimal.Decimal('1.00'))
        except decimal.InvalidOperation:
            raise forms.ValidationError("Scholarship per BU values must be numbers")
        return schol
    def clean_sin(self):
        sin = self.cleaned_data['sin']
        sin = re.sub('[ -]+','',str(sin))
        if not re.match('\d{9}$',sin):
            raise forms.ValidationError("Invalid SIN")
        return sin

    def clean_appointment_start(self):
        start = self.cleaned_data['appointment_start']
        return start

    def clean_appointment_end(self):
        end = self.cleaned_data['appointment_end']
        if end and 'appointment_start' in self.cleaned_data:
            start = self.cleaned_data['appointment_start']
            if start >= end:
                raise forms.ValidationError("Contracts must end after they start")
        return end

    def clean_pay_start(self):
        start = self.cleaned_data['pay_start']
        return start

    def clean_pay_end(self):
        end = self.cleaned_data['pay_end']
        if 'pay_start' in self.cleaned_data:
            start = self.cleaned_data['pay_start']
            if start >= end:
                raise forms.ValidationError("Contracts must end after they start")
        return end
    def clean_deadline(self):
        deadline = self.cleaned_data['deadline']
        status = self.cleaned_data['status']
        today =
        if status not in ['REJ', 'CAN'] and deadline < today:
            raise forms.ValidationError("Deadline for acceptance cannot be before today")
        return deadline

class TACourseForm(forms.ModelForm):           
    class Meta:
        model = TACourse
        exclude = ('contract',)
        widgets = {'course': forms.Select(attrs={'class': 'course_select'}),
                   'description': forms.Select(attrs={'class': 'desc_select'}),
                   'bu': forms.TextInput(attrs={'class': 'bu_inp'})

class BaseTACourseFormSet(BaseInlineFormSet):    
    def clean(self):
        #check at least one course selected
        count = 0
        if any(self.errors):
        for form in self.forms:
                if form.cleaned_data:
                    count += 1
            except AttributeError:
        if count < 1:
            raise forms.ValidationError("Please select at least one course")
        #check no duplicate course selection
        courses = []
        for form in self.forms:
            if form.cleaned_data and 'course' in form.cleaned_data:
                course = form.cleaned_data['course']
                if(course in courses):
                        raise forms.ValidationError("Duplicate course selection")

# helpers for the TAPostingForm

class PayWidget(forms.MultiWidget):
    "Widget for entering salary/scholarship values"
    template_name = 'ta/pay_widget.html'

    def __init__(self, *args, **kwargs):
        widgets = [forms.TextInput(attrs={'size': 6}) for _ in CATEGORY_CHOICES]
        kwargs['widgets'] = widgets
        super(PayWidget, self).__init__(*args, **kwargs)

    def get_context(self, name, value, attrs):
        ctx = super().get_context(name, value, attrs)
        ctx['label_widgets'] = [(l[0], w) for l, w in zip(CATEGORY_CHOICES, ctx['widget']['subwidgets'])]
        return ctx

    def decompress(self, value):
        # should already be a list: if we get here, have no defaults
        return [0]*len(CATEGORY_CHOICES)

class PayField(forms.MultiValueField):
    "Field for entering salary/scholarship values"
    def __init__(self, *args, **kwargs):
        fields = [forms.CharField() for _ in CATEGORY_CHOICES]
        kwargs['fields'] = fields
        kwargs['widget'] = PayWidget()
        super(PayField, self).__init__(*args, **kwargs)

    def compress(self, values):
        return values

class AccountsWidget(forms.MultiWidget):
    "Widget for selecting Account values"
    template_name = 'ta/accounts_widget.html'
    def __init__(self, *args, **kwargs):
        widgets = [forms.Select() for c in CATEGORY_CHOICES]
        kwargs['widgets'] = widgets
        super(AccountsWidget, self).__init__(*args, **kwargs)
    def get_context(self, name, value, attrs):
        ctx = super().get_context(name, value, attrs)
        ctx['label_widgets'] = [(l[0], w) for l, w in zip(CATEGORY_CHOICES, ctx['widget']['subwidgets'])]
        return ctx

    def decompress(self, value):
        # should already be a list: if we get here, have no defaults
        return [0]*len(CATEGORY_CHOICES)

class AccountsField(forms.MultiValueField):
    "Field for selecting Account values"
    def __init__(self, *args, **kwargs):
        fields = [forms.ModelChoiceField(Account.objects.all()) for _ in CATEGORY_CHOICES]
        kwargs['fields'] = fields
        kwargs['widget'] = AccountsWidget()
        super(AccountsField, self).__init__(*args, **kwargs)

    def compress(self, values):
        return values

class TAPostingForm(forms.ModelForm):
    deadline = forms.DateField(label="Acceptance Deadline", 
        help_text='Default deadline for apointees to accept/decline contracts')
    start = forms.DateField(label="Appointment Start",
        help_text='Default appointment start date for contracts')
    end = forms.DateField(label="Appointment End",
        help_text='Default appointment end date for contracts')
    payroll_start = forms.DateField(label="Payroll Start",
        help_text='Default payroll start date for contracts', required=False)
    payroll_end = forms.DateField(label="Payroll End",
        help_text='Default payroll end date for contracts', required=False)
    salary = PayField(label="Salary per BU",
        help_text="Default pay rates for contracts")
    scholarship = PayField(label="Scholarship per BU", 
        help_text="Default scholarship rates for contracts")
    accounts = AccountsField(label="Position Number", 
        help_text="Default position number for contracts")
    payperiods = forms.DecimalField(label="Pay periods", 
        help_text='Number of pay periods in the semester',
        max_value=20, min_value=1, widget=forms.TextInput(attrs={'size': 5}))
    contact = forms.ChoiceField(label="Contact Person", 
        help_text="Person to give applicants/offers to ask questions.")
    max_courses = forms.IntegerField(label="Maximum courses", 
        help_text="The maximum number of courses an applicant can specify.")
    min_courses = forms.IntegerField(label="Minimum courses", 
        help_text="The minimum number of courses an applicant can specify.")
    excluded = forms.MultipleChoiceField(
        help_text="Courses that should <strong>not</strong> be selectable for TA positions",
        choices=[], required=False, widget=forms.SelectMultiple(attrs={'size': 15}))
    skills = forms.CharField(label="Skills", required=False, widget=forms.Textarea(),
        help_text='Skills to ask applicants about: one per line')
    extra_questions = forms.CharField(label="Extra Questions", required=False,
        help_text='Extra questions to ask applicants: one per line',
    instructions = forms.CharField(label="Instructions", 
        help_text='Additional instructions for students filling out the application.',
        required=False, widget=forms.Textarea())
    hide_campuses = forms.BooleanField(label="Hide Campuses", initial=False, 
        help_text='Do not prompt students for their Campus choice.')
    offer_text = WikiField(label="Offer Text", required=False, 
        help_text='Presented as "More Information About This Offer"; formatted in <a href="/docs/pages">WikiCreole markup</a>.')

    # TODO: sanity-check the dates against semester start/end
    class Meta:
        model = TAPosting
        exclude = ('config',) 
    def __init__(self, *args, **kwargs):
        super(TAPostingForm, self).__init__(*args, **kwargs)
        # populate initial data fron instance.config
        self.initial['salary'] = self.instance.salary()
        self.initial['scholarship'] = self.instance.scholarship()
        self.initial['start'] = self.instance.start()
        self.initial['accounts'] = self.instance.accounts()
        self.initial['end'] = self.instance.end()
        self.initial['payroll_start'] = self.instance.payroll_start()
        self.initial['payroll_end'] = self.instance.payroll_end()
        self.initial['deadline'] = self.instance.deadline()
        self.initial['excluded'] = self.instance.excluded()
        self.initial['max_courses'] = self.instance.max_courses()
        self.initial['min_courses'] = self.instance.min_courses()
        self.initial['payperiods'] = decimal.Decimal(self.instance.payperiods())
        self.initial['contact'] =
        self.initial['offer_text'] = self.instance.offer_text()
        skills = Skill.objects.filter(posting=self.instance)
        self.initial['extra_questions'] = '\n'.join(self.instance.extra_questions())
        self.initial['skills'] = '\n'.join(( for s in skills))
        self.initial['instructions'] = self.instance.instructions()
        self.initial['hide_campuses'] = self.instance.hide_campuses()
    def clean_payperiods(self):
        payperiods = self.cleaned_data['payperiods']
        self.instance.config['payperiods'] = payperiods
        return payperiods

    def clean_contact(self):
        contact = self.cleaned_data['contact']
        self.instance.config['contact'] = contact
        return contact

    def clean_start(self):
        start = self.cleaned_data['start']
        self.instance.config['start'] = str(start)
        return start

    def clean_end(self):
        end = self.cleaned_data['end']
        if 'start' in self.cleaned_data:
            start = self.cleaned_data['start']
            if start >= end:
                raise forms.ValidationError("Contracts must end after they start")
        self.instance.config['end'] = str(end)
        return end

    def clean_payroll_start(self):
        payroll_start = self.cleaned_data['payroll_start']
        if payroll_start:
            self.instance.config['payroll_start'] = str(payroll_start)
        return payroll_start

    def clean_payroll_end(self):
        payroll_end = self.cleaned_data['payroll_end']
        if 'payroll_start' in self.cleaned_data:
            payroll_start = self.cleaned_data['payroll_start']
            if payroll_start:
                if not payroll_end or payroll_start >= payroll_end:
                    raise forms.ValidationError("Payroll periods must end after they start")
        if payroll_end:
            self.instance.config['payroll_end'] = str(payroll_end)
        return payroll_end

    def clean_deadline(self):
        deadline = self.cleaned_data['deadline']
        self.instance.config['deadline'] = str(deadline)
        return deadline
    def clean_opens(self):
        opens = self.cleaned_data['opens']
        #today =
        #if opens < today:
        #    raise forms.ValidationError("Postings cannot open before today")
        return opens

    def clean_closes(self):
        closes = self.cleaned_data['closes']
        #today =
        #if closes <= today:
        #    raise forms.ValidationError("Postings must close after today")
        if 'opens' in self.cleaned_data:
            opens = self.cleaned_data['opens']
            if opens >= closes:
                raise forms.ValidationError("Postings must close after they open")
        return closes
    def clean_salary(self):
        sals = self.cleaned_data['salary']
            sals = [decimal.Decimal(s).quantize(decimal.Decimal('1.00')) for s in sals]
        except decimal.InvalidOperation:
            raise forms.ValidationError("Salary values must be numbers")
        self.instance.config['salary'] = [str(s) for s in sals]
        return sals
    def clean_scholarship(self):
        schols = self.cleaned_data['scholarship']
            schols = [decimal.Decimal(s).quantize(decimal.Decimal('1.00')) for s in schols]
        except decimal.InvalidOperation:
            raise forms.ValidationError("Scholarship values must be numbers")

        self.instance.config['scholarship'] = [str(s) for s in schols]
        return schols

    def clean_accounts(self):
        accounts = self.cleaned_data['accounts']
        self.instance.config['accounts'] = [ for a in accounts]
        return [ for a in accounts]
    def clean_max_courses(self):
        max_courses = self.cleaned_data['max_courses']
        self.instance.config['max_courses'] = max_courses
        return max_courses
    def clean_min_courses(self):
        min_courses = self.cleaned_data['min_courses']
        max_courses = self.cleaned_data['max_courses']
        self.instance.config['min_courses'] = min_courses
        if max_courses < min_courses:
            raise forms.ValidationError("Maximum courses must be greater than Minimum courses")
        return min_courses
    def clean_excluded(self):
        excluded = self.cleaned_data['excluded']
        excluded = [int(e) for e in excluded]
        self.instance.config['excluded'] = excluded
        return excluded

    def clean_offer_text(self):
        offer_text = self.cleaned_data['offer_text']
        self.instance.config['offer_text'] = offer_text
        return offer_text
    def clean_skills(self):
        skills = self.cleaned_data['skills']
        skills = [s.strip() for s in skills.split("\n") if len(s.strip()) > 0]
        old_skills = Skill.objects.filter(posting=self.instance)
        res = []
        for i, skill in enumerate(skills):
            if len(old_skills) < i+1:
                # nothing existing
                new = Skill(posting=self.instance, name=skill, position=i)
                # update old
                old = old_skills[i]
       = skill
        return res

    def clean_extra_questions(self):
        extra_questions = self.cleaned_data['extra_questions']
        extra_questions = [q.strip() for q in extra_questions.split('\n') if len(q.strip()) > 0 ]
        self.instance.config['extra_questions'] = extra_questions
        return extra_questions

    def clean_instructions(self):
        instructions = self.cleaned_data['instructions']
        self.instance.config['instructions'] = instructions
        return instructions

    def clean_hide_campuses(self):
        hide_campuses = self.cleaned_data['hide_campuses']
        self.instance.config['hide_campuses'] = hide_campuses
        return hide_campuses

class BUForm(forms.Form):
    students = forms.IntegerField(min_value=0, max_value=1000)
    bus = forms.DecimalField(min_value=0, max_digits=5, decimal_places=2, widget=forms.TextInput(attrs={'class' : 'smallnumberinput'}))

BUFormSet = formset_factory(BUForm, extra=10)
                 ('100', '100-level'),
                 ('200', '200-level'),
                 ('300', '300-level'),
                 ('400', '400-level'),
class TAPostingBUForm(forms.Form):
    level = forms.ChoiceField(choices=LEVEL_CHOICES)

class AssignBUForm(forms.Form):
    rank = forms.IntegerField(min_value=0, label="rank", widget=forms.TextInput(attrs={'class': 'smallnumberinput'}))
    rank.widget.attrs['size'] = '2'
    bu = forms.DecimalField(min_value=0, max_digits=5, decimal_places=2, required=False, widget=forms.TextInput(attrs={'class': 'smallnumberinput'}))
    bu.widget.attrs['class'] = 'bu_inp'
    bu.widget.attrs['size'] = '3'

# fake contract statuses to allow selecting applicants in the form
APPLICANT_STATUSES = (('_APPLIC', 'Applicants (not late)'), ('_LATEAPP', 'Late Applicants'))
class TAContactForm(forms.Form):
    statuses = forms.MultipleChoiceField(choices=APPLICANT_STATUSES+STATUS_CHOICES, help_text="TAs to contact (according to contract status)")
    subject = forms.CharField()
    text = forms.CharField(widget=forms.Textarea(), help_text='Message body. <a href="">Textile markup</a> allowed.')
    url = forms.URLField(label="URL", required=False, help_text='Link to include in the message. (optional)')

class CourseDescriptionForm(forms.ModelForm):
    class Meta:
        model = CourseDescription
        exclude = ('config','hidden') 

class TAContractEmailTextForm(forms.ModelForm):
    class Meta:
        model = TAContractEmailText
        exclude = ()
        widgets = {
            'content': forms.Textarea(attrs={'rows': '20', 'cols': '60'}),