import datetime from email.utils import parseaddr from django import forms from django.conf import settings from django.db.models import Q from django.forms.util import ErrorList from django.core.validators import email_re from django.template.loader import render_to_string from ietf.idtracker.models import PersonOrOrgInfo from ietf.liaisons.accounts import (can_add_outgoing_liaison, can_add_incoming_liaison, get_person_for_user, is_secretariat, is_sdo_liaison_manager) from ietf.liaisons.models import LiaisonDetail, Uploads, OutgoingLiaisonApproval, SDOs from ietf.liaisons.utils import IETFHM from ietf.liaisons.widgets import (FromWidget, ReadOnlyWidget, ButtonWidget, ShowAttachmentsWidget, RelatedLiaisonWidget) class LiaisonForm(forms.ModelForm): from_field = forms.ChoiceField(widget=FromWidget, label=u'From') replyto = forms.CharField(label=u'Reply to') organization = forms.ChoiceField() to_poc = forms.CharField(widget=ReadOnlyWidget, label="POC", required=False) cc1 = forms.CharField(widget=forms.Textarea, label="CC", required=False, help_text='Please insert one email address per line') purpose_text = forms.CharField(widget=forms.Textarea, label='Other purpose') deadline_date = forms.DateField(label='Deadline') submitted_date = forms.DateField(label='Submission date', initial=datetime.date.today()) title = forms.CharField(label=u'Title') attachments = forms.CharField(label='Attachments', widget=ShowAttachmentsWidget, required=False) attach_title = forms.CharField(label='Title', required=False) attach_file = forms.FileField(label='File', required=False) attach_button = forms.CharField(label='', widget=ButtonWidget(label='Attach', show_on='id_attachments', require=['id_attach_title', 'id_attach_file'], required_label='title and file'), required=False) related_to = forms.ModelChoiceField(LiaisonDetail.objects.all(), label=u'Related Liaison', widget=RelatedLiaisonWidget, required=False) fieldsets = [('From', ('from_field', 'replyto')), ('To', ('organization', 'to_poc')), ('Other email addresses', ('response_contact', 'technical_contact', 'cc1')), ('Purpose', ('purpose', 'purpose_text', 'deadline_date')), ('References', ('related_to', )), ('Liaison Statement', ('title', 'submitted_date', 'body', 'attachments')), ('Add attachment', ('attach_title', 'attach_file', 'attach_button')), ] class Meta: model = LiaisonDetail class Media: js = ("/js/jquery-1.5.1.min.js", "/js/jquery-ui-1.8.11.custom.min.js", "/js/liaisons.js", ) css = {'all': ("/css/liaisons.css", "/css/jquery-ui-themes/jquery-ui-1.8.11.custom.css")} def __init__(self, user, *args, **kwargs): self.user = user self.fake_person = None self.person = get_person_for_user(user) if kwargs.get('data', None): kwargs['data'].update({'person': self.person.pk}) if is_secretariat(self.user) and 'from_fake_user' in kwargs['data'].keys(): self.fake_person = PersonOrOrgInfo.objects.get(pk=kwargs['data']['from_fake_user']) kwargs['data'].update({'person': self.fake_person.pk}) super(LiaisonForm, self).__init__(*args, **kwargs) self.hm = IETFHM self.set_from_field() self.set_replyto_field() self.set_organization_field() def __unicode__(self): return self.as_div() def get_post_only(self): return False def set_required_fields(self): purpose = self.data.get('purpose', None) if purpose == '5': self.fields['purpose_text'].required=True else: self.fields['purpose_text'].required=False if purpose in ['1', '2']: self.fields['deadline_date'].required=True else: self.fields['deadline_date'].required=False def reset_required_fields(self): self.fields['purpose_text'].required=True self.fields['deadline_date'].required=True def set_from_field(self): assert NotImplemented def set_replyto_field(self): email = self.person.email() self.fields['replyto'].initial = email and email[1] def set_organization_field(self): assert NotImplemented def as_div(self): return render_to_string('liaisons/liaisonform.html', {'form': self}) def get_fieldsets(self): if not self.fieldsets: yield dict(name=None, fields=self) else: for fieldset, fields in self.fieldsets: fieldset_dict = dict(name=fieldset, fields=[]) for field_name in fields: if field_name in self.fields.keyOrder: fieldset_dict['fields'].append(self[field_name]) if not fieldset_dict['fields']: # if there is no fields in this fieldset, we continue to next fieldset continue yield fieldset_dict def full_clean(self): self.set_required_fields() super(LiaisonForm, self).full_clean() self.reset_required_fields() def has_attachments(self): for key in self.files.keys(): if key.startswith('attach_file_') and key.replace('file', 'title') in self.data.keys(): return True return False def check_email(self, value): if not value: return emails = value.split(',') for email in emails: name, addr = parseaddr(email) if not email_re.search(addr): raise forms.ValidationError('Invalid email address: %s' % addr) def clean_response_contact(self): value = self.cleaned_data.get('response_contact', None) self.check_email(value) return value def clean_technical_contact(self): value = self.cleaned_data.get('technical_contact', None) self.check_email(value) return value def clean_reply_to(self): value = self.cleaned_data.get('reply_to', None) self.check_email(value) return value def clean(self): if not self.cleaned_data.get('body', None) and not self.has_attachments(): self._errors['body'] = ErrorList([u'You must provide a body or attachment files']) self._errors['attachments'] = ErrorList([u'You must provide a body or attachment files']) return self.cleaned_data def get_from_entity(self): organization_key = self.cleaned_data.get('from_field') return self.hm.get_entity_by_key(organization_key) def get_to_entity(self): organization_key = self.cleaned_data.get('organization') return self.hm.get_entity_by_key(organization_key) def get_poc(self, organization): return ', '.join([i.email()[1] for i in organization.get_poc()]) def clean_cc1(self): value = self.cleaned_data.get('cc1', '') result = [] errors = [] for address in value.split('\n'): address = address.strip(); if not address: continue try: self.check_email(address) except forms.ValidationError: errors.append(address) result.append(address) if errors: raise forms.ValidationError('Invalid email addresses: %s' % ', '.join(errors)) return ','.join(result) def get_cc(self, from_entity, to_entity): #Old automatic Cc code, now we retrive it from cleaned_data #persons = to_entity.get_cc(self.person) #persons += from_entity.get_from_cc(self.person) #return ', '.join(['%s <%s>' % i.email() for i in persons]) cc = self.cleaned_data.get('cc1', '') return cc def save(self, *args, **kwargs): liaison = super(LiaisonForm, self).save(*args, **kwargs) self.save_extra_fields(liaison) self.save_attachments(liaison) return liaison def save_extra_fields(self, liaison): now = datetime.datetime.now() liaison.last_modified_date = now from_entity = self.get_from_entity() liaison.from_raw_body = from_entity.name liaison.from_raw_code = self.cleaned_data.get('from_field') organization = self.get_to_entity() liaison.to_raw_code = self.cleaned_data.get('organization') liaison.to_body = organization.name liaison.to_poc = self.get_poc(organization) liaison.submitter_name, liaison.submitter_email = self.person.email() liaison.cc1 = self.get_cc(from_entity, organization) liaison.save() def save_attachments(self, instance): for key in self.files.keys(): title_key = key.replace('file', 'title') if not key.startswith('attach_file_') or not title_key in self.data.keys(): continue attached_file = self.files.get(key) extension=attached_file.name.rsplit('.', 1) if len(extension) > 1: extension = '.' + extension[1] else: extension = '' attach = Uploads.objects.create( file_title = self.data.get(title_key), person = self.person, detail = instance, file_extension = extension, ) attach_file = open('%sfile%s%s' % (settings.LIAISON_ATTACH_PATH, attach.pk, attach.file_extension), 'w') attach_file.write(attached_file.read()) attach_file.close() def clean_title(self): title = self.cleaned_data.get('title', None) if self.instance and self.instance.pk: exclude_filter = {'pk': self.instance.pk} else: exclude_filter = {} exists = bool(LiaisonDetail.objects.exclude(**exclude_filter).filter(title__iexact=title).count()) if exists: raise forms.ValidationError('A liaison statement with the same title has previously been submitted.') return title class IncomingLiaisonForm(LiaisonForm): def set_from_field(self): if is_secretariat(self.user): sdos = SDOs.objects.all() else: sdo_managed = [i.sdo for i in self.person.liaisonmanagers_set.all()] sdo_authorized = [i.sdo for i in self.person.sdoauthorizedindividual_set.all()] sdos = set(sdo_managed).union(sdo_authorized) self.fields['from_field'].choices = [('sdo_%s' % i.pk, i.sdo_name) for i in sdos] self.fields['from_field'].widget.submitter = unicode(self.person) def set_organization_field(self): self.fields['organization'].choices = self.hm.get_all_incoming_entities() def get_post_only(self): from_entity = self.get_from_entity() if is_secretariat(self.user) or self.person.sdoauthorizedindividual_set.filter(sdo=from_entity.obj): return False return True def clean(self): if 'send' in self.data.keys() and self.get_post_only(): self._errors['from_field'] = ErrorList([u'As an IETF Liaison Manager you can not send an incoming liaison statements, you only can post them']) return super(IncomingLiaisonForm, self).clean() class OutgoingLiaisonForm(LiaisonForm): to_poc = forms.CharField(label="POC", required=True) approved = forms.BooleanField(label="Obtained prior approval", required=False) other_organization = forms.CharField(label="Other SDO", required=True) def get_to_entity(self): organization_key = self.cleaned_data.get('organization') organization = self.hm.get_entity_by_key(organization_key) if organization_key == 'othersdo' and self.cleaned_data.get('other_organization', None): organization.name=self.cleaned_data['other_organization'] return organization def set_from_field(self): if is_secretariat(self.user): self.fields['from_field'].choices = self.hm.get_all_incoming_entities() elif is_sdo_liaison_manager(self.person): self.fields['from_field'].choices = self.hm.get_all_incoming_entities() all_entities = [] for i in self.hm.get_entities_for_person(self.person): all_entities += i[1] if all_entities: self.fields['from_field'].widget.full_power_on = [i[0] for i in all_entities] self.fields['from_field'].widget.reduced_to_set = ['sdo_%s' % i.sdo.pk for i in self.person.liaisonmanagers_set.all().distinct()] else: self.fields['from_field'].choices = self.hm.get_entities_for_person(self.person) self.fields['from_field'].widget.submitter = unicode(self.person) self.fieldsets[0] = ('From', ('from_field', 'replyto', 'approved')) def set_organization_field(self): # If the user is a liaison manager and is nothing more, reduce the To field to his SDOs if not self.hm.get_entities_for_person(self.person) and is_sdo_liaison_manager(self.person): sdos = [i.sdo for i in self.person.liaisonmanagers_set.all().distinct()] self.fields['organization'].choices = [('sdo_%s' % i.pk, i.sdo_name) for i in sdos] else: self.fields['organization'].choices = self.hm.get_all_outgoing_entities() self.fieldsets[1] = ('To', ('organization', 'other_organization', 'to_poc')) def set_required_fields(self): super(OutgoingLiaisonForm, self).set_required_fields() organization = self.data.get('organization', None) if organization == 'othersdo': self.fields['other_organization'].required=True else: self.fields['other_organization'].required=False def reset_required_fields(self): super(OutgoingLiaisonForm, self).reset_required_fields() self.fields['other_organization'].required=True def get_poc(self, organization): return self.cleaned_data['to_poc'] def save_extra_fields(self, liaison): super(OutgoingLiaisonForm, self).save_extra_fields(liaison) from_entity = self.get_from_entity() needs_approval = from_entity.needs_approval(self.person) if not needs_approval or self.cleaned_data.get('approved', False): approved = True approval_date = datetime.datetime.now() else: approved = False approval_date = None approval = OutgoingLiaisonApproval.objects.create( approved = approved, approval_date = approval_date) liaison.approval = approval liaison.save() def clean_to_poc(self): value = self.cleaned_data.get('to_poc', None) self.check_email(value) return value def clean_organization(self): to_code = self.cleaned_data.get('organization', None) from_code = self.cleaned_data.get('from_field', None) if not to_code or not from_code: return to_code all_entities = [] person = self.fake_person or self.person for i in self.hm.get_entities_for_person(person): all_entities += i[1] # If the from entity is one in which the user has full privileges the to entity could be anyone if from_code in [i[0] for i in all_entities]: return to_code sdo_codes = ['sdo_%s' % i.sdo.pk for i in person.liaisonmanagers_set.all().distinct()] if to_code in sdo_codes: return to_code entity = self.get_to_entity() entity_name = entity and entity.name or to_code if self.fake_person: raise forms.ValidationError('%s is not allowed to send a liaison to: %s' % (self.fake_person, entity_name)) else: raise forms.ValidationError('You are not allowed to send a liaison to: %s' % entity_name) class EditLiaisonForm(LiaisonForm): from_field = forms.CharField(widget=forms.TextInput, label=u'From') replyto = forms.CharField(label=u'Reply to', widget=forms.TextInput) organization = forms.CharField(widget=forms.TextInput) to_poc = forms.CharField(widget=forms.TextInput, label="POC", required=False) cc1 = forms.CharField(widget=forms.TextInput, label="CC", required=False) class Meta: model = LiaisonDetail fields = ('from_raw_body', 'to_body', 'to_poc', 'cc1', 'last_modified_date', 'title', 'response_contact', 'technical_contact', 'purpose_text', 'body', 'deadline_date', 'purpose', 'replyto', 'related_to') def __init__(self, *args, **kwargs): super(EditLiaisonForm, self).__init__(*args, **kwargs) self.edit = True self.initial.update({'attachments': self.instance.uploads_set.all()}) self.fields['submitted_date'].initial = self.instance.submitted_date def set_from_field(self): self.fields['from_field'].initial = self.instance.from_body def set_replyto_field(self): self.fields['replyto'].initial = self.instance.replyto def set_organization_field(self): self.fields['organization'].initial = self.instance.to_body def save_extra_fields(self, liaison): now = datetime.datetime.now() liaison.last_modified_date = now liaison.from_raw_body = self.cleaned_data.get('from_field') liaison.to_body = self.cleaned_data.get('organization') liaison.to_poc = self.cleaned_data['to_poc'] liaison.cc1 = self.cleaned_data['cc1'] liaison.save() def liaison_form_factory(request, **kwargs): user = request.user force_incoming = 'incoming' in request.GET.keys() liaison = kwargs.pop('liaison', None) if liaison: return EditLiaisonForm(user, instance=liaison, **kwargs) if not force_incoming and can_add_outgoing_liaison(user): return OutgoingLiaisonForm(user, **kwargs) elif can_add_incoming_liaison(user): return IncomingLiaisonForm(user, **kwargs) return None if settings.USE_DB_REDESIGN_PROXY_CLASSES: from ietf.liaisons.formsREDESIGN import *