class SearchLiaisonForm(forms.Form): '''Expects initial keyword argument queryset which then gets filtered based on form data''' text = forms.CharField(required=False) scope = forms.ChoiceField(choices=(("all", "All text fields"), ("title", "Title field")), required=False, initial='title', widget=forms.RadioSelect(renderer=RadioRenderer)) source = forms.CharField(required=False) destination = forms.CharField(required=False) start_date = DatepickerDateField(date_format="yyyy-mm-dd", picker_settings={"autoclose": "1" }, label='Start date', required=False) end_date = DatepickerDateField(date_format="yyyy-mm-dd", picker_settings={"autoclose": "1" }, label='End date', required=False) def __init__(self, *args, **kwargs): self.queryset = kwargs.pop('queryset') super(SearchLiaisonForm, self).__init__(*args, **kwargs) def get_results(self): results = self.queryset if self.is_bound: query = self.cleaned_data.get('text') if query: q = (Q(title__icontains=query) | Q(from_contact__address__icontains=query) | Q(to_contacts__icontains=query) | Q(other_identifiers__icontains=query) | Q(body__icontains=query) | Q(attachments__title__icontains=query,liaisonstatementattachment__removed=False) | Q(technical_contacts__icontains=query) | Q(action_holder_contacts__icontains=query) | Q(cc_contacts=query) | Q(response_contacts__icontains=query)) results = results.filter(q) source = self.cleaned_data.get('source') if source: source_list = source.split(',') if len(source_list) > 1: results = results.filter(Q(from_groups__acronym__in=source_list)) else: results = results.filter(Q(from_groups__name__icontains=source) | Q(from_groups__acronym__iexact=source)) destination = self.cleaned_data.get('destination') if destination: destination_list = destination.split(',') if len(destination_list) > 1: results = results.filter(Q(to_groups__acronym__in=destination_list)) else: results = results.filter(Q(to_groups__name__icontains=destination) | Q(to_groups__acronym__iexact=destination)) start_date = self.cleaned_data.get('start_date') end_date = self.cleaned_data.get('end_date') events = None if start_date: events = LiaisonStatementEvent.objects.filter(type='posted', time__gte=start_date) if end_date: events = events.filter(time__lte=end_date) elif end_date: events = LiaisonStatementEvent.objects.filter(type='posted', time__lte=end_date) if events: results = results.filter(liaisonstatementevent__in=events) results = results.distinct().order_by('title') return results
def __init__(self, *args, **kwargs): super(AddUnavailablePeriodForm, self).__init__(*args, **kwargs) self.fields["start_date"] = DatepickerDateField( date_format="yyyy-mm-dd", picker_settings={"autoclose": "1"}, label=self.fields["start_date"].label, help_text=self.fields["start_date"].help_text, required=self.fields["start_date"].required) self.fields["end_date"] = DatepickerDateField( date_format="yyyy-mm-dd", picker_settings={"autoclose": "1"}, label=self.fields["end_date"].label, help_text=self.fields["end_date"].help_text, required=self.fields["end_date"].required) self.fields['availability'].widget = forms.RadioSelect( choices=UnavailablePeriod.LONG_AVAILABILITY_CHOICES)
def __init__(self, start_date, *args, **kwargs): super(EndUnavailablePeriodForm, self).__init__(*args, **kwargs) self.fields["end_date"] = DatepickerDateField( date_format="yyyy-mm-dd", picker_settings={ "autoclose": "1", "start-date": start_date.isoformat() if start_date else "" }) self.start_date = start_date
class InterimSessionModelForm(forms.ModelForm): date = DatepickerDateField(date_format="yyyy-mm-dd", picker_settings={"autoclose": "1"}, label='Date', required=False) time = forms.TimeField(widget=forms.TimeInput(format='%H:%M'), required=True) requested_duration = CustomDurationField(required=True) end_time = forms.TimeField(required=False) remote_instructions = forms.CharField(max_length=1024, required=True) agenda = forms.CharField(required=False, widget=forms.Textarea, strip=False) agenda_note = forms.CharField(max_length=255, required=False) class Meta: model = Session fields = ('date', 'time', 'requested_duration', 'end_time', 'remote_instructions', 'agenda', 'agenda_note') def __init__(self, *args, **kwargs): if 'user' in kwargs: self.user = kwargs.pop('user') if 'group' in kwargs: self.group = kwargs.pop('group') if 'is_approved_or_virtual' in kwargs: self.is_approved_or_virtual = kwargs.pop('is_approved_or_virtual') super(InterimSessionModelForm, self).__init__(*args, **kwargs) self.is_edit = bool(self.instance.pk) # setup fields that aren't intrinsic to the Session object if self.is_edit: self.initial['date'] = self.instance.official_timeslotassignment().timeslot.time self.initial['time'] = self.instance.official_timeslotassignment().timeslot.time if self.instance.agenda(): doc = self.instance.agenda() path = os.path.join(doc.get_file_path(), doc.filename_with_rev()) self.initial['agenda'] = get_document_content(os.path.basename(path), path, markup=False) def clean_date(self): '''Date field validator. We can't use required on the input because it is a datepicker widget''' date = self.cleaned_data.get('date') if not date: raise forms.ValidationError('Required field') return date def save(self, *args, **kwargs): """NOTE: as the baseform of an inlineformset self.save(commit=True) never gets called""" session = super(InterimSessionModelForm, self).save(commit=kwargs.get('commit', True)) if self.is_approved_or_virtual: session.status_id = 'scheda' else: session.status_id = 'apprw' session.group = self.group session.type_id = 'session' if not self.instance.pk: session.requested_by = self.user.person return session def save_agenda(self): if self.instance.agenda(): doc = self.instance.agenda() doc.rev = str(int(doc.rev) + 1).zfill(2) e = NewRevisionDocEvent.objects.create( type='new_revision', by=self.user.person, doc=doc, rev=doc.rev, desc='New revision available') doc.save_with_history([e]) else: filename = get_next_agenda_name(meeting=self.instance.meeting) doc = Document.objects.create( type_id='agenda', group=self.group, name=filename, rev='00', external_url='{}-00.txt'.format(filename)) doc.set_state(State.objects.get(type__slug=doc.type.slug, slug='active')) DocAlias.objects.create(name=doc.name, document=doc) self.instance.sessionpresentation_set.create(document=doc, rev=doc.rev) NewRevisionDocEvent.objects.create( type='new_revision', by=self.user.person, doc=doc, rev=doc.rev, desc='New revision available') # write file path = os.path.join(self.instance.meeting.get_materials_path(), 'agenda', doc.filename_with_rev()) directory = os.path.dirname(path) if not os.path.exists(directory): os.makedirs(directory) with codecs.open(path, "w", encoding='utf-8') as file: file.write(self.cleaned_data['agenda'])
class LiaisonModelForm(BetterModelForm): '''Specify fields which require a custom widget or that are not part of the model. NOTE: from_groups and to_groups are marked as not required because select2 has a problem with validating ''' from_groups = forms.ModelMultipleChoiceField(queryset=Group.objects.all(),label=u'Groups',required=False) from_contact = forms.EmailField() to_contacts = forms.CharField(label="Contacts", widget=forms.Textarea(attrs={'rows':'3', }), strip=False) to_groups = forms.ModelMultipleChoiceField(queryset=Group.objects,label=u'Groups',required=False) deadline = DatepickerDateField(date_format="yyyy-mm-dd", picker_settings={"autoclose": "1" }, label='Deadline', required=True) related_to = SearchableLiaisonStatementsField(label=u'Related Liaison Statement', required=False) submitted_date = DatepickerDateField(date_format="yyyy-mm-dd", picker_settings={"autoclose": "1" }, label='Submission date', required=True, initial=datetime.date.today()) attachments = CustomModelMultipleChoiceField(queryset=Document.objects,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) class Meta: model = LiaisonStatement exclude = ('attachments','state','from_name','to_name') fieldsets = [('From', {'fields': ['from_groups','from_contact', 'response_contacts'], 'legend': ''}), ('To', {'fields': ['to_groups','to_contacts'], 'legend': ''}), ('Other email addresses', {'fields': ['technical_contacts','action_holder_contacts','cc_contacts'], 'legend': ''}), ('Purpose', {'fields':['purpose', 'deadline'], 'legend': ''}), ('Reference', {'fields': ['other_identifiers','related_to'], 'legend': ''}), ('Liaison Statement', {'fields': ['title', 'submitted_date', 'body', 'attachments'], 'legend': ''}), ('Add attachment', {'fields': ['attach_title', 'attach_file', 'attach_button'], 'legend': ''})] def __init__(self, user, *args, **kwargs): super(LiaisonModelForm, self).__init__(*args, **kwargs) self.user = user self.edit = False self.person = get_person_for_user(user) self.is_new = not self.instance.pk self.fields["from_groups"].widget.attrs["placeholder"] = "Type in name to search for group" self.fields["to_groups"].widget.attrs["placeholder"] = "Type in name to search for group" self.fields["to_contacts"].label = 'Contacts' self.fields["other_identifiers"].widget.attrs["rows"] = 2 # add email validators for field in ['from_contact','to_contacts','technical_contacts','action_holder_contacts','cc_contacts']: if field in self.fields: self.fields[field].validators.append(validate_emails) self.set_from_fields() self.set_to_fields() def clean_from_groups(self): from_groups = self.cleaned_data.get('from_groups') if not from_groups: raise forms.ValidationError('You must specify a From Group') return from_groups def clean_to_groups(self): to_groups = self.cleaned_data.get('to_groups') if not to_groups: raise forms.ValidationError('You must specify a To Group') return to_groups def clean_from_contact(self): contact = self.cleaned_data.get('from_contact') try: email = Email.objects.get(address=contact) except ObjectDoesNotExist: raise forms.ValidationError('Email address does not exist') return email def clean_cc_contacts(self): '''Return a comma separated list of addresses''' cc_contacts = self.cleaned_data.get('cc_contacts') cc_contacts = cc_contacts.replace('\r\n',',') cc_contacts = cc_contacts.rstrip(',') return cc_contacts 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']) # if purpose=response there must be a related statement purpose = LiaisonStatementPurposeName.objects.get(slug='response') if self.cleaned_data.get('purpose') == purpose and not self.cleaned_data.get('related_to'): self._errors['related_to'] = ErrorList([u'You must provide a related statement when purpose is In Response']) return self.cleaned_data def full_clean(self): self.set_required_fields() super(LiaisonModelForm, 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 is_approved(self): assert NotImplemented def save(self, *args, **kwargs): super(LiaisonModelForm, self).save(*args,**kwargs) # set state for new statements if self.is_new: self.instance.change_state(state_id='pending',person=self.person) if self.is_approved(): self.instance.change_state(state_id='posted',person=self.person) else: # create modified event LiaisonStatementEvent.objects.create( type_id='modified', by=self.person, statement=self.instance, desc='Statement Modified' ) self.save_related_liaisons() self.save_attachments() self.save_tags() return self.instance def save_attachments(self): '''Saves new attachments. Files come in with keys like "attach_file_N" where N is index of attachments displayed in the form. The attachment title is in the corresponding request.POST[attach_title_N] ''' written = self.instance.attachments.all().count() for key in self.files.keys(): title_key = key.replace('file', 'title') attachment_title = self.data.get(title_key) 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 = '' written += 1 name = self.instance.name() + ("-attachment-%s" % written) attach, created = Document.objects.get_or_create( name = name, defaults=dict( title = attachment_title, type_id = "liai-att", external_url = name + extension, # strictly speaking not necessary, but just for the time being ... ) ) if created: attach.docalias_set.create(name=attach.name) LiaisonStatementAttachment.objects.create(statement=self.instance,document=attach) attach_file = open(os.path.join(settings.LIAISON_ATTACH_PATH, attach.name + extension), 'w') attach_file.write(attached_file.read()) attach_file.close() if not self.is_new: # create modified event LiaisonStatementEvent.objects.create( type_id='modified', by=self.person, statement=self.instance, desc='Added attachment: {}'.format(attachment_title) ) def save_related_liaisons(self): rel = DocRelationshipName.objects.get(slug='refold') new_related = self.cleaned_data.get('related_to', []) # add new ones for stmt in new_related: self.instance.source_of_set.get_or_create(target=stmt,relationship=rel) # delete removed ones for related in self.instance.source_of_set.all(): if related.target not in new_related: related.delete() def save_tags(self): '''Create tags as needed''' if self.instance.deadline and not self.instance.tags.filter(slug='taken'): self.instance.tags.add('required') def set_from_fields(self): assert NotImplemented def set_required_fields(self): purpose = self.data.get('purpose', None) if purpose in ['action', 'comment']: self.fields['deadline'].required = True else: self.fields['deadline'].required = False def reset_required_fields(self): self.fields['deadline'].required = True def set_to_fields(self): assert NotImplemented
class MilestoneForm(forms.Form): id = forms.IntegerField(required=True, widget=forms.HiddenInput) desc = forms.CharField(max_length=500, label="Milestone", required=True) due = DatepickerDateField(date_format="MM yyyy", picker_settings={ "min-view-mode": "months", "autoclose": "1", "view-mode": "years" }, required=True) docs = SearchableDocumentsField( label="Drafts", required=False, help_text="Any drafts that the milestone concerns.") resolved_checkbox = forms.BooleanField(required=False, label="Resolved") resolved = forms.CharField(label="Resolved as", max_length=50, required=False) delete = forms.BooleanField(required=False, initial=False) review = forms.ChoiceField( label="Review action", help_text="Choose whether to accept or reject the proposed changes.", choices=(("accept", "Accept"), ("reject", "Reject and delete"), ("noaction", "No action")), required=False, initial="noaction", widget=forms.RadioSelect) def __init__(self, needs_review, reviewer, *args, **kwargs): m = self.milestone = kwargs.pop("instance", None) can_review = not needs_review if m: needs_review = m.state_id == "review" if not "initial" in kwargs: kwargs["initial"] = {} kwargs["initial"].update( dict( id=m.pk, desc=m.desc, due=m.due, resolved_checkbox=bool(m.resolved), resolved=m.resolved, docs=m.docs.all(), delete=False, review="noaction" if can_review and needs_review else "", )) kwargs["prefix"] = "m%s" % m.pk super(MilestoneForm, self).__init__(*args, **kwargs) self.fields["resolved"].widget.attrs["data-default"] = "Done" if needs_review and self.milestone and self.milestone.state_id != "review": self.fields["desc"].widget.attrs["readonly"] = True self.changed = False if not (needs_review and can_review): self.fields["review"].widget = forms.HiddenInput() self.needs_review = needs_review def clean_resolved(self): r = self.cleaned_data["resolved"].strip() if self.cleaned_data["resolved_checkbox"]: if not r: raise forms.ValidationError( 'Please provide explanation (like "Done") for why the milestone is no longer due.' ) else: r = "" return r
class RequestReviewForm(forms.ModelForm): team = forms.ModelMultipleChoiceField(queryset=Group.objects.all(), widget=forms.CheckboxSelectMultiple) deadline = DatepickerDateField(date_format="yyyy-mm-dd", picker_settings={ "autoclose": "1", "start-date": "+0d" }) class Meta: model = ReviewRequest fields = ('requested_by', 'type', 'deadline', 'requested_rev', 'comment') def __init__(self, user, doc, *args, **kwargs): super(RequestReviewForm, self).__init__(*args, **kwargs) self.doc = doc f = self.fields["team"] f.queryset = active_review_teams() f.initial = [ group.pk for group in f.queryset if can_manage_review_requests_for_team( user, group, allow_personnel_outside_team=False) ] self.fields['type'].queryset = self.fields['type'].queryset.filter( used=True, reviewteamsettings__group__in=self.fields["team"].queryset ).distinct() self.fields['type'].widget = forms.RadioSelect( choices=[t for t in self.fields['type'].choices if t[0]]) self.fields["requested_rev"].label = "Document revision" if has_role(user, "Secretariat"): self.fields["requested_by"] = SearchablePersonField() else: self.fields["requested_by"].widget = forms.HiddenInput() self.fields["requested_by"].initial = user.person.pk def clean_deadline(self): v = self.cleaned_data.get('deadline') if v < datetime.date.today(): raise forms.ValidationError( "Select today or a date in the future.") return v def clean_requested_rev(self): return clean_doc_revision(self.doc, self.cleaned_data.get("requested_rev")) def clean(self): chosen_type = self.cleaned_data.get("type") chosen_teams = self.cleaned_data.get("team") if chosen_type and chosen_teams: for t in chosen_teams: if chosen_type not in t.reviewteamsettings.review_types.all(): self.add_error( "type", "{} does not use the review type {}.".format( t.name, chosen_type.name)) return self.cleaned_data
class CompleteReviewForm(forms.Form): state = forms.ModelChoiceField( queryset=ReviewRequestStateName.objects.filter( slug__in=("completed", "part-completed")).order_by("-order"), widget=forms.RadioSelect, initial="completed") reviewed_rev = forms.CharField(label="Reviewed revision", max_length=4) result = forms.ModelChoiceField( queryset=ReviewResultName.objects.filter(used=True), widget=forms.RadioSelect, empty_label=None) ACTIONS = [ ("enter", "Enter review content (automatically posts to {mailing_list})"), ("upload", "Upload review content in text file (automatically posts to {mailing_list})" ), ("link", "Link to review message already sent to {mailing_list}"), ] review_submission = forms.ChoiceField(choices=ACTIONS, widget=forms.RadioSelect) review_url = forms.URLField(label="Link to message", required=False) review_file = forms.FileField(label="Text file to upload", required=False) review_content = forms.CharField(widget=forms.Textarea, required=False, strip=False) completion_date = DatepickerDateField( date_format="yyyy-mm-dd", picker_settings={"autoclose": "1"}, initial=datetime.date.today, help_text="Date of announcement of the results of this review") completion_time = forms.TimeField(widget=forms.HiddenInput, initial=datetime.time.min) cc = MultiEmailField( required=False, help_text= "Email addresses to send to in addition to the review team list") def __init__(self, review_req, *args, **kwargs): self.review_req = review_req super(CompleteReviewForm, self).__init__(*args, **kwargs) doc = self.review_req.doc known_revisions = NewRevisionDocEvent.objects.filter(doc=doc).order_by( "time", "id").values_list("rev", flat=True) revising_review = review_req.state_id not in ["requested", "accepted"] if not revising_review: self.fields["state"].choices = [ (slug, "{} - extra reviewer is to be assigned".format(label)) if slug == "part-completed" else (slug, label) for slug, label in self.fields["state"].choices ] self.fields["reviewed_rev"].help_text = mark_safe(" ".join( "<a class=\"rev label label-default\">{}</a>".format(r) for r in known_revisions)) self.fields["result"].queryset = self.fields["result"].queryset.filter( reviewteamsettings__group=review_req.team) def format_submission_choice(label): if revising_review: label = label.replace( " (automatically posts to {mailing_list})", "") return label.format(mailing_list=review_req.team.list_email or "[error: team has no mailing list set]") self.fields["review_submission"].choices = [ (k, format_submission_choice(label)) for k, label in self.fields["review_submission"].choices ] if revising_review: del self.fields["cc"] else: del self.fields["completion_date"] del self.fields["completion_time"] def clean_reviewed_rev(self): return clean_doc_revision(self.review_req.doc, self.cleaned_data.get("reviewed_rev")) def clean_review_content(self): return self.cleaned_data["review_content"].replace("\r", "") def clean_review_file(self): return get_cleaned_text_file_content(self.cleaned_data["review_file"]) def clean(self): if "@" in self.review_req.reviewer.person.ascii: raise forms.ValidationError( "Reviewer name must be filled in (the ASCII version is currently \"{}\" - since it contains an @ sign the name is probably still the original email address)." .format(self.review_req.reviewer.person.ascii)) def require_field(f): if not self.cleaned_data.get(f): self.add_error(f, ValidationError("You must fill in this field.")) submission_method = self.cleaned_data.get("review_submission") if submission_method == "enter": require_field("review_content") elif submission_method == "upload": require_field("review_file") elif submission_method == "link": require_field("review_url") require_field("review_content")