# Copyright The IETF Trust 2017-2023, All Rights Reserved
# -*- coding: utf-8 -*-
# Stdlib imports
import re
import debug # pyflakes:ignore
# Django imports
from django import forms
from django.utils.html import mark_safe # type:ignore
from django.db.models import F
from django.core.exceptions import ValidationError, ObjectDoesNotExist
from django.db.models import Q
# IETF imports
from ietf.group.models import Group, GroupHistory, GroupStateName, GroupFeatures
from ietf.name.models import ReviewTypeName, RoleName, ExtResourceName
from ietf.person.fields import SearchableEmailsField, PersonEmailChoiceField
from ietf.person.models import Email
from ietf.review.models import ReviewerSettings, UnavailablePeriod, ReviewSecretarySettings
from ietf.review.policies import get_reviewer_queue_policy
from ietf.review.utils import close_review_request_states
from ietf.utils import log
from ietf.utils.textupload import get_cleaned_text_file_content
#from ietf.utils.ordereddict import insert_after_in_ordered_dict
from ietf.utils.fields import DatepickerDateField, MultiEmailField
from ietf.utils.timezone import date_today
from ietf.utils.validators import validate_external_resource_value
# --- Constants --------------------------------------------------------
MAX_GROUP_DELEGATES = 3
# --- Forms ------------------------------------------------------------
class StatusUpdateForm(forms.Form):
content = forms.CharField(widget=forms.Textarea, label='Status update', help_text = "Enter the status update", required=False, strip=False)
txt = forms.FileField(label='.txt format', help_text='Or upload a .txt file', required=False)
def clean_content(self):
return self.cleaned_data['content'].replace('\r','')
def clean_txt(self):
return get_cleaned_text_file_content(self.cleaned_data["txt"])
def clean(self):
if (self.cleaned_data['content'] and self.cleaned_data['content'].strip() and self.cleaned_data['txt']):
raise forms.ValidationError("Cannot enter both text box and TXT file")
elif (self.cleaned_data['content'] and not self.cleaned_data['content'].strip() and not self.cleaned_data['txt']):
raise forms.ValidationError("NULL input is not a valid option")
elif (self.cleaned_data['txt'] and not self.cleaned_data['txt'].strip()) :
raise forms.ValidationError("NULL TXT file input is not a valid option")
class ConcludeGroupForm(forms.Form):
instructions = forms.CharField(widget=forms.Textarea(attrs={'rows': 15}), required=True, strip=False)
closing_note = forms.CharField(widget=forms.Textarea(attrs={'rows': 5}), label='Closing note, for WG history (optional)', required=False, strip=False)
class GroupForm(forms.Form):
name = forms.CharField(max_length=80, label="Name", required=True)
acronym = forms.CharField(max_length=40, label="Acronym", required=True)
state = forms.ModelChoiceField(GroupStateName.objects.all(), label="State", required=True)
# Note that __init__ will add role fields here
parent = forms.ModelChoiceField(Group.objects.filter(state="active").order_by('name'), empty_label="(None)", required=False)
list_email = forms.CharField(max_length=64, required=False)
list_subscribe = forms.CharField(max_length=255, required=False)
list_archive = forms.CharField(max_length=255, required=False)
description = forms.CharField(widget=forms.Textarea, required=False, help_text='Text that appears on the "about" page.')
urls = forms.CharField(widget=forms.Textarea, label="Additional URLs", help_text="Format: https://site/path (Optional description). Separate multiple entries with newline. Prefer HTTPS URLs where possible.", required=False)
resources = forms.CharField(widget=forms.Textarea, label="Additional Resources", help_text="Format: tag value (Optional description). Separate multiple entries with newline. Prefer HTTPS URLs where possible.", required=False)
closing_note = forms.CharField(widget=forms.Textarea, label="Closing note", required=False)
def __init__(self, *args, **kwargs):
self.group = kwargs.pop('group', None)
self.group_type = kwargs.pop('group_type', False)
if self.group:
group_features = self.group.features
self.used_roles = self.group.used_roles or group_features.default_used_roles
else:
group_features = GroupFeatures.objects.filter(type_id=self.group_type).first()
self.used_roles = group_features.default_used_roles
log.assertion('group_features is not None')
if group_features is not None:
parent_types = group_features.parent_types.all()
need_parent = group_features.need_parent
default_parent = group_features.default_parent
else:
# This should not happen, but in the absence of constraints that ensure it
# cannot, prevent the form from breaking if it does.
self.used_roles = []
parent_types = GroupFeatures.objects.none()
need_parent = False
default_parent = None
if "field" in kwargs:
field = kwargs["field"]
del kwargs["field"]
if field in self.used_roles:
field = field + "_roles"
else:
field = None
self.hide_parent = kwargs.pop('hide_parent', False)
super(self.__class__, self).__init__(*args, **kwargs)
if not group_features or group_features.has_chartering_process:
self.fields.pop('description') # do not show the description field for chartered groups
for role_slug in self.used_roles:
role_name = RoleName.objects.get(slug=role_slug)
fieldname = '%s_roles'%role_slug
field_args = {
'label' : role_name.name,
'required' : False,
'only_users' : True,
}
if fieldname == 'delegate_roles':
field_args['max_entries'] = MAX_GROUP_DELEGATES
field_args['help_text'] = mark_safe("Chairs can delegate the authority to update the state of group documents - at most %s persons at a given time." % MAX_GROUP_DELEGATES)
if role_slug == "ad":
field_args['extra_prefetch'] = Email.objects.filter(Q(role__name__in=('pre-ad', 'ad'), role__group__type='area', role__group__state='active')).distinct()
field_args['disable_ajax'] = True # only use the prefetched options
field_args['min_search_length'] = 0 # do not require typing to display options
self.fields[fieldname] = SearchableEmailsField(**field_args)
self.fields[fieldname].initial = Email.objects.filter(person__role__name_id=role_slug,person__role__group=self.group,person__role__email__pk=F('pk')).distinct()
self.adjusted_field_order = ['name','acronym','state']
for role_slug in self.used_roles:
self.adjusted_field_order.append('%s_roles'%role_slug)
self.order_fields(self.adjusted_field_order)
if self.group_type == "rg":
self.fields["state"].queryset = self.fields["state"].queryset.exclude(slug__in=("bof", "bof-conc"))
if self.group:
self.fields['acronym'].widget.attrs['readonly'] = ""
# Sort out parent options
if self.hide_parent:
self.fields.pop('parent')
else:
self.fields['parent'].queryset = self.fields['parent'].queryset.filter(type__in=parent_types)
if need_parent:
self.fields['parent'].required = True
self.fields['parent'].empty_label = None
# if this is a new group, fill in the default parent, if any
if self.group is None or (not hasattr(self.group, 'pk')):
self.fields['parent'].initial = self.fields['parent'].queryset.filter(
acronym=default_parent
).first()
# label the parent field as 'IETF Area' if appropriate, for consistency with past behavior
if parent_types.count() == 1 and parent_types.first().pk == 'area':
self.fields['parent'].label = "IETF Area"
if field:
keys = list(self.fields.keys())
for f in keys:
if f != field and not (f == 'closing_note' and field == 'state'):
del self.fields[f]
if 'resources' in self.fields:
info = "Format: 'tag value (Optional description)'. " \
+ "Separate multiple entries with newline. When the value is a URL, use https:// where possible.
" \
+ "Valid tags: %s" % ', '.join([ o.slug for o in ExtResourceName.objects.all().order_by('slug') ])
self.fields['resources'].help_text = mark_safe('