Allow to edit the stream/state/tags of a draft. Fixes #582
- Legacy-Id: 2818
This commit is contained in:
parent
3f13f31569
commit
53ad5be094
|
@ -48,6 +48,7 @@ from ietf.idtracker.templatetags.ietf_filters import format_textarea, fill
|
|||
from ietf.idrfc import markup_txt
|
||||
from ietf.idrfc.models import RfcIndex, DraftVersions
|
||||
from ietf.idrfc.idrfc_wrapper import BallotWrapper, IdWrapper, RfcWrapper
|
||||
from ietf.ietfworkflows.utils import get_full_info_for_draft
|
||||
|
||||
def document_debug(request, name):
|
||||
r = re.compile("^rfc([1-9][0-9]*)$")
|
||||
|
@ -109,25 +110,6 @@ def document_main(request, name):
|
|||
doc = IdWrapper(id)
|
||||
|
||||
info = {}
|
||||
stream_id = doc.stream_id()
|
||||
if stream_id == 2:
|
||||
stream = " (IAB document)"
|
||||
elif stream_id == 3:
|
||||
stream = " (IRTF document)"
|
||||
elif stream_id == 4:
|
||||
stream = " (Independent submission via RFC Editor)"
|
||||
elif doc.group_acronym():
|
||||
stream = " ("+doc.group_acronym().upper()+" WG document)"
|
||||
else:
|
||||
stream = " (Individual document)"
|
||||
|
||||
if id.status.status == "Active":
|
||||
info['is_active_draft'] = True
|
||||
info['type'] = "Active Internet-Draft"+stream
|
||||
else:
|
||||
info['is_active_draft'] = False
|
||||
info['type'] = "Old Internet-Draft"+stream
|
||||
|
||||
info['has_pdf'] = (".pdf" in doc.file_types())
|
||||
info['is_rfc'] = False
|
||||
|
||||
|
@ -141,6 +123,7 @@ def document_main(request, name):
|
|||
return render_to_response('idrfc/doc_main_id.html',
|
||||
{'content1':content1, 'content2':content2,
|
||||
'doc':doc, 'info':info,
|
||||
'stream_info': get_full_info_for_draft(id),
|
||||
'versions':versions, 'history':history},
|
||||
context_instance=RequestContext(request));
|
||||
|
||||
|
|
|
@ -1,16 +1,31 @@
|
|||
from django import forms
|
||||
import datetime
|
||||
|
||||
|
||||
from django import forms
|
||||
from django.template.loader import render_to_string
|
||||
from workflows.models import State
|
||||
|
||||
from ietf.idtracker.models import PersonOrOrgInfo
|
||||
from ietf.wgchairs.accounts import get_person_for_user
|
||||
from ietf.ietfworkflows.models import Stream
|
||||
from ietf.ietfworkflows.utils import (get_workflow_for_draft,
|
||||
get_state_for_draft)
|
||||
get_state_for_draft,
|
||||
update_state, FOLLOWUP_TAG,
|
||||
get_annotation_tags_for_draft,
|
||||
update_tags, update_stream)
|
||||
from ietf.ietfworkflows.streams import get_stream_from_draft
|
||||
|
||||
|
||||
class StreamDraftForm(forms.Form):
|
||||
|
||||
can_cancel = False
|
||||
template = None
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.draft = kwargs.pop('draft', None)
|
||||
self.user = kwargs.pop('user', None)
|
||||
self.person = get_person_for_user(self.user)
|
||||
self.workflow = get_workflow_for_draft(self.draft)
|
||||
self.message = {}
|
||||
super(StreamDraftForm, self).__init__(*args, **kwargs)
|
||||
|
||||
|
@ -22,6 +37,9 @@ class StreamDraftForm(forms.Form):
|
|||
'value': msg_value,
|
||||
}
|
||||
|
||||
def __unicode__(self):
|
||||
return render_to_string(self.template, {'form': self})
|
||||
|
||||
|
||||
class DraftStateForm(StreamDraftForm):
|
||||
|
||||
|
@ -29,17 +47,19 @@ class DraftStateForm(StreamDraftForm):
|
|||
new_state = forms.ChoiceField()
|
||||
weeks = forms.IntegerField(required=False)
|
||||
|
||||
template = 'ietfworkflows/state_form.html'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(DraftStateForm, self).__init__(*args, **kwargs)
|
||||
self.state = get_state_for_draft(self.draft)
|
||||
self.fields['new_state'].choices = self.get_states()
|
||||
if self.is_bound:
|
||||
for key, value in self.data.items():
|
||||
if key.startswith('transition_'):
|
||||
new_state = self.get_new_state(key)
|
||||
if new_state:
|
||||
self.data = self.data.copy()
|
||||
self.data.update({'new_state': new_state.id})
|
||||
self.workflow = get_workflow_for_draft(self.draft)
|
||||
self.state = get_state_for_draft(self.draft)
|
||||
self.fields['new_state'].choices = self.get_states()
|
||||
|
||||
def get_new_state(self, key):
|
||||
transition_id = key.replace('transition_', '')
|
||||
|
@ -53,3 +73,78 @@ class DraftStateForm(StreamDraftForm):
|
|||
|
||||
def get_states(self):
|
||||
return [(i.pk, i.name) for i in self.workflow.get_states()]
|
||||
|
||||
def save(self):
|
||||
comment = self.cleaned_data.get('comment')
|
||||
state = State.objects.get(pk=self.cleaned_data.get('new_state'))
|
||||
weeks = self.cleaned_data.get('weeks')
|
||||
estimated_date = None
|
||||
if weeks:
|
||||
now = datetime.date.today()
|
||||
estimated_date = now + datetime.timedelta(weeks=weeks)
|
||||
update_state(obj=self.draft,
|
||||
comment=comment,
|
||||
person=self.person,
|
||||
to_state=state,
|
||||
estimated_date=estimated_date)
|
||||
|
||||
|
||||
class DraftTagsForm(StreamDraftForm):
|
||||
|
||||
comment = forms.CharField(widget=forms.Textarea)
|
||||
tags = forms.MultipleChoiceField(widget=forms.CheckboxSelectMultiple, required=False)
|
||||
|
||||
template = 'ietfworkflows/tags_form.html'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(DraftTagsForm, self).__init__(*args, **kwargs)
|
||||
self.available_tags = self.workflow.get_tags()
|
||||
self.tags = [i.annotation_tag for i in get_annotation_tags_for_draft(self.draft)]
|
||||
self.fields['tags'].choices = [(i.pk, i.name) for i in self.available_tags]
|
||||
self.fields['tags'].initial = [i.pk for i in self.tags]
|
||||
|
||||
def save(self):
|
||||
comment = self.cleaned_data.get('comment')
|
||||
new_tags = self.cleaned_data.get('tags')
|
||||
|
||||
set_tags = [tag for tag in self.available_tags if str(tag.pk) in new_tags and tag not in self.tags]
|
||||
reset_tags = [tag for tag in self.available_tags if str(tag.pk) not in new_tags and tag in self.tags]
|
||||
followup = bool([tag for tag in set_tags if tag.name == FOLLOWUP_TAG])
|
||||
extra_notify = []
|
||||
if followup:
|
||||
try:
|
||||
shepherd = self.draft.shepherd
|
||||
if shepherd:
|
||||
extra_notify = ['%s <%s>' % shepherd.email()]
|
||||
except PersonOrOrgInfo.DoesNotExist:
|
||||
pass
|
||||
update_tags(self.draft,
|
||||
comment=comment,
|
||||
person=self.person,
|
||||
set_tags=set_tags,
|
||||
reset_tags=reset_tags,
|
||||
extra_notify=extra_notify)
|
||||
|
||||
|
||||
class DraftStreamForm(StreamDraftForm):
|
||||
|
||||
comment = forms.CharField(widget=forms.Textarea)
|
||||
stream = forms.ModelChoiceField(Stream.objects.all())
|
||||
|
||||
template = 'ietfworkflows/stream_form.html'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(DraftStreamForm, self).__init__(*args, **kwargs)
|
||||
self.stream = get_stream_from_draft(self.draft)
|
||||
self.tags = [i.annotation_tag for i in get_annotation_tags_for_draft(self.draft)]
|
||||
if self.stream:
|
||||
self.fields['stream'].initial = self.stream.pk
|
||||
|
||||
def save(self):
|
||||
comment = self.cleaned_data.get('comment')
|
||||
to_stream = self.cleaned_data.get('stream')
|
||||
|
||||
update_stream(self.draft,
|
||||
comment=comment,
|
||||
person=self.person,
|
||||
to_stream=to_stream)
|
||||
|
|
197
ietf/ietfworkflows/migrations/0009_allow_null_in_from_date.py
Normal file
197
ietf/ietfworkflows/migrations/0009_allow_null_in_from_date.py
Normal file
|
@ -0,0 +1,197 @@
|
|||
|
||||
from south.db import db
|
||||
from django.db import models
|
||||
from ietf.ietfworkflows.models import *
|
||||
|
||||
class Migration:
|
||||
|
||||
def forwards(self, orm):
|
||||
|
||||
# Changing field 'StateObjectRelationMetadata.from_date'
|
||||
# (to signature: django.db.models.fields.DateTimeField(null=True, blank=True))
|
||||
db.alter_column('ietfworkflows_stateobjectrelationmetadata', 'from_date', orm['ietfworkflows.stateobjectrelationmetadata:from_date'])
|
||||
|
||||
|
||||
|
||||
def backwards(self, orm):
|
||||
|
||||
# Changing field 'StateObjectRelationMetadata.from_date'
|
||||
# (to signature: django.db.models.fields.DateTimeField())
|
||||
db.alter_column('ietfworkflows_stateobjectrelationmetadata', 'from_date', orm['ietfworkflows.stateobjectrelationmetadata:from_date'])
|
||||
|
||||
|
||||
|
||||
models = {
|
||||
'contenttypes.contenttype': {
|
||||
'Meta': {'unique_together': "(('app_label', 'model'),)", 'db_table': "'django_content_type'"},
|
||||
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
|
||||
},
|
||||
'idtracker.acronym': {
|
||||
'Meta': {'db_table': "'acronym'"},
|
||||
'acronym': ('django.db.models.fields.CharField', [], {'max_length': '12'}),
|
||||
'acronym_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'name_key': ('django.db.models.fields.CharField', [], {'max_length': '50'})
|
||||
},
|
||||
'idtracker.idintendedstatus': {
|
||||
'Meta': {'db_table': "'id_intended_status'"},
|
||||
'intended_status': ('django.db.models.fields.CharField', [], {'max_length': '25', 'db_column': "'status_value'"}),
|
||||
'intended_status_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
|
||||
},
|
||||
'idtracker.idstatus': {
|
||||
'Meta': {'db_table': "'id_status'"},
|
||||
'status': ('django.db.models.fields.CharField', [], {'max_length': '25', 'db_column': "'status_value'"}),
|
||||
'status_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
|
||||
},
|
||||
'idtracker.internetdraft': {
|
||||
'Meta': {'db_table': "'internet_drafts'"},
|
||||
'abstract': ('django.db.models.fields.TextField', [], {}),
|
||||
'b_approve_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'b_discussion_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'b_sent_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'comments': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'dunn_sent_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'expiration_date': ('django.db.models.fields.DateField', [], {'null': 'True'}),
|
||||
'expired_tombstone': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
|
||||
'extension_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'file_type': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
|
||||
'filename': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
|
||||
'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.Acronym']", 'db_column': "'group_acronym_id'"}),
|
||||
'id_document_key': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'id_document_tag': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'intended_status': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.IDIntendedStatus']"}),
|
||||
'last_modified_date': ('django.db.models.fields.DateField', [], {}),
|
||||
'lc_changes': ('django.db.models.fields.CharField', [], {'max_length': '3', 'null': 'True'}),
|
||||
'lc_expiration_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'lc_sent_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'local_path': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
|
||||
'replaced_by': ('django.db.models.fields.related.ForeignKey', ["orm['idtracker.InternetDraft']"], {'related_name': "'replaces_set'", 'null': 'True', 'db_column': "'replaced_by'", 'blank': 'True'}),
|
||||
'review_by_rfc_editor': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
|
||||
'revision': ('django.db.models.fields.CharField', [], {'max_length': '2'}),
|
||||
'revision_date': ('django.db.models.fields.DateField', [], {}),
|
||||
'rfc_number': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}),
|
||||
'shepherd': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']", 'null': 'True', 'blank': 'True'}),
|
||||
'start_date': ('django.db.models.fields.DateField', [], {}),
|
||||
'status': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.IDStatus']"}),
|
||||
'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_column': "'id_document_name'"}),
|
||||
'txt_page_count': ('django.db.models.fields.IntegerField', [], {}),
|
||||
'wgreturn_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'})
|
||||
},
|
||||
'idtracker.personororginfo': {
|
||||
'Meta': {'db_table': "'person_or_org_info'"},
|
||||
'address_type': ('django.db.models.fields.CharField', [], {'max_length': '4', 'null': 'True', 'blank': 'True'}),
|
||||
'created_by': ('django.db.models.fields.CharField', [], {'max_length': '8', 'null': 'True', 'blank': 'True'}),
|
||||
'date_created': ('django.db.models.fields.DateField', [], {'auto_now_add': 'True', 'null': 'True', 'blank': 'True'}),
|
||||
'date_modified': ('django.db.models.fields.DateField', [], {'auto_now': 'True', 'null': 'True', 'blank': 'True'}),
|
||||
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'}),
|
||||
'first_name_key': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'}),
|
||||
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
|
||||
'last_name_key': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
|
||||
'middle_initial': ('django.db.models.fields.CharField', [], {'max_length': '4', 'null': 'True', 'blank': 'True'}),
|
||||
'middle_initial_key': ('django.db.models.fields.CharField', [], {'max_length': '4', 'null': 'True', 'blank': 'True'}),
|
||||
'modified_by': ('django.db.models.fields.CharField', [], {'max_length': '8', 'null': 'True', 'blank': 'True'}),
|
||||
'name_prefix': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}),
|
||||
'name_suffix': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}),
|
||||
'person_or_org_tag': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'record_type': ('django.db.models.fields.CharField', [], {'max_length': '8', 'null': 'True', 'blank': 'True'})
|
||||
},
|
||||
'ietfworkflows.annotationtag': {
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'permission': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['permissions.Permission']", 'null': 'True', 'blank': 'True'}),
|
||||
'workflow': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'annotation_tags'", 'to': "orm['workflows.Workflow']"})
|
||||
},
|
||||
'ietfworkflows.annotationtagobjectrelation': {
|
||||
'annotation_tag': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['ietfworkflows.AnnotationTag']"}),
|
||||
'content_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'annotation_tags'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
|
||||
},
|
||||
'ietfworkflows.objectannotationtaghistoryentry': {
|
||||
'objecthistoryentry_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['ietfworkflows.ObjectHistoryEntry']", 'unique': 'True', 'primary_key': 'True'}),
|
||||
'setted': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'unsetted': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'})
|
||||
},
|
||||
'ietfworkflows.objecthistoryentry': {
|
||||
'comment': ('django.db.models.fields.TextField', [], {}),
|
||||
'content_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'workflow_history'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
|
||||
'date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['idtracker.PersonOrOrgInfo']"})
|
||||
},
|
||||
'ietfworkflows.objectstreamhistoryentry': {
|
||||
'from_stream': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'objecthistoryentry_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['ietfworkflows.ObjectHistoryEntry']", 'unique': 'True', 'primary_key': 'True'}),
|
||||
'to_stream': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'})
|
||||
},
|
||||
'ietfworkflows.objectworkflowhistoryentry': {
|
||||
'from_state': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'objecthistoryentry_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['ietfworkflows.ObjectHistoryEntry']", 'unique': 'True', 'primary_key': 'True'}),
|
||||
'to_state': ('django.db.models.fields.CharField', [], {'max_length': '100'})
|
||||
},
|
||||
'ietfworkflows.stateobjectrelationmetadata': {
|
||||
'estimated_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'from_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'relation': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['workflows.StateObjectRelation']"})
|
||||
},
|
||||
'ietfworkflows.stream': {
|
||||
'group_chair_model': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
|
||||
'group_model': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'with_groups': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
|
||||
'workflow': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['ietfworkflows.WGWorkflow']"})
|
||||
},
|
||||
'ietfworkflows.streamedid': {
|
||||
'content_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'streamed_id'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
|
||||
'draft': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['idtracker.InternetDraft']", 'unique': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'stream': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['ietfworkflows.Stream']", 'null': 'True', 'blank': 'True'})
|
||||
},
|
||||
'ietfworkflows.wgworkflow': {
|
||||
'selected_states': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['workflows.State']", 'null': 'True', 'blank': 'True'}),
|
||||
'selected_tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['ietfworkflows.AnnotationTag']", 'null': 'True', 'blank': 'True'}),
|
||||
'workflow_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['workflows.Workflow']", 'unique': 'True', 'primary_key': 'True'})
|
||||
},
|
||||
'permissions.permission': {
|
||||
'codename': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}),
|
||||
'content_types': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['contenttypes.ContentType']", 'null': 'True', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'})
|
||||
},
|
||||
'workflows.state': {
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'transitions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['workflows.Transition']", 'null': 'True', 'blank': 'True'}),
|
||||
'workflow': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'states'", 'to': "orm['workflows.Workflow']"})
|
||||
},
|
||||
'workflows.stateobjectrelation': {
|
||||
'Meta': {'unique_together': "(('content_type', 'content_id', 'state'),)"},
|
||||
'content_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'state_object'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'state': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['workflows.State']"})
|
||||
},
|
||||
'workflows.transition': {
|
||||
'condition': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
|
||||
'destination': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'destination_state'", 'null': 'True', 'to': "orm['workflows.State']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'permission': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['permissions.Permission']", 'null': 'True', 'blank': 'True'}),
|
||||
'workflow': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'transitions'", 'to': "orm['workflows.Workflow']"})
|
||||
},
|
||||
'workflows.workflow': {
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'initial_state': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'workflow_state'", 'null': 'True', 'to': "orm['workflows.State']"}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}),
|
||||
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['permissions.Permission']", 'symmetrical': 'False'})
|
||||
}
|
||||
}
|
||||
|
||||
complete_apps = ['ietfworkflows']
|
|
@ -98,7 +98,7 @@ class AnnotationTagObjectRelation(models.Model):
|
|||
|
||||
class StateObjectRelationMetadata(models.Model):
|
||||
relation = models.ForeignKey(StateObjectRelation)
|
||||
from_date = models.DateTimeField(_('Initial date'))
|
||||
from_date = models.DateTimeField(_('Initial date'), blank=True, null=True)
|
||||
estimated_date = models.DateTimeField(_('Estimated date'), blank=True, null=True)
|
||||
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
from django.db import models
|
||||
|
||||
from workflows.utils import set_workflow_for_object
|
||||
|
||||
from ietf.idrfc.idrfc_wrapper import IdRfcWrapper, IdWrapper
|
||||
from ietf.ietfworkflows.models import StreamedID, Stream
|
||||
|
||||
|
@ -82,3 +84,13 @@ def get_stream_from_wrapper(idrfc_wrapper):
|
|||
else:
|
||||
return stream
|
||||
return None
|
||||
|
||||
|
||||
def set_stream_for_draft(draft, stream):
|
||||
(streamed, created) = StreamedID.objects.get_or_create(draft=draft)
|
||||
if streamed.stream != stream:
|
||||
streamed.stream = stream
|
||||
streamed.group = None
|
||||
streamed.save()
|
||||
set_workflow_for_object(draft, stream.workflow)
|
||||
return streamed.stream
|
||||
|
|
|
@ -10,11 +10,12 @@ from workflows.models import State, StateObjectRelation
|
|||
from workflows.utils import (get_workflow_for_object, set_workflow_for_object,
|
||||
get_state, set_state)
|
||||
|
||||
from ietf.ietfworkflows.streams import get_streamed_draft, get_stream_from_draft
|
||||
from ietf.ietfworkflows.streams import (get_streamed_draft, get_stream_from_draft,
|
||||
set_stream_for_draft)
|
||||
from ietf.ietfworkflows.models import (WGWorkflow, AnnotationTagObjectRelation,
|
||||
AnnotationTag, ObjectAnnotationTagHistoryEntry,
|
||||
ObjectHistoryEntry, StateObjectRelationMetadata,
|
||||
ObjectWorkflowHistoryEntry)
|
||||
ObjectWorkflowHistoryEntry, ObjectStreamHistoryEntry)
|
||||
|
||||
|
||||
WAITING_WRITEUP = 'WG Consensus: Waiting for Write-Up'
|
||||
|
@ -133,23 +134,25 @@ def get_annotation_tag_by_name(tag_name):
|
|||
return None
|
||||
|
||||
|
||||
def set_tag_by_name(obj, tag_name):
|
||||
def set_tag(obj, tag):
|
||||
ctype = ContentType.objects.get_for_model(obj)
|
||||
try:
|
||||
tag = AnnotationTag.objects.get(name=tag_name)
|
||||
(relation, created) = AnnotationTagObjectRelation.objects.get_or_create(
|
||||
content_type=ctype,
|
||||
content_id=obj.pk,
|
||||
annotation_tag=tag)
|
||||
except AnnotationTag.DoesNotExist:
|
||||
return None
|
||||
(relation, created) = AnnotationTagObjectRelation.objects.get_or_create(
|
||||
content_type=ctype,
|
||||
content_id=obj.pk,
|
||||
annotation_tag=tag)
|
||||
return relation
|
||||
|
||||
|
||||
def reset_tag_by_name(obj, tag_name):
|
||||
ctype = ContentType.objects.get_for_model(obj)
|
||||
def set_tag_by_name(obj, tag_name):
|
||||
try:
|
||||
tag = AnnotationTag.objects.get(name=tag_name)
|
||||
return set_tag(obj, tag)
|
||||
except AnnotationTag.DoesNotExist:
|
||||
return None
|
||||
|
||||
|
||||
def reset_tag(obj, tag):
|
||||
ctype = ContentType.objects.get_for_model(obj)
|
||||
try:
|
||||
tag_relation = AnnotationTagObjectRelation.objects.get(
|
||||
content_type=ctype,
|
||||
content_id=obj.pk,
|
||||
|
@ -158,6 +161,12 @@ def reset_tag_by_name(obj, tag_name):
|
|||
return True
|
||||
except AnnotationTagObjectRelation.DoesNotExist:
|
||||
return False
|
||||
|
||||
|
||||
def reset_tag_by_name(obj, tag_name):
|
||||
try:
|
||||
tag = AnnotationTag.objects.get(name=tag_name)
|
||||
return reset_tag(obj, tag)
|
||||
except AnnotationTag.DoesNotExist:
|
||||
return False
|
||||
|
||||
|
@ -208,16 +217,28 @@ def notify_state_entry(entry, extra_notify=[]):
|
|||
return notify_entry(entry, 'ietfworkflows/state_updated_mail.txt', extra_notify)
|
||||
|
||||
|
||||
def notify_stream_entry(entry, extra_notify=[]):
|
||||
return notify_entry(entry, 'ietfworkflows/stream_updated_mail.txt', extra_notify)
|
||||
|
||||
|
||||
def update_tags(obj, comment, person, set_tags=[], reset_tags=[], extra_notify=[]):
|
||||
ctype = ContentType.objects.get_for_model(obj)
|
||||
setted = []
|
||||
resetted = []
|
||||
for name in set_tags:
|
||||
if set_tag_by_name(obj, name):
|
||||
setted.append(name)
|
||||
for name in reset_tags:
|
||||
if reset_tag_by_name(obj, name):
|
||||
resetted.append(name)
|
||||
for tag in set_tags:
|
||||
if isinstance(tag, basestring):
|
||||
if set_tag_by_name(obj, tag):
|
||||
setted.append(tag)
|
||||
else:
|
||||
if set_tag(obj, tag):
|
||||
setted.append(tag.name)
|
||||
for tag in reset_tags:
|
||||
if isinstance(tag, basestring):
|
||||
if reset_tag_by_name(obj, tag):
|
||||
resetted.append(tag)
|
||||
else:
|
||||
if reset_tag(obj, tag):
|
||||
resetted.append(tag.name)
|
||||
entry = ObjectAnnotationTagHistoryEntry.objects.create(
|
||||
content_type=ctype,
|
||||
content_id=obj.pk,
|
||||
|
@ -238,14 +259,29 @@ def update_state(obj, comment, person, to_state, estimated_date=None, extra_noti
|
|||
entry = ObjectWorkflowHistoryEntry.objects.create(
|
||||
content_type=ctype,
|
||||
content_id=obj.pk,
|
||||
from_state=from_state.name,
|
||||
to_state=to_state.name,
|
||||
from_state=from_state and from_state.name or '',
|
||||
to_state=to_state and to_state.name or '',
|
||||
date=datetime.datetime.now(),
|
||||
comment=comment,
|
||||
person=person)
|
||||
notify_state_entry(entry, extra_notify)
|
||||
|
||||
|
||||
def update_stream(obj, comment, person, to_stream, extra_notify=[]):
|
||||
ctype = ContentType.objects.get_for_model(obj)
|
||||
from_stream = get_stream_from_draft(obj)
|
||||
to_stream = set_stream_for_draft(obj, to_stream)
|
||||
entry = ObjectStreamHistoryEntry.objects.create(
|
||||
content_type=ctype,
|
||||
content_id=obj.pk,
|
||||
from_stream=from_stream and from_stream.name or '',
|
||||
to_stream=to_stream and to_stream.name or '',
|
||||
date=datetime.datetime.now(),
|
||||
comment=comment,
|
||||
person=person)
|
||||
notify_stream_entry(entry, extra_notify)
|
||||
|
||||
|
||||
def get_full_info_for_draft(draft):
|
||||
return dict(
|
||||
streamed=get_streamed_draft(draft),
|
||||
|
|
|
@ -2,7 +2,8 @@ from ietf.idtracker.models import InternetDraft
|
|||
from django.shortcuts import get_object_or_404, render_to_response
|
||||
from django.template import RequestContext
|
||||
|
||||
from ietf.ietfworkflows.forms import DraftStateForm
|
||||
from ietf.ietfworkflows.forms import (DraftStateForm, DraftTagsForm,
|
||||
DraftStreamForm)
|
||||
from ietf.ietfworkflows.streams import (get_stream_from_draft,
|
||||
get_streamed_draft)
|
||||
from ietf.ietfworkflows.utils import (get_workflow_history_for_draft,
|
||||
|
@ -43,18 +44,21 @@ def stream_history(request, name):
|
|||
context_instance=RequestContext(request))
|
||||
|
||||
|
||||
def edit_state(request, name):
|
||||
def edit_state(request, name, form_class=DraftStateForm):
|
||||
user = request.user
|
||||
draft = get_object_or_404(InternetDraft, filename=name)
|
||||
if request.method == 'POST':
|
||||
form = form_class(user=user, draft=draft, data=request.POST)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
form = form_class(user=user, draft=draft)
|
||||
else:
|
||||
form = form_class(user=user, draft=draft)
|
||||
state = get_state_for_draft(draft)
|
||||
stream = get_stream_from_draft(draft)
|
||||
workflow = get_workflow_for_draft(draft)
|
||||
history = get_workflow_history_for_draft(draft, 'objectworkflowhistoryentry')
|
||||
tags = get_annotation_tags_for_draft(draft)
|
||||
if request.method == 'POST':
|
||||
form = DraftStateForm(user=user, draft=draft, data=request.POST)
|
||||
else:
|
||||
form = DraftStateForm(user=user, draft=draft)
|
||||
return render_to_response('ietfworkflows/state_edit.html',
|
||||
{'draft': draft,
|
||||
'state': state,
|
||||
|
@ -68,8 +72,8 @@ def edit_state(request, name):
|
|||
|
||||
|
||||
def edit_tags(request, name):
|
||||
pass
|
||||
return edit_state(request, name, DraftTagsForm)
|
||||
|
||||
|
||||
def edit_stream(request, name):
|
||||
pass
|
||||
return edit_state(request, name, DraftStreamForm)
|
||||
|
|
|
@ -39,19 +39,24 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
{% block doc_h1 %}{{ doc.title|escape }}<br/>{{ doc.draft_name_and_revision }}{% endblock %}
|
||||
|
||||
{% block doc_metatable %}
|
||||
<tr><td style="width:18ex;">Document type:</td><td>{{ info.type|escape }}
|
||||
{% with doc.replaces as r %}{% if r %}<br />Replaces {% filter urlize_ietf_docs %}{{ r|join:", "}}{% endfilter %}{% endif %}{% endwith %}
|
||||
</td></tr>
|
||||
<tr><td>Last updated:</td><td> {{ doc.publication_date|default:"(data missing)" }}</td></tr>
|
||||
<tr><td>State:</td><td>
|
||||
{{ doc.friendly_state|safe }}
|
||||
{% if doc.rfc_editor_state %}<br />RFC Editor State: <a href="http://www.rfc-editor.org/queue2.html#{{doc.draft_name}}">{{ doc.rfc_editor_state|escape }}</a>{% endif %}
|
||||
<tr><td>Document Stream:</td><td> {{ stream_info.stream.name|default:"No stream defined" }}</td></tr>
|
||||
<tr><td>I-D availability status:</td><td> {{ doc.draft_status }}
|
||||
{% ifequal doc.draft_status "Expired" %}
|
||||
{% if doc.resurrect_requested_by %}(resurrect requested by {{ doc.resurrect_requested_by }}){% endif %}
|
||||
{% endifequal %}
|
||||
{% with doc.replaces as r %}{% if r %}<br />Replaces {% filter urlize_ietf_docs %}{{ r|join:", "}}{% endfilter %}{% endif %}{% endwith %}
|
||||
</td></tr>
|
||||
<tr><td>Last updated:</td><td> {{ doc.publication_date|default:"(data missing)" }}</td></tr>
|
||||
<tr><td>IETF WG status:</td><td>{{ stream_info.state.name }} ({{ stream_info.streamed.group }})
|
||||
{% if stream_info.tags %}<br /><i>{% for tag in stream_info.tags %}{{ tag.name }}{% if not forloop.last %}, {% endif %}{% endfor %}{% endif %}
|
||||
</td></tr>
|
||||
<tr><td>Intended RFC status:</td><td>{% if doc.in_ietf_process %}{{ doc.ietf_process.intended_maturity_level|default:"-" }}{% else %}-{%endif%}</td></tr>
|
||||
<tr><td>Document shepherd:</td><td>{{ stream_info.shepherd }}</td></tr>
|
||||
<tr><td>IESG state:</td><td>
|
||||
{{ doc.friendly_state|safe }}
|
||||
{% if doc.rfc_editor_state %}<br />RFC Editor State: <a href="http://www.rfc-editor.org/queue2.html#{{doc.draft_name}}">{{ doc.rfc_editor_state|escape }}</a>{% endif %}
|
||||
{% if doc.in_ietf_process %}{% if doc.ietf_process.telechat_date %}<br/>On agenda of {{ doc.ietf_process.telechat_date }} IESG telechat {% if doc.ietf_process.telechat_returning_item %} (returning item){%endif%}{%endif%}{% if doc.ietf_process.has_active_iesg_ballot %}<br/><i>({{ doc.ietf_process.iesg_ballot_needed }})</i>{%endif%}{%endif%}
|
||||
</td></tr>
|
||||
<tr><td>Intended status:</td><td>{% if doc.in_ietf_process %}{{ doc.ietf_process.intended_maturity_level|default:"-" }}{% else %}-{%endif%}</td></tr>
|
||||
<tr><td>Submission:</td><td>{{ doc.submission }}</td></tr>
|
||||
<tr><td>Responsible AD:</td><td>{% if doc.in_ietf_process %}{{ doc.ietf_process.ad_name|default:"-"|escape }}{%else%}-{%endif%}</td></tr>
|
||||
{% if doc.in_ietf_process and doc.ietf_process.iesg_note %}<tr><td>IESG Note:</td><td>{{ doc.ietf_process.iesg_note|format_textarea|safe }}</td></tr>{% endif %}
|
||||
|
|
|
@ -11,7 +11,12 @@ table.edit-form span.required { color: red; }
|
|||
table.edit-form ul.errorlist { border-width: 0px; padding: 0px; margin: 0px;}
|
||||
table.edit-form ul.errorlist li { color: red; margin: 0px; padding: 0px;}
|
||||
table.edit-form div.field { margin-bottom: 1em; }
|
||||
table.edit-form div.submit-row { margin: 1em 2em; }
|
||||
table.edit-form div.error { border: 1px solid red; background-color: #ffeebb; padding: 5px 10px; }
|
||||
table.edit-form-tags tr { vertical-align: top; }
|
||||
table.edit-form-tags textarea { height: 200px; }
|
||||
table.edit-form-tags ul { border-width: 0px; padding: 1em 2em; }
|
||||
table.edit-form-tags ul li { padding: 0px; }
|
||||
{% endblock morecss %}
|
||||
|
||||
{% block title %}Change state for {{ draft }}{% endblock %}
|
||||
|
@ -26,7 +31,7 @@ table.edit-form div.error { border: 1px solid red; background-color: #ffeebb; pa
|
|||
<th>Annotation tags</th>
|
||||
</tr>
|
||||
<tr><td style="width: 25%;">
|
||||
{{ state.name|default:"None" }}
|
||||
{{ stream.name|default:"None" }}
|
||||
</td>
|
||||
<td style="width: 25%;">
|
||||
{{ state.name|default:"None" }}
|
||||
|
@ -39,48 +44,7 @@ table.edit-form div.error { border: 1px solid red; background-color: #ffeebb; pa
|
|||
</td></tr></table>
|
||||
<br />
|
||||
|
||||
<form action="" method="post">
|
||||
<table class="ietf-table edit-form" style="width: 100%;">
|
||||
<tr>
|
||||
<th>1. Input information about change</th>
|
||||
<th>2. Select the new state</th>
|
||||
</tr>
|
||||
<tr><td style="width: 50%;">
|
||||
<div class="field{% if form.errors.comment %} error{% endif %}">
|
||||
{{ form.errors.comment }}
|
||||
Comment: <span class="required">*</span><br />
|
||||
<textarea name="comment">{{ form.data.comment }}</textarea>
|
||||
</div>
|
||||
<div class="field{% if form.errors.weeks %} error{% endif %}">
|
||||
{{ form.errors.weeks }}
|
||||
Estimated time in next status:<br />
|
||||
<input type="text" name="weeks" value="{{ form.data.weeks }}" /> (in weeks)
|
||||
</div>
|
||||
</td><td style="padding: 0px; vertical-align: top;">
|
||||
{% with form.get_transitions as transitions %}
|
||||
{% if transitions %}
|
||||
<ul>
|
||||
{% for transition in transitions %}
|
||||
<li class="{% cycle oddrow,evenrow %}">
|
||||
<input type="submit" name="transition_{{ transition.pk }}" value="{{ transition.name }}" />
|
||||
Changes state to: <strong>{{ transition.destination.name }}</strong>
|
||||
</li>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</ul>
|
||||
{% endwith %}
|
||||
<div class="free-change field{% if form.errors.new_state %} error{% endif %}">
|
||||
{{ form.errors.new_state }}
|
||||
<select name="new_state">
|
||||
{% for value, name in form.get_states %}
|
||||
<option value="{{ value }}">{{ name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<input type="submit" name="change" value="State change" />
|
||||
</div>
|
||||
</td></tr>
|
||||
</table>
|
||||
</form>
|
||||
{{ form }}
|
||||
|
||||
<br />
|
||||
<strong>State history</strong>
|
||||
|
|
42
ietf/templates/ietfworkflows/state_form.html
Normal file
42
ietf/templates/ietfworkflows/state_form.html
Normal file
|
@ -0,0 +1,42 @@
|
|||
<form action="" method="post">
|
||||
<table class="ietf-table edit-form" style="width: 100%;">
|
||||
<tr>
|
||||
<th>1. Input information about change</th>
|
||||
<th>2. Select the new state</th>
|
||||
</tr>
|
||||
<tr><td style="width: 50%;">
|
||||
<div class="field{% if form.errors.comment %} error{% endif %}">
|
||||
{{ form.errors.comment }}
|
||||
Comment: <span class="required">*</span><br />
|
||||
<textarea name="comment">{{ form.data.comment }}</textarea>
|
||||
</div>
|
||||
<div class="field{% if form.errors.weeks %} error{% endif %}">
|
||||
{{ form.errors.weeks }}
|
||||
Estimated time in next status:<br />
|
||||
<input type="text" name="weeks" value="{{ form.data.weeks }}" /> (in weeks)
|
||||
</div>
|
||||
</td><td style="padding: 0px; vertical-align: top;">
|
||||
{% with form.get_transitions as transitions %}
|
||||
{% if transitions %}
|
||||
<ul>
|
||||
{% for transition in transitions %}
|
||||
<li class="{% cycle oddrow,evenrow %}">
|
||||
<input type="submit" name="transition_{{ transition.pk }}" value="{{ transition.name }}" />
|
||||
Changes state to: <strong>{{ transition.destination.name }}</strong>
|
||||
</li>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</ul>
|
||||
{% endwith %}
|
||||
<div class="free-change field{% if form.errors.new_state %} error{% endif %}">
|
||||
{{ form.errors.new_state }}
|
||||
<select name="new_state">
|
||||
{% for value, name in form.get_states %}
|
||||
<option value="{{ value }}">{{ name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<input type="submit" name="change" value="State change" />
|
||||
</div>
|
||||
</td></tr>
|
||||
</table>
|
||||
</form>
|
21
ietf/templates/ietfworkflows/stream_form.html
Normal file
21
ietf/templates/ietfworkflows/stream_form.html
Normal file
|
@ -0,0 +1,21 @@
|
|||
<form action="" method="post">
|
||||
<table class="ietf-table edit-form" style="width: 100%;">
|
||||
<tr>
|
||||
<th>Select the new stream</th>
|
||||
</tr>
|
||||
<tr><td>
|
||||
<div class="field{% if form.errors.comment %} error{% endif %}">
|
||||
{{ form.errors.comment }}
|
||||
Comment: <span class="required">*</span><br />
|
||||
<textarea name="comment">{{ form.data.comment }}</textarea>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<td>
|
||||
<div class="field{% if form.errors.stream %} error{% endif %}">
|
||||
{{ form.errors.stream }}
|
||||
{{ form.stream }} <input type="submit" value="Change stream" />
|
||||
</div>
|
||||
</td></tr>
|
||||
</table>
|
||||
</form>
|
9
ietf/templates/ietfworkflows/stream_updated_mail.txt
Normal file
9
ietf/templates/ietfworkflows/stream_updated_mail.txt
Normal file
|
@ -0,0 +1,9 @@
|
|||
The stream of document {{ doc }} has been updated. See more information below.
|
||||
|
||||
Previous stream: {{ entry.from_stream }}
|
||||
Current stream: {{ entry.to_stream }}
|
||||
Transition date: {{ entry.transition_date }}
|
||||
Author of the change: {{ entry.person }}
|
||||
|
||||
Comment:
|
||||
{{ entry.comment }}
|
22
ietf/templates/ietfworkflows/tags_form.html
Normal file
22
ietf/templates/ietfworkflows/tags_form.html
Normal file
|
@ -0,0 +1,22 @@
|
|||
<form action="" method="post">
|
||||
<table class="ietf-table edit-form edit-form-tags" style="width: 100%;">
|
||||
<tr>
|
||||
<th>1. Input information about change</th>
|
||||
<th>2. Select annotation tags</th>
|
||||
</tr>
|
||||
<tr><td style="width: 50%;">
|
||||
<div class="field comment{% if form.errors.comment %} error{% endif %}">
|
||||
{{ form.errors.comment }}
|
||||
Comment: <span class="required">*</span><br />
|
||||
<textarea name="comment">{{ form.data.comment }}</textarea>
|
||||
</div>
|
||||
</td><td style="padding: 0px; vertical-align: top;">
|
||||
<div class="field">
|
||||
{{ form.tags }}
|
||||
</div>
|
||||
<div class="submit-row">
|
||||
<input type="submit" value="Edit tags" />
|
||||
</div>
|
||||
</td></tr>
|
||||
</table>
|
||||
</form>
|
Loading…
Reference in a new issue