Make new branch from trunk (r9007) and merge facelift-r8876 into it, fixing a few merge conflicts
- Legacy-Id: 9014
This commit is contained in:
commit
eccd8d27c4
25
README
Normal file
25
README
Normal file
|
@ -0,0 +1,25 @@
|
|||
This is the "facelift" datatracker branch that uses Twitter Bootstrap for
|
||||
the UI.
|
||||
|
||||
You need to install a few new django extensions:
|
||||
https://pypi.python.org/pypi/django-widget-tweaks
|
||||
https://pypi.python.org/pypi/django-bootstrap3
|
||||
https://pypi.python.org/pypi/django-typogrify
|
||||
|
||||
The meta goal of this effort is: *** NO CHANGES TO THE PYTHON CODE ***
|
||||
|
||||
Whenever changes to the python code are made, they can only fix HTML bugs,
|
||||
or add comments (tagged with "FACELIFT") about functionality that can be
|
||||
removed once the facelift templates become default. Or they need to add
|
||||
functionality that is only called from the new facelift templates.
|
||||
|
||||
Javascript that is only used on one template goes into that template.
|
||||
Javascript that is used by more than one template goes into ietf.js.
|
||||
|
||||
CSS that is only used on one template goes into that template.
|
||||
CSS that is used by more than one template goes into ietf.css. No CSS in the
|
||||
templates or - god forbid - style tags! (And no CSS or HTML styling in
|
||||
python code!!)
|
||||
|
||||
Templates that use jquery or bootstrap plugins include the css in the pagehead
|
||||
block, and the js in the js block.
|
11
TODO
Normal file
11
TODO
Normal file
|
@ -0,0 +1,11 @@
|
|||
Major pieces not facelifted: milestone editing, liaison editing, WG workflow customization
|
||||
|
||||
Use affix for navigation on active_wgs.html
|
||||
|
||||
Figure out why {% if debug %} does not work in the text templates under ietf/templates_facelift/community/public.
|
||||
|
||||
Make django generate HTML5 date inputs or use a js-based datepicker.
|
||||
|
||||
Deferring ballots does not work. (Seems to be an upstream bug.)
|
||||
|
||||
Make tables that are too wide to usefully work on small screens responsive. See http://getbootstrap.com/css/#tables-responsive
|
3
bootstrap3/__init__.py
Normal file
3
bootstrap3/__init__.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
__version__ = '5.1.1'
|
106
bootstrap3/bootstrap.py
Normal file
106
bootstrap3/bootstrap.py
Normal file
|
@ -0,0 +1,106 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.conf import settings
|
||||
from django.utils.importlib import import_module
|
||||
|
||||
|
||||
# Default settings
|
||||
BOOTSTRAP3_DEFAULTS = {
|
||||
'jquery_url': '//code.jquery.com/jquery.min.js',
|
||||
'base_url': '//netdna.bootstrapcdn.com/bootstrap/3.3.2/',
|
||||
'css_url': None,
|
||||
'theme_url': None,
|
||||
'javascript_url': None,
|
||||
'javascript_in_head': False,
|
||||
'include_jquery': False,
|
||||
'horizontal_label_class': 'col-md-2',
|
||||
'horizontal_field_class': 'col-md-4',
|
||||
'set_required': True,
|
||||
'set_placeholder': True,
|
||||
'required_css_class': '',
|
||||
'error_css_class': 'has-error',
|
||||
'success_css_class': 'has-success',
|
||||
'formset_renderers': {
|
||||
'default': 'bootstrap3.renderers.FormsetRenderer',
|
||||
},
|
||||
'form_renderers': {
|
||||
'default': 'bootstrap3.renderers.FormRenderer',
|
||||
},
|
||||
'field_renderers': {
|
||||
'default': 'bootstrap3.renderers.FieldRenderer',
|
||||
'inline': 'bootstrap3.renderers.InlineFieldRenderer',
|
||||
},
|
||||
}
|
||||
|
||||
# Start with a copy of default settings
|
||||
BOOTSTRAP3 = BOOTSTRAP3_DEFAULTS.copy()
|
||||
|
||||
# Override with user settings from settings.py
|
||||
BOOTSTRAP3.update(getattr(settings, 'BOOTSTRAP3', {}))
|
||||
|
||||
|
||||
def get_bootstrap_setting(setting, default=None):
|
||||
"""
|
||||
Read a setting
|
||||
"""
|
||||
return BOOTSTRAP3.get(setting, default)
|
||||
|
||||
|
||||
def bootstrap_url(postfix):
|
||||
"""
|
||||
Prefix a relative url with the bootstrap base url
|
||||
"""
|
||||
return get_bootstrap_setting('base_url') + postfix
|
||||
|
||||
|
||||
def jquery_url():
|
||||
"""
|
||||
Return the full url to jQuery file to use
|
||||
"""
|
||||
return get_bootstrap_setting('jquery_url')
|
||||
|
||||
|
||||
def javascript_url():
|
||||
"""
|
||||
Return the full url to the Bootstrap JavaScript file
|
||||
"""
|
||||
return get_bootstrap_setting('javascript_url') or \
|
||||
bootstrap_url('js/bootstrap.min.js')
|
||||
|
||||
|
||||
def css_url():
|
||||
"""
|
||||
Return the full url to the Bootstrap CSS file
|
||||
"""
|
||||
return get_bootstrap_setting('css_url') or \
|
||||
bootstrap_url('css/bootstrap.min.css')
|
||||
|
||||
|
||||
def theme_url():
|
||||
"""
|
||||
Return the full url to the theme CSS file
|
||||
"""
|
||||
return get_bootstrap_setting('theme_url')
|
||||
|
||||
|
||||
def get_renderer(renderers, **kwargs):
|
||||
layout = kwargs.get('layout', '')
|
||||
path = renderers.get(layout, renderers['default'])
|
||||
mod, cls = path.rsplit(".", 1)
|
||||
return getattr(import_module(mod), cls)
|
||||
|
||||
|
||||
def get_formset_renderer(**kwargs):
|
||||
renderers = get_bootstrap_setting('formset_renderers')
|
||||
return get_renderer(renderers, **kwargs)
|
||||
|
||||
|
||||
def get_form_renderer(**kwargs):
|
||||
renderers = get_bootstrap_setting('form_renderers')
|
||||
return get_renderer(renderers, **kwargs)
|
||||
|
||||
|
||||
def get_field_renderer(**kwargs):
|
||||
renderers = get_bootstrap_setting('field_renderers')
|
||||
return get_renderer(renderers, **kwargs)
|
37
bootstrap3/components.py
Normal file
37
bootstrap3/components.py
Normal file
|
@ -0,0 +1,37 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.forms.widgets import flatatt
|
||||
|
||||
from .text import text_value
|
||||
|
||||
|
||||
def render_icon(icon, title=''):
|
||||
"""
|
||||
Render a Bootstrap glyphicon icon
|
||||
"""
|
||||
attrs = {
|
||||
'class': 'glyphicon glyphicon-{icon}'.format(icon=icon),
|
||||
}
|
||||
if title:
|
||||
attrs['title'] = title
|
||||
return '<span{attrs}></span>'.format(attrs=flatatt(attrs))
|
||||
|
||||
|
||||
def render_alert(content, alert_type=None, dismissable=True):
|
||||
"""
|
||||
Render a Bootstrap alert
|
||||
"""
|
||||
button = ''
|
||||
if not alert_type:
|
||||
alert_type = 'info'
|
||||
css_classes = ['alert', 'alert-' + text_value(alert_type)]
|
||||
if dismissable:
|
||||
css_classes.append('alert-dismissable')
|
||||
button = '<button type="button" class="close" ' + \
|
||||
'data-dismiss="alert" aria-hidden="true">×</button>'
|
||||
return '<div class="{css_classes}">{button}{content}</div>'.format(
|
||||
css_classes=' '.join(css_classes),
|
||||
button=button,
|
||||
content=text_value(content),
|
||||
)
|
16
bootstrap3/exceptions.py
Normal file
16
bootstrap3/exceptions.py
Normal file
|
@ -0,0 +1,16 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
||||
class BootstrapException(Exception):
|
||||
"""
|
||||
Any exception from this package
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class BootstrapError(BootstrapException):
|
||||
"""
|
||||
Any exception that is an error
|
||||
"""
|
||||
pass
|
173
bootstrap3/forms.py
Normal file
173
bootstrap3/forms.py
Normal file
|
@ -0,0 +1,173 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.contrib.admin.widgets import AdminFileWidget
|
||||
from django.forms import (
|
||||
HiddenInput, FileInput, CheckboxSelectMultiple, Textarea, TextInput
|
||||
)
|
||||
|
||||
from .bootstrap import (
|
||||
get_bootstrap_setting, get_form_renderer, get_field_renderer,
|
||||
get_formset_renderer
|
||||
)
|
||||
from .text import text_concat, text_value
|
||||
from .exceptions import BootstrapError
|
||||
from .utils import add_css_class, render_tag
|
||||
from .components import render_icon
|
||||
|
||||
|
||||
FORM_GROUP_CLASS = 'form-group'
|
||||
|
||||
|
||||
def render_formset(formset, **kwargs):
|
||||
"""
|
||||
Render a formset to a Bootstrap layout
|
||||
"""
|
||||
renderer_cls = get_formset_renderer(**kwargs)
|
||||
return renderer_cls(formset, **kwargs).render()
|
||||
|
||||
|
||||
def render_formset_errors(form, **kwargs):
|
||||
"""
|
||||
Render formset errors to a Bootstrap layout
|
||||
"""
|
||||
renderer_cls = get_formset_renderer(**kwargs)
|
||||
return renderer_cls(form, **kwargs).render_errors()
|
||||
|
||||
|
||||
def render_form(form, **kwargs):
|
||||
"""
|
||||
Render a formset to a Bootstrap layout
|
||||
"""
|
||||
renderer_cls = get_form_renderer(**kwargs)
|
||||
return renderer_cls(form, **kwargs).render()
|
||||
|
||||
|
||||
def render_form_errors(form, type='all', **kwargs):
|
||||
"""
|
||||
Render form errors to a Bootstrap layout
|
||||
"""
|
||||
renderer_cls = get_form_renderer(**kwargs)
|
||||
return renderer_cls(form, **kwargs).render_errors(type)
|
||||
|
||||
|
||||
def render_field(field, **kwargs):
|
||||
"""
|
||||
Render a formset to a Bootstrap layout
|
||||
"""
|
||||
renderer_cls = get_field_renderer(**kwargs)
|
||||
return renderer_cls(field, **kwargs).render()
|
||||
|
||||
|
||||
def render_label(content, label_for=None, label_class=None, label_title=''):
|
||||
"""
|
||||
Render a label with content
|
||||
"""
|
||||
attrs = {}
|
||||
if label_for:
|
||||
attrs['for'] = label_for
|
||||
if label_class:
|
||||
attrs['class'] = label_class
|
||||
if label_title:
|
||||
attrs['title'] = label_title
|
||||
return render_tag('label', attrs=attrs, content=content)
|
||||
|
||||
|
||||
def render_button(
|
||||
content, button_type=None, icon=None, button_class='', size='',
|
||||
href=''):
|
||||
"""
|
||||
Render a button with content
|
||||
"""
|
||||
attrs = {}
|
||||
classes = add_css_class('btn', button_class)
|
||||
size = text_value(size).lower().strip()
|
||||
if size == 'xs':
|
||||
classes = add_css_class(classes, 'btn-xs')
|
||||
elif size == 'sm' or size == 'small':
|
||||
classes = add_css_class(classes, 'btn-sm')
|
||||
elif size == 'lg' or size == 'large':
|
||||
classes = add_css_class(classes, 'btn-lg')
|
||||
elif size == 'md' or size == 'medium':
|
||||
pass
|
||||
elif size:
|
||||
raise BootstrapError(
|
||||
'Parameter "size" should be "xs", "sm", "lg" or ' +
|
||||
'empty ("{}" given).'.format(size))
|
||||
if button_type:
|
||||
if button_type == 'submit':
|
||||
classes = add_css_class(classes, 'btn-primary')
|
||||
elif button_type not in ('reset', 'button', 'link'):
|
||||
raise BootstrapError(
|
||||
'Parameter "button_type" should be "submit", "reset", ' +
|
||||
'"button", "link" or empty ("{}" given).'.format(button_type))
|
||||
attrs['type'] = button_type
|
||||
attrs['class'] = classes
|
||||
icon_content = render_icon(icon) if icon else ''
|
||||
if href:
|
||||
attrs['href'] = href
|
||||
tag = 'a'
|
||||
else:
|
||||
tag = 'button'
|
||||
return render_tag(
|
||||
tag, attrs=attrs, content=text_concat(
|
||||
icon_content, content, separator=' '))
|
||||
|
||||
|
||||
def render_field_and_label(
|
||||
field, label, field_class='', label_for=None, label_class='',
|
||||
layout='', **kwargs):
|
||||
"""
|
||||
Render a field with its label
|
||||
"""
|
||||
if layout == 'horizontal':
|
||||
if not label_class:
|
||||
label_class = get_bootstrap_setting('horizontal_label_class')
|
||||
if not field_class:
|
||||
field_class = get_bootstrap_setting('horizontal_field_class')
|
||||
if not label:
|
||||
label = ' '
|
||||
label_class = add_css_class(label_class, 'control-label')
|
||||
html = field
|
||||
if field_class:
|
||||
html = '<div class="{klass}">{html}</div>'.format(
|
||||
klass=field_class, html=html)
|
||||
if label:
|
||||
html = render_label(
|
||||
label, label_for=label_for, label_class=label_class) + html
|
||||
return html
|
||||
|
||||
|
||||
def render_form_group(content, css_class=FORM_GROUP_CLASS):
|
||||
"""
|
||||
Render a Bootstrap form group
|
||||
"""
|
||||
return '<div class="{klass}">{content}</div>'.format(
|
||||
klass=css_class,
|
||||
content=content,
|
||||
)
|
||||
|
||||
|
||||
def is_widget_required_attribute(widget):
|
||||
"""
|
||||
Is this widget required?
|
||||
"""
|
||||
if not get_bootstrap_setting('set_required'):
|
||||
return False
|
||||
if not widget.is_required:
|
||||
return False
|
||||
if isinstance(
|
||||
widget, (
|
||||
AdminFileWidget, HiddenInput, FileInput,
|
||||
CheckboxSelectMultiple)):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def is_widget_with_placeholder(widget):
|
||||
"""
|
||||
Is this a widget that should have a placeholder?
|
||||
Only text, search, url, tel, e-mail, password, number have placeholders
|
||||
These are all derived form TextInput, except for Textarea
|
||||
"""
|
||||
return isinstance(widget, (TextInput, Textarea))
|
3
bootstrap3/models.py
Normal file
3
bootstrap3/models.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Empty models.py, required file for Django tests
|
485
bootstrap3/renderers.py
Normal file
485
bootstrap3/renderers.py
Normal file
|
@ -0,0 +1,485 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
from django.contrib.auth.forms import ReadOnlyPasswordHashWidget
|
||||
|
||||
from django.forms import (
|
||||
TextInput, DateInput, FileInput, CheckboxInput,
|
||||
ClearableFileInput, Select, RadioSelect, CheckboxSelectMultiple
|
||||
)
|
||||
from django.forms.extras import SelectDateWidget
|
||||
from django.forms.forms import BaseForm, BoundField
|
||||
from django.forms.formsets import BaseFormSet
|
||||
from django.utils.html import conditional_escape, strip_tags
|
||||
from django.template import Context
|
||||
from django.template.loader import get_template
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
from .bootstrap import get_bootstrap_setting
|
||||
from .text import text_value
|
||||
from .exceptions import BootstrapError
|
||||
from .utils import add_css_class
|
||||
from .forms import (
|
||||
render_form, render_field, render_label, render_form_group,
|
||||
is_widget_with_placeholder, is_widget_required_attribute, FORM_GROUP_CLASS
|
||||
)
|
||||
|
||||
|
||||
class BaseRenderer(object):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.layout = kwargs.get('layout', '')
|
||||
self.form_group_class = kwargs.get(
|
||||
'form_group_class', FORM_GROUP_CLASS)
|
||||
self.field_class = kwargs.get('field_class', '')
|
||||
self.label_class = kwargs.get('label_class', '')
|
||||
self.show_help = kwargs.get('show_help', True)
|
||||
self.show_label = kwargs.get('show_label', True)
|
||||
self.exclude = kwargs.get('exclude', '')
|
||||
self.set_required = kwargs.get('set_required', True)
|
||||
self.size = self.parse_size(kwargs.get('size', ''))
|
||||
self.horizontal_label_class = kwargs.get(
|
||||
'horizontal_label_class',
|
||||
get_bootstrap_setting('horizontal_label_class')
|
||||
)
|
||||
self.horizontal_field_class = kwargs.get(
|
||||
'horizontal_field_class',
|
||||
get_bootstrap_setting('horizontal_field_class')
|
||||
)
|
||||
|
||||
def parse_size(self, size):
|
||||
size = text_value(size).lower().strip()
|
||||
if size in ('sm', 'small'):
|
||||
return 'small'
|
||||
if size in ('lg', 'large'):
|
||||
return 'large'
|
||||
if size in ('md', 'medium', ''):
|
||||
return 'medium'
|
||||
raise BootstrapError('Invalid value "%s" for parameter "size" (expected "sm", "md", "lg" or "").' % size)
|
||||
|
||||
def get_size_class(self, prefix='input'):
|
||||
if self.size == 'small':
|
||||
return prefix + '-sm'
|
||||
if self.size == 'large':
|
||||
return prefix + '-lg'
|
||||
return ''
|
||||
|
||||
|
||||
class FormsetRenderer(BaseRenderer):
|
||||
"""
|
||||
Default formset renderer
|
||||
"""
|
||||
|
||||
def __init__(self, formset, *args, **kwargs):
|
||||
if not isinstance(formset, BaseFormSet):
|
||||
raise BootstrapError(
|
||||
'Parameter "formset" should contain a valid Django Formset.')
|
||||
self.formset = formset
|
||||
super(FormsetRenderer, self).__init__(*args, **kwargs)
|
||||
|
||||
def render_management_form(self):
|
||||
return text_value(self.formset.management_form)
|
||||
|
||||
def render_form(self, form, **kwargs):
|
||||
return render_form(form, **kwargs)
|
||||
|
||||
def render_forms(self):
|
||||
rendered_forms = []
|
||||
for form in self.formset.forms:
|
||||
rendered_forms.append(self.render_form(
|
||||
form,
|
||||
layout=self.layout,
|
||||
form_group_class=self.form_group_class,
|
||||
field_class=self.field_class,
|
||||
label_class=self.label_class,
|
||||
show_help=self.show_help,
|
||||
exclude=self.exclude,
|
||||
set_required=self.set_required,
|
||||
size=self.size,
|
||||
horizontal_label_class=self.horizontal_label_class,
|
||||
horizontal_field_class=self.horizontal_field_class,
|
||||
))
|
||||
return '\n'.join(rendered_forms)
|
||||
|
||||
def get_formset_errors(self):
|
||||
return self.formset.non_form_errors()
|
||||
|
||||
def render_errors(self):
|
||||
formset_errors = self.get_formset_errors()
|
||||
if formset_errors:
|
||||
return get_template(
|
||||
'bootstrap3/form_errors.html').render(
|
||||
Context({
|
||||
'errors': formset_errors,
|
||||
'form': self.formset,
|
||||
'layout': self.layout,
|
||||
})
|
||||
)
|
||||
return ''
|
||||
|
||||
def render(self):
|
||||
return self.render_errors() + self.render_management_form() + \
|
||||
self.render_forms()
|
||||
|
||||
|
||||
class FormRenderer(BaseRenderer):
|
||||
"""
|
||||
Default form renderer
|
||||
"""
|
||||
|
||||
def __init__(self, form, *args, **kwargs):
|
||||
if not isinstance(form, BaseForm):
|
||||
raise BootstrapError(
|
||||
'Parameter "form" should contain a valid Django Form.')
|
||||
self.form = form
|
||||
super(FormRenderer, self).__init__(*args, **kwargs)
|
||||
# Handle form.empty_permitted
|
||||
if self.form.empty_permitted:
|
||||
self.set_required = False
|
||||
|
||||
def render_fields(self):
|
||||
rendered_fields = []
|
||||
for field in self.form:
|
||||
rendered_fields.append(render_field(
|
||||
field,
|
||||
layout=self.layout,
|
||||
form_group_class=self.form_group_class,
|
||||
field_class=self.field_class,
|
||||
label_class=self.label_class,
|
||||
show_help=self.show_help,
|
||||
exclude=self.exclude,
|
||||
set_required=self.set_required,
|
||||
size=self.size,
|
||||
horizontal_label_class=self.horizontal_label_class,
|
||||
horizontal_field_class=self.horizontal_field_class,
|
||||
))
|
||||
return '\n'.join(rendered_fields)
|
||||
|
||||
def get_fields_errors(self):
|
||||
form_errors = []
|
||||
for field in self.form:
|
||||
if field.is_hidden and field.errors:
|
||||
form_errors += field.errors
|
||||
return form_errors
|
||||
|
||||
def render_errors(self, type='all'):
|
||||
form_errors = None
|
||||
if type == 'all':
|
||||
form_errors = self.get_fields_errors() + \
|
||||
self.form.non_field_errors()
|
||||
elif type == 'fields':
|
||||
form_errors = self.get_fields_errors()
|
||||
elif type == 'non_fields':
|
||||
form_errors = self.form.non_field_errors()
|
||||
|
||||
if form_errors:
|
||||
return get_template(
|
||||
'bootstrap3/form_errors.html').render(
|
||||
Context({
|
||||
'errors': form_errors,
|
||||
'form': self.form,
|
||||
'layout': self.layout,
|
||||
})
|
||||
)
|
||||
return ''
|
||||
|
||||
def render(self):
|
||||
return self.render_errors() + self.render_fields()
|
||||
|
||||
|
||||
class FieldRenderer(BaseRenderer):
|
||||
"""
|
||||
Default field renderer
|
||||
"""
|
||||
|
||||
def __init__(self, field, *args, **kwargs):
|
||||
if not isinstance(field, BoundField):
|
||||
raise BootstrapError(
|
||||
'Parameter "field" should contain a valid Django BoundField.')
|
||||
self.field = field
|
||||
super(FieldRenderer, self).__init__(*args, **kwargs)
|
||||
|
||||
self.widget = field.field.widget
|
||||
self.initial_attrs = self.widget.attrs.copy()
|
||||
self.field_help = text_value(mark_safe(field.help_text)) \
|
||||
if self.show_help and field.help_text else ''
|
||||
self.field_errors = [conditional_escape(text_value(error))
|
||||
for error in field.errors]
|
||||
|
||||
if get_bootstrap_setting('set_placeholder'):
|
||||
self.placeholder = field.label
|
||||
else:
|
||||
self.placeholder = ''
|
||||
|
||||
self.addon_before = kwargs.get('addon_before', self.initial_attrs.pop('addon_before', ''))
|
||||
self.addon_after = kwargs.get('addon_after', self.initial_attrs.pop('addon_after', ''))
|
||||
|
||||
# These are set in Django or in the global BOOTSTRAP3 settings, and
|
||||
# they can be overwritten in the template
|
||||
error_css_class = kwargs.get('error_css_class', '')
|
||||
required_css_class = kwargs.get('required_css_class', '')
|
||||
bound_css_class = kwargs.get('bound_css_class', '')
|
||||
if error_css_class:
|
||||
self.error_css_class = error_css_class
|
||||
else:
|
||||
self.error_css_class = getattr(
|
||||
field.form, 'error_css_class',
|
||||
get_bootstrap_setting('error_css_class'))
|
||||
if required_css_class:
|
||||
self.required_css_class = required_css_class
|
||||
else:
|
||||
self.required_css_class = getattr(
|
||||
field.form, 'required_css_class',
|
||||
get_bootstrap_setting('required_css_class'))
|
||||
if bound_css_class:
|
||||
self.success_css_class = bound_css_class
|
||||
else:
|
||||
self.success_css_class = getattr(
|
||||
field.form, 'bound_css_class',
|
||||
get_bootstrap_setting('success_css_class'))
|
||||
|
||||
# Handle form.empty_permitted
|
||||
if self.field.form.empty_permitted:
|
||||
self.set_required = False
|
||||
self.required_css_class = ''
|
||||
|
||||
def restore_widget_attrs(self):
|
||||
self.widget.attrs = self.initial_attrs
|
||||
|
||||
def add_class_attrs(self):
|
||||
classes = self.widget.attrs.get('class', '')
|
||||
if isinstance(self.widget, ReadOnlyPasswordHashWidget):
|
||||
classes = add_css_class(
|
||||
classes, 'form-control-static', prepend=True)
|
||||
elif not isinstance(self.widget, (CheckboxInput,
|
||||
RadioSelect,
|
||||
CheckboxSelectMultiple,
|
||||
FileInput)):
|
||||
classes = add_css_class(classes, 'form-control', prepend=True)
|
||||
# For these widget types, add the size class here
|
||||
classes = add_css_class(classes, self.get_size_class())
|
||||
self.widget.attrs['class'] = classes
|
||||
|
||||
def add_placeholder_attrs(self):
|
||||
placeholder = self.widget.attrs.get('placeholder', self.placeholder)
|
||||
if placeholder and is_widget_with_placeholder(self.widget):
|
||||
self.widget.attrs['placeholder'] = placeholder
|
||||
|
||||
def add_help_attrs(self):
|
||||
if not isinstance(self.widget, CheckboxInput):
|
||||
self.widget.attrs['title'] = self.widget.attrs.get(
|
||||
'title', strip_tags(self.field_help))
|
||||
|
||||
def add_required_attrs(self):
|
||||
if self.set_required and is_widget_required_attribute(self.widget):
|
||||
self.widget.attrs['required'] = 'required'
|
||||
|
||||
def add_widget_attrs(self):
|
||||
self.add_class_attrs()
|
||||
self.add_placeholder_attrs()
|
||||
self.add_help_attrs()
|
||||
self.add_required_attrs()
|
||||
|
||||
def list_to_class(self, html, klass):
|
||||
classes = add_css_class(klass, self.get_size_class())
|
||||
mapping = [
|
||||
('<ul', '<div'),
|
||||
('</ul>', '</div>'),
|
||||
('<li', '<div class="{klass}"'.format(klass=classes)),
|
||||
('</li>', '</div>'),
|
||||
]
|
||||
for k, v in mapping:
|
||||
html = html.replace(k, v)
|
||||
return html
|
||||
|
||||
def put_inside_label(self, html):
|
||||
content = '{field} {label}'.format(field=html, label=self.field.label)
|
||||
return render_label(
|
||||
content=content, label_for=self.field.id_for_label,
|
||||
label_title=strip_tags(self.field_help))
|
||||
|
||||
def fix_date_select_input(self, html):
|
||||
div1 = '<div class="col-xs-4">'
|
||||
div2 = '</div>'
|
||||
html = html.replace('<select', div1 + '<select')
|
||||
html = html.replace('</select>', '</select>' + div2)
|
||||
return '<div class="row bootstrap3-multi-input">' + html + '</div>'
|
||||
|
||||
def fix_clearable_file_input(self, html):
|
||||
"""
|
||||
Fix a clearable file input
|
||||
TODO: This needs improvement
|
||||
|
||||
Currently Django returns
|
||||
Currently:
|
||||
<a href="dummy.txt">dummy.txt</a>
|
||||
<input id="file4-clear_id" name="file4-clear" type="checkbox" />
|
||||
<label for="file4-clear_id">Clear</label><br />
|
||||
Change: <input id="id_file4" name="file4" type="file" />
|
||||
<span class=help-block></span>
|
||||
</div>
|
||||
|
||||
"""
|
||||
# TODO This needs improvement
|
||||
return '<div class="row bootstrap3-multi-input">' + \
|
||||
'<div class="col-xs-12">' + html + '</div></div>'
|
||||
|
||||
def post_widget_render(self, html):
|
||||
if isinstance(self.widget, RadioSelect):
|
||||
html = self.list_to_class(html, 'radio')
|
||||
elif isinstance(self.widget, CheckboxSelectMultiple):
|
||||
html = self.list_to_class(html, 'checkbox')
|
||||
elif isinstance(self.widget, SelectDateWidget):
|
||||
html = self.fix_date_select_input(html)
|
||||
elif isinstance(self.widget, ClearableFileInput):
|
||||
html = self.fix_clearable_file_input(html)
|
||||
elif isinstance(self.widget, CheckboxInput):
|
||||
html = self.put_inside_label(html)
|
||||
return html
|
||||
|
||||
def wrap_widget(self, html):
|
||||
if isinstance(self.widget, CheckboxInput):
|
||||
checkbox_class = add_css_class('checkbox', self.get_size_class())
|
||||
html = \
|
||||
'<div class="{klass}">{content}</div>'.format(
|
||||
klass=checkbox_class, content=html
|
||||
)
|
||||
return html
|
||||
|
||||
def make_input_group(self, html):
|
||||
if (
|
||||
(self.addon_before or self.addon_after) and
|
||||
isinstance(self.widget, (TextInput, DateInput, Select))
|
||||
):
|
||||
before = '<span class="input-group-addon">{addon}</span>'.format(
|
||||
addon=self.addon_before) if self.addon_before else ''
|
||||
after = '<span class="input-group-addon">{addon}</span>'.format(
|
||||
addon=self.addon_after) if self.addon_after else ''
|
||||
html = \
|
||||
'<div class="input-group">' + \
|
||||
'{before}{html}{after}</div>'.format(
|
||||
before=before,
|
||||
after=after,
|
||||
html=html
|
||||
)
|
||||
return html
|
||||
|
||||
def append_to_field(self, html):
|
||||
help_text_and_errors = [self.field_help] + self.field_errors \
|
||||
if self.field_help else self.field_errors
|
||||
if help_text_and_errors:
|
||||
help_html = get_template(
|
||||
'bootstrap3/field_help_text_and_errors.html'
|
||||
).render(Context({
|
||||
'field': self.field,
|
||||
'help_text_and_errors': help_text_and_errors,
|
||||
'layout': self.layout,
|
||||
}))
|
||||
html += '<span class="help-block">{help}</span>'.format(
|
||||
help=help_html)
|
||||
return html
|
||||
|
||||
def get_field_class(self):
|
||||
field_class = self.field_class
|
||||
if not field_class and self.layout == 'horizontal':
|
||||
field_class = self.horizontal_field_class
|
||||
return field_class
|
||||
|
||||
def wrap_field(self, html):
|
||||
field_class = self.get_field_class()
|
||||
if field_class:
|
||||
html = '<div class="{klass}">{html}</div>'.format(
|
||||
klass=field_class, html=html)
|
||||
return html
|
||||
|
||||
def get_label_class(self):
|
||||
label_class = self.label_class
|
||||
if not label_class and self.layout == 'horizontal':
|
||||
label_class = self.horizontal_label_class
|
||||
label_class = text_value(label_class)
|
||||
if not self.show_label:
|
||||
label_class = add_css_class(label_class, 'sr-only')
|
||||
return add_css_class(label_class, 'control-label')
|
||||
|
||||
def get_label(self):
|
||||
if isinstance(self.widget, CheckboxInput):
|
||||
label = None
|
||||
else:
|
||||
label = self.field.label
|
||||
if self.layout == 'horizontal' and not label:
|
||||
return ' '
|
||||
return label
|
||||
|
||||
def add_label(self, html):
|
||||
label = self.get_label()
|
||||
if label:
|
||||
html = render_label(
|
||||
label,
|
||||
label_for=self.field.id_for_label,
|
||||
label_class=self.get_label_class()) + html
|
||||
return html
|
||||
|
||||
def get_form_group_class(self):
|
||||
form_group_class = self.form_group_class
|
||||
if self.field.errors and self.error_css_class:
|
||||
form_group_class = add_css_class(
|
||||
form_group_class, self.error_css_class)
|
||||
if self.field.field.required and self.required_css_class:
|
||||
form_group_class = add_css_class(
|
||||
form_group_class, self.required_css_class)
|
||||
if self.field_errors:
|
||||
form_group_class = add_css_class(form_group_class, 'has-error')
|
||||
elif self.field.form.is_bound:
|
||||
form_group_class = add_css_class(
|
||||
form_group_class, self.success_css_class)
|
||||
if self.layout == 'horizontal':
|
||||
form_group_class = add_css_class(
|
||||
form_group_class, self.get_size_class(prefix='form-group'))
|
||||
return form_group_class
|
||||
|
||||
def wrap_label_and_field(self, html):
|
||||
return render_form_group(html, self.get_form_group_class())
|
||||
|
||||
def render(self):
|
||||
# See if we're not excluded
|
||||
if self.field.name in self.exclude.replace(' ', '').split(','):
|
||||
return ''
|
||||
# Hidden input requires no special treatment
|
||||
if self.field.is_hidden:
|
||||
return text_value(self.field)
|
||||
# Render the widget
|
||||
self.add_widget_attrs()
|
||||
html = self.field.as_widget(attrs=self.widget.attrs)
|
||||
self.restore_widget_attrs()
|
||||
# Start post render
|
||||
html = self.post_widget_render(html)
|
||||
html = self.wrap_widget(html)
|
||||
html = self.make_input_group(html)
|
||||
html = self.append_to_field(html)
|
||||
html = self.wrap_field(html)
|
||||
html = self.add_label(html)
|
||||
html = self.wrap_label_and_field(html)
|
||||
return html
|
||||
|
||||
|
||||
class InlineFieldRenderer(FieldRenderer):
|
||||
"""
|
||||
Inline field renderer
|
||||
"""
|
||||
|
||||
def add_error_attrs(self):
|
||||
field_title = self.widget.attrs.get('title', '')
|
||||
field_title += ' ' + ' '.join(
|
||||
[strip_tags(e) for e in self.field_errors])
|
||||
self.widget.attrs['title'] = field_title.strip()
|
||||
|
||||
def add_widget_attrs(self):
|
||||
super(InlineFieldRenderer, self).add_widget_attrs()
|
||||
self.add_error_attrs()
|
||||
|
||||
def append_to_field(self, html):
|
||||
return html
|
||||
|
||||
def get_field_class(self):
|
||||
return self.field_class
|
||||
|
||||
def get_label_class(self):
|
||||
return add_css_class(self.label_class, 'sr-only')
|
56
bootstrap3/templates.py
Normal file
56
bootstrap3/templates.py
Normal file
|
@ -0,0 +1,56 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import re
|
||||
|
||||
from django.template import Variable, VariableDoesNotExist
|
||||
from django.template.base import (
|
||||
FilterExpression, kwarg_re, TemplateSyntaxError
|
||||
)
|
||||
|
||||
# Extra features for template file handling
|
||||
|
||||
QUOTED_STRING = re.compile(r'^["\'](?P<noquotes>.+)["\']$')
|
||||
|
||||
|
||||
def handle_var(value, context):
|
||||
# Resolve FilterExpression and Variable immediately
|
||||
if isinstance(value, FilterExpression) or isinstance(value, Variable):
|
||||
return value.resolve(context)
|
||||
# Return quoted strings unquotes, from djangosnippets.org/snippets/886
|
||||
stringval = QUOTED_STRING.search(value)
|
||||
if stringval:
|
||||
return stringval.group('noquotes')
|
||||
# Resolve variable or return string value
|
||||
try:
|
||||
return Variable(value).resolve(context)
|
||||
except VariableDoesNotExist:
|
||||
return value
|
||||
|
||||
|
||||
def parse_token_contents(parser, token):
|
||||
bits = token.split_contents()
|
||||
tag = bits.pop(0)
|
||||
args = []
|
||||
kwargs = {}
|
||||
asvar = None
|
||||
if len(bits) >= 2 and bits[-2] == 'as':
|
||||
asvar = bits[-1]
|
||||
bits = bits[:-2]
|
||||
if len(bits):
|
||||
for bit in bits:
|
||||
match = kwarg_re.match(bit)
|
||||
if not match:
|
||||
raise TemplateSyntaxError(
|
||||
'Malformed arguments to tag "{}"'.format(tag))
|
||||
name, value = match.groups()
|
||||
if name:
|
||||
kwargs[name] = parser.compile_filter(value)
|
||||
else:
|
||||
args.append(parser.compile_filter(value))
|
||||
return {
|
||||
'tag': tag,
|
||||
'args': args,
|
||||
'kwargs': kwargs,
|
||||
'asvar': asvar,
|
||||
}
|
27
bootstrap3/templates/bootstrap3/bootstrap3.html
Normal file
27
bootstrap3/templates/bootstrap3/bootstrap3.html
Normal file
|
@ -0,0 +1,27 @@
|
|||
<!DOCTYPE html>
|
||||
{% load bootstrap3 %}
|
||||
<html{% if request.LANGUAGE_CODE %} lang="{{ request.LANGUAGE_CODE }}"{% endif %}>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<!--[if IE]><meta http-equiv="X-UA-Compatible" content="IE=edge" /><![endif]-->
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{% block bootstrap3_title %}django-bootstrap3 template title{% endblock %}</title>
|
||||
{% bootstrap_css %}
|
||||
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
|
||||
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
|
||||
<!--[if lt IE 9]>
|
||||
<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
|
||||
<script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
|
||||
<![endif]-->
|
||||
{% if 'javascript_in_head'|bootstrap_setting %}{% bootstrap_javascript jquery=True %}{% endif %}
|
||||
{% block bootstrap3_extra_head %}{% endblock %}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
{% block bootstrap3_content %}django-bootstrap3 template content{% endblock %}
|
||||
{% if not 'javascript_in_head'|bootstrap_setting %}{% bootstrap_javascript jquery=True %}{% endif %}
|
||||
{% block bootstrap3_extra_script %}{% endblock %}
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -0,0 +1 @@
|
|||
{{ help_text_and_errors|join:' ' }}
|
6
bootstrap3/templates/bootstrap3/form_errors.html
Normal file
6
bootstrap3/templates/bootstrap3/form_errors.html
Normal file
|
@ -0,0 +1,6 @@
|
|||
<div class="alert alert-danger alert-dismissable alert-link">
|
||||
<button class="close" type="button" data-dismiss="alert" aria-hidden="true">×</button>
|
||||
{% for error in errors %}
|
||||
{{ error }}{% if not forloop.last %}<br>{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
6
bootstrap3/templates/bootstrap3/messages.html
Normal file
6
bootstrap3/templates/bootstrap3/messages.html
Normal file
|
@ -0,0 +1,6 @@
|
|||
{% for message in messages %}
|
||||
<div class="alert{% if message.tags %} alert-{% if message.tags == 'error' %}danger{% else %}{{ message.tags }}{% endif %}{% endif %} alert-dismissable">
|
||||
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
|
||||
{{ message|safe }}
|
||||
</div>
|
||||
{% endfor %}
|
33
bootstrap3/templates/bootstrap3/pagination.html
Normal file
33
bootstrap3/templates/bootstrap3/pagination.html
Normal file
|
@ -0,0 +1,33 @@
|
|||
{% with bootstrap_pagination_url=bootstrap_pagination_url|default:"?" %}
|
||||
|
||||
<ul class="{{ pagination_css_classes }}">
|
||||
|
||||
<li class="prev{% if current_page == 1 %} disabled{% endif %}">
|
||||
<a href="{% if current_page == 1 %}#{% else %}{{ bootstrap_pagination_url }}{{ parameter_name }}=1{% endif %}">«</a>
|
||||
</li>
|
||||
|
||||
{% if pages_back %}
|
||||
<li>
|
||||
<a href="{{ bootstrap_pagination_url }}{{ parameter_name }}={{ pages_back }}">…</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
{% for p in pages_shown %}
|
||||
<li{% if current_page == p %} class="active"{% endif %}>
|
||||
<a href="{% if current_page == p %}#{% else %}{{ bootstrap_pagination_url }}{{ parameter_name }}={{ p }}{% endif %}">{{ p }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
|
||||
{% if pages_forward %}
|
||||
<li>
|
||||
<a href="{{ bootstrap_pagination_url }}{{ parameter_name }}={{ pages_forward }}">…</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
<li class="last{% if current_page == num_pages %} disabled{% endif %}">
|
||||
<a href="{% if current_page == num_pages %}#{% else %}{{ bootstrap_pagination_url }}{{ parameter_name }}={{ num_pages }}{% endif %}">»</a>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
|
||||
{% endwith %}
|
1
bootstrap3/templatetags/__init__.py
Normal file
1
bootstrap3/templatetags/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
# -*- coding: utf-8 -*-
|
637
bootstrap3/templatetags/bootstrap3.py
Normal file
637
bootstrap3/templatetags/bootstrap3.py
Normal file
|
@ -0,0 +1,637 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import re
|
||||
|
||||
from math import floor
|
||||
|
||||
from django import template
|
||||
from django.template.loader import get_template
|
||||
|
||||
from ..bootstrap import (
|
||||
css_url, javascript_url, jquery_url, theme_url, get_bootstrap_setting
|
||||
)
|
||||
from ..utils import render_link_tag
|
||||
from ..forms import (
|
||||
render_button, render_field, render_field_and_label, render_form,
|
||||
render_form_group, render_formset,
|
||||
render_label, render_form_errors, render_formset_errors
|
||||
)
|
||||
from ..components import render_icon, render_alert
|
||||
from ..templates import handle_var, parse_token_contents
|
||||
from ..text import force_text
|
||||
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.filter
|
||||
def bootstrap_setting(value):
|
||||
"""
|
||||
A simple way to read bootstrap settings in a template.
|
||||
Please consider this filter private for now, do not use it in your own
|
||||
templates.
|
||||
"""
|
||||
return get_bootstrap_setting(value)
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def bootstrap_jquery_url():
|
||||
"""
|
||||
**Tag name**::
|
||||
|
||||
bootstrap_jquery_url
|
||||
|
||||
Return the full url to jQuery file to use
|
||||
|
||||
Default value: ``//code.jquery.com/jquery.min.js``
|
||||
|
||||
This value is configurable, see Settings section
|
||||
|
||||
**usage**::
|
||||
|
||||
{% bootstrap_jquery_url %}
|
||||
|
||||
**example**::
|
||||
|
||||
{% bootstrap_jquery_url %}
|
||||
"""
|
||||
return jquery_url()
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def bootstrap_javascript_url():
|
||||
"""
|
||||
Return the full url to the Bootstrap JavaScript library
|
||||
|
||||
Default value: ``None``
|
||||
|
||||
This value is configurable, see Settings section
|
||||
|
||||
**Tag name**::
|
||||
|
||||
bootstrap_javascript_url
|
||||
|
||||
**usage**::
|
||||
|
||||
{% bootstrap_javascript_url %}
|
||||
|
||||
**example**::
|
||||
|
||||
{% bootstrap_javascript_url %}
|
||||
"""
|
||||
return javascript_url()
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def bootstrap_css_url():
|
||||
"""
|
||||
Return the full url to the Bootstrap CSS library
|
||||
|
||||
Default value: ``None``
|
||||
|
||||
This value is configurable, see Settings section
|
||||
|
||||
**Tag name**::
|
||||
|
||||
bootstrap_css_url
|
||||
|
||||
**usage**::
|
||||
|
||||
{% bootstrap_css_url %}
|
||||
|
||||
**example**::
|
||||
|
||||
{% bootstrap_css_url %}
|
||||
"""
|
||||
return css_url()
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def bootstrap_theme_url():
|
||||
"""
|
||||
Return the full url to a Bootstrap theme CSS library
|
||||
|
||||
Default value: ``None``
|
||||
|
||||
This value is configurable, see Settings section
|
||||
|
||||
**Tag name**::
|
||||
|
||||
bootstrap_css_url
|
||||
|
||||
**usage**::
|
||||
|
||||
{% bootstrap_css_url %}
|
||||
|
||||
**example**::
|
||||
|
||||
{% bootstrap_css_url %}
|
||||
"""
|
||||
return theme_url()
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def bootstrap_css():
|
||||
"""
|
||||
Return HTML for Bootstrap CSS
|
||||
Adjust url in settings. If no url is returned, we don't want this statement
|
||||
to return any HTML.
|
||||
This is intended behavior.
|
||||
|
||||
Default value: ``FIXTHIS``
|
||||
|
||||
This value is configurable, see Settings section
|
||||
|
||||
**Tag name**::
|
||||
|
||||
bootstrap_css
|
||||
|
||||
**usage**::
|
||||
|
||||
{% bootstrap_css %}
|
||||
|
||||
**example**::
|
||||
|
||||
{% bootstrap_css %}
|
||||
"""
|
||||
urls = [url for url in [bootstrap_css_url(), bootstrap_theme_url()] if url]
|
||||
return ''.join([render_link_tag(url) for url in urls])
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def bootstrap_javascript(jquery=None):
|
||||
"""
|
||||
Return HTML for Bootstrap JavaScript.
|
||||
|
||||
Adjust url in settings. If no url is returned, we don't want this
|
||||
statement to return any HTML.
|
||||
This is intended behavior.
|
||||
|
||||
Default value: ``None``
|
||||
|
||||
This value is configurable, see Settings section
|
||||
|
||||
**Tag name**::
|
||||
|
||||
bootstrap_javascript
|
||||
|
||||
**Parameters**:
|
||||
|
||||
:jquery: Truthy to include jQuery as well as Bootstrap
|
||||
|
||||
**usage**::
|
||||
|
||||
{% bootstrap_javascript %}
|
||||
|
||||
**example**::
|
||||
|
||||
{% bootstrap_javascript jquery=1 %}
|
||||
"""
|
||||
|
||||
javascript = ''
|
||||
# See if we have to include jQuery
|
||||
if jquery is None:
|
||||
jquery = get_bootstrap_setting('include_jquery', False)
|
||||
# NOTE: No async on scripts, not mature enough. See issue #52 and #56
|
||||
if jquery:
|
||||
url = bootstrap_jquery_url()
|
||||
if url:
|
||||
javascript += '<script src="{url}"></script>'.format(url=url)
|
||||
url = bootstrap_javascript_url()
|
||||
if url:
|
||||
javascript += '<script src="{url}"></script>'.format(url=url)
|
||||
return javascript
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def bootstrap_formset(*args, **kwargs):
|
||||
"""
|
||||
Render a formset
|
||||
|
||||
|
||||
**Tag name**::
|
||||
|
||||
bootstrap_formset
|
||||
|
||||
**Parameters**:
|
||||
|
||||
:args:
|
||||
:kwargs:
|
||||
|
||||
**usage**::
|
||||
|
||||
{% bootstrap_formset formset %}
|
||||
|
||||
**example**::
|
||||
|
||||
{% bootstrap_formset formset layout='horizontal' %}
|
||||
|
||||
"""
|
||||
return render_formset(*args, **kwargs)
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def bootstrap_formset_errors(*args, **kwargs):
|
||||
"""
|
||||
Render form errors
|
||||
|
||||
**Tag name**::
|
||||
|
||||
bootstrap_form_errors
|
||||
|
||||
**Parameters**:
|
||||
|
||||
:args:
|
||||
:kwargs:
|
||||
|
||||
**usage**::
|
||||
|
||||
{% bootstrap_form_errors form %}
|
||||
|
||||
**example**::
|
||||
|
||||
{% bootstrap_form_errors form layout='inline' %}
|
||||
"""
|
||||
return render_formset_errors(*args, **kwargs)
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def bootstrap_form(*args, **kwargs):
|
||||
"""
|
||||
Render a form
|
||||
|
||||
**Tag name**::
|
||||
|
||||
bootstrap_form
|
||||
|
||||
**Parameters**:
|
||||
|
||||
:args:
|
||||
:kwargs:
|
||||
|
||||
**usage**::
|
||||
|
||||
{% bootstrap_form form %}
|
||||
|
||||
**example**::
|
||||
|
||||
{% bootstrap_form form layout='inline' %}
|
||||
"""
|
||||
return render_form(*args, **kwargs)
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def bootstrap_form_errors(*args, **kwargs):
|
||||
"""
|
||||
Render form errors
|
||||
|
||||
**Tag name**::
|
||||
|
||||
bootstrap_form_errors
|
||||
|
||||
**Parameters**:
|
||||
|
||||
:args:
|
||||
:kwargs:
|
||||
|
||||
**usage**::
|
||||
|
||||
{% bootstrap_form_errors form %}
|
||||
|
||||
**example**::
|
||||
|
||||
{% bootstrap_form_errors form layout='inline' %}
|
||||
"""
|
||||
return render_form_errors(*args, **kwargs)
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def bootstrap_field(*args, **kwargs):
|
||||
"""
|
||||
Render a field
|
||||
|
||||
**Tag name**::
|
||||
|
||||
bootstrap_field
|
||||
|
||||
**Parameters**:
|
||||
|
||||
:args:
|
||||
:kwargs:
|
||||
|
||||
**usage**::
|
||||
|
||||
{% bootstrap_field form_field %}
|
||||
|
||||
**example**::
|
||||
|
||||
{% bootstrap_field form_field %}
|
||||
"""
|
||||
return render_field(*args, **kwargs)
|
||||
|
||||
|
||||
@register.simple_tag()
|
||||
def bootstrap_label(*args, **kwargs):
|
||||
"""
|
||||
Render a label
|
||||
|
||||
**Tag name**::
|
||||
|
||||
bootstrap_label
|
||||
|
||||
**Parameters**:
|
||||
|
||||
:args:
|
||||
:kwargs:
|
||||
|
||||
**usage**::
|
||||
|
||||
{% bootstrap_label FIXTHIS %}
|
||||
|
||||
**example**::
|
||||
|
||||
{% bootstrap_label FIXTHIS %}
|
||||
"""
|
||||
return render_label(*args, **kwargs)
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def bootstrap_button(*args, **kwargs):
|
||||
"""
|
||||
Render a button
|
||||
|
||||
**Tag name**::
|
||||
|
||||
bootstrap_button
|
||||
|
||||
**Parameters**:
|
||||
|
||||
:args:
|
||||
:kwargs:
|
||||
|
||||
**usage**::
|
||||
|
||||
{% bootstrap_button FIXTHIS %}
|
||||
|
||||
**example**::
|
||||
|
||||
{% bootstrap_button FIXTHIS %}
|
||||
"""
|
||||
return render_button(*args, **kwargs)
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def bootstrap_icon(icon, **kwargs):
|
||||
"""
|
||||
Render an icon
|
||||
|
||||
**Tag name**::
|
||||
|
||||
bootstrap_icon
|
||||
|
||||
**Parameters**:
|
||||
|
||||
:icon: icon name
|
||||
|
||||
**usage**::
|
||||
|
||||
{% bootstrap_icon "icon_name" %}
|
||||
|
||||
**example**::
|
||||
|
||||
{% bootstrap_icon "star" %}
|
||||
|
||||
"""
|
||||
return render_icon(icon, **kwargs)
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def bootstrap_alert(content, alert_type='info', dismissable=True):
|
||||
"""
|
||||
Render an alert
|
||||
|
||||
**Tag name**::
|
||||
|
||||
bootstrap_alert
|
||||
|
||||
**Parameters**:
|
||||
|
||||
:content: HTML content of alert
|
||||
:alert_type: one of 'info', 'warning', 'danger' or 'success'
|
||||
:dismissable: boolean, is alert dismissable
|
||||
|
||||
**usage**::
|
||||
|
||||
{% bootstrap_alert "my_content" %}
|
||||
|
||||
**example**::
|
||||
|
||||
{% bootstrap_alert "Something went wrong" alert_type='error' %}
|
||||
|
||||
"""
|
||||
return render_alert(content, alert_type, dismissable)
|
||||
|
||||
|
||||
@register.tag('buttons')
|
||||
def bootstrap_buttons(parser, token):
|
||||
"""
|
||||
Render buttons for form
|
||||
|
||||
**Tag name**::
|
||||
|
||||
bootstrap_buttons
|
||||
|
||||
**Parameters**:
|
||||
|
||||
:parser:
|
||||
:token:
|
||||
|
||||
**usage**::
|
||||
|
||||
{% bootstrap_buttons FIXTHIS %}
|
||||
|
||||
**example**::
|
||||
|
||||
{% bootstrap_buttons FIXTHIS %}
|
||||
"""
|
||||
kwargs = parse_token_contents(parser, token)
|
||||
kwargs['nodelist'] = parser.parse(('endbuttons', ))
|
||||
parser.delete_first_token()
|
||||
return ButtonsNode(**kwargs)
|
||||
|
||||
|
||||
class ButtonsNode(template.Node):
|
||||
def __init__(self, nodelist, args, kwargs, asvar, **kwargs2):
|
||||
self.nodelist = nodelist
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
self.asvar = asvar
|
||||
|
||||
def render(self, context):
|
||||
output_kwargs = {}
|
||||
for key in self.kwargs:
|
||||
output_kwargs[key] = handle_var(self.kwargs[key], context)
|
||||
buttons = []
|
||||
submit = output_kwargs.get('submit', None)
|
||||
reset = output_kwargs.get('reset', None)
|
||||
if submit:
|
||||
buttons.append(bootstrap_button(submit, 'submit'))
|
||||
if reset:
|
||||
buttons.append(bootstrap_button(reset, 'reset'))
|
||||
buttons = ' '.join(buttons) + self.nodelist.render(context)
|
||||
output_kwargs.update({
|
||||
'label': None,
|
||||
'field': buttons,
|
||||
})
|
||||
output = render_form_group(render_field_and_label(**output_kwargs))
|
||||
if self.asvar:
|
||||
context[self.asvar] = output
|
||||
return ''
|
||||
else:
|
||||
return output
|
||||
|
||||
|
||||
@register.simple_tag(takes_context=True)
|
||||
def bootstrap_messages(context, *args, **kwargs):
|
||||
"""
|
||||
Show django.contrib.messages Messages in Bootstrap alert containers.
|
||||
|
||||
In order to make the alerts dismissable (with the close button),
|
||||
we have to set the jquery parameter too when using the
|
||||
bootstrap_javascript tag.
|
||||
|
||||
**Tag name**::
|
||||
|
||||
bootstrap_messages
|
||||
|
||||
**Parameters**:
|
||||
|
||||
:context:
|
||||
:args:
|
||||
:kwargs:
|
||||
|
||||
**usage**::
|
||||
|
||||
{% bootstrap_messages FIXTHIS %}
|
||||
|
||||
**example**::
|
||||
|
||||
{% bootstrap_javascript jquery=1 %}
|
||||
{% bootstrap_messages FIXTHIS %}
|
||||
|
||||
"""
|
||||
return get_template('bootstrap3/messages.html').render(context)
|
||||
|
||||
|
||||
@register.inclusion_tag('bootstrap3/pagination.html')
|
||||
def bootstrap_pagination(page, **kwargs):
|
||||
"""
|
||||
Render pagination for a page
|
||||
|
||||
**Tag name**::
|
||||
|
||||
bootstrap_pagination
|
||||
|
||||
**Parameters**:
|
||||
|
||||
:page:
|
||||
:parameter_name: Name of paging URL parameter (default: "page")
|
||||
:kwargs:
|
||||
|
||||
**usage**::
|
||||
|
||||
{% bootstrap_pagination FIXTHIS %}
|
||||
|
||||
**example**::
|
||||
|
||||
{% bootstrap_pagination FIXTHIS %}
|
||||
"""
|
||||
|
||||
pagination_kwargs = kwargs.copy()
|
||||
pagination_kwargs['page'] = page
|
||||
return get_pagination_context(**pagination_kwargs)
|
||||
|
||||
|
||||
def get_pagination_context(page, pages_to_show=11,
|
||||
url=None, size=None, extra=None,
|
||||
parameter_name='page'):
|
||||
"""
|
||||
Generate Bootstrap pagination context from a page object
|
||||
"""
|
||||
pages_to_show = int(pages_to_show)
|
||||
if pages_to_show < 1:
|
||||
raise ValueError("Pagination pages_to_show should be a positive " +
|
||||
"integer, you specified {pages}".format(
|
||||
pages=pages_to_show))
|
||||
num_pages = page.paginator.num_pages
|
||||
current_page = page.number
|
||||
half_page_num = int(floor(pages_to_show / 2)) - 1
|
||||
if half_page_num < 0:
|
||||
half_page_num = 0
|
||||
first_page = current_page - half_page_num
|
||||
if first_page <= 1:
|
||||
first_page = 1
|
||||
if first_page > 1:
|
||||
pages_back = first_page - half_page_num
|
||||
if pages_back < 1:
|
||||
pages_back = 1
|
||||
else:
|
||||
pages_back = None
|
||||
last_page = first_page + pages_to_show - 1
|
||||
if pages_back is None:
|
||||
last_page += 1
|
||||
if last_page > num_pages:
|
||||
last_page = num_pages
|
||||
if last_page < num_pages:
|
||||
pages_forward = last_page + half_page_num
|
||||
if pages_forward > num_pages:
|
||||
pages_forward = num_pages
|
||||
else:
|
||||
pages_forward = None
|
||||
if first_page > 1:
|
||||
first_page -= 1
|
||||
if pages_back is not None and pages_back > 1:
|
||||
pages_back -= 1
|
||||
else:
|
||||
pages_back = None
|
||||
pages_shown = []
|
||||
for i in range(first_page, last_page + 1):
|
||||
pages_shown.append(i)
|
||||
# Append proper character to url
|
||||
if url:
|
||||
# Remove existing page GET parameters
|
||||
url = force_text(url)
|
||||
url = re.sub(r'\?{0}\=[^\&]+'.format(parameter_name), '?', url)
|
||||
url = re.sub(r'\&{0}\=[^\&]+'.format(parameter_name), '', url)
|
||||
# Append proper separator
|
||||
if '?' in url:
|
||||
url += '&'
|
||||
else:
|
||||
url += '?'
|
||||
# Append extra string to url
|
||||
if extra:
|
||||
if not url:
|
||||
url = '?'
|
||||
url += force_text(extra) + '&'
|
||||
if url:
|
||||
url = url.replace('?&', '?')
|
||||
# Set CSS classes, see http://getbootstrap.com/components/#pagination
|
||||
pagination_css_classes = ['pagination']
|
||||
if size == 'small':
|
||||
pagination_css_classes.append('pagination-sm')
|
||||
elif size == 'large':
|
||||
pagination_css_classes.append('pagination-lg')
|
||||
# Build context object
|
||||
return {
|
||||
'bootstrap_pagination_url': url,
|
||||
'num_pages': num_pages,
|
||||
'current_page': current_page,
|
||||
'first_page': first_page,
|
||||
'last_page': last_page,
|
||||
'pages_shown': pages_shown,
|
||||
'pages_back': pages_back,
|
||||
'pages_forward': pages_forward,
|
||||
'pagination_css_classes': ' '.join(pagination_css_classes),
|
||||
'parameter_name': parameter_name,
|
||||
}
|
447
bootstrap3/tests.py
Normal file
447
bootstrap3/tests.py
Normal file
|
@ -0,0 +1,447 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import re
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
from django import forms
|
||||
from django.template import Template, Context
|
||||
|
||||
from .text import text_value, text_concat
|
||||
from .exceptions import BootstrapError
|
||||
from .utils import add_css_class
|
||||
|
||||
|
||||
RADIO_CHOICES = (
|
||||
('1', 'Radio 1'),
|
||||
('2', 'Radio 2'),
|
||||
)
|
||||
|
||||
MEDIA_CHOICES = (
|
||||
('Audio', (
|
||||
('vinyl', 'Vinyl'),
|
||||
('cd', 'CD'),
|
||||
)
|
||||
),
|
||||
('Video', (
|
||||
('vhs', 'VHS Tape'),
|
||||
('dvd', 'DVD'),
|
||||
)
|
||||
),
|
||||
('unknown', 'Unknown'),
|
||||
)
|
||||
|
||||
|
||||
class TestForm(forms.Form):
|
||||
"""
|
||||
Form with a variety of widgets to test bootstrap3 rendering.
|
||||
"""
|
||||
date = forms.DateField(required=False)
|
||||
subject = forms.CharField(
|
||||
max_length=100,
|
||||
help_text='my_help_text',
|
||||
required=True,
|
||||
widget=forms.TextInput(attrs={'placeholder': 'placeholdertest'}),
|
||||
)
|
||||
message = forms.CharField(required=False, help_text='<i>my_help_text</i>')
|
||||
sender = forms.EmailField(label='Sender © unicode')
|
||||
secret = forms.CharField(initial=42, widget=forms.HiddenInput)
|
||||
cc_myself = forms.BooleanField(
|
||||
required=False, help_text='You will get a copy in your mailbox.')
|
||||
select1 = forms.ChoiceField(choices=RADIO_CHOICES)
|
||||
select2 = forms.MultipleChoiceField(
|
||||
choices=RADIO_CHOICES,
|
||||
help_text='Check as many as you like.',
|
||||
)
|
||||
select3 = forms.ChoiceField(choices=MEDIA_CHOICES)
|
||||
select4 = forms.MultipleChoiceField(
|
||||
choices=MEDIA_CHOICES,
|
||||
help_text='Check as many as you like.',
|
||||
)
|
||||
category1 = forms.ChoiceField(
|
||||
choices=RADIO_CHOICES, widget=forms.RadioSelect)
|
||||
category2 = forms.MultipleChoiceField(
|
||||
choices=RADIO_CHOICES,
|
||||
widget=forms.CheckboxSelectMultiple,
|
||||
help_text='Check as many as you like.',
|
||||
)
|
||||
category3 = forms.ChoiceField(
|
||||
widget=forms.RadioSelect, choices=MEDIA_CHOICES)
|
||||
category4 = forms.MultipleChoiceField(
|
||||
choices=MEDIA_CHOICES,
|
||||
widget=forms.CheckboxSelectMultiple,
|
||||
help_text='Check as many as you like.',
|
||||
)
|
||||
addon = forms.CharField(
|
||||
widget=forms.TextInput(attrs={'addon_before': 'before', 'addon_after': 'after'}),
|
||||
)
|
||||
|
||||
required_css_class = 'bootstrap3-req'
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super(TestForm, self).clean()
|
||||
raise forms.ValidationError(
|
||||
"This error was added to show the non field errors styling.")
|
||||
return cleaned_data
|
||||
|
||||
|
||||
class TestFormWithoutRequiredClass(TestForm):
|
||||
required_css_class = ''
|
||||
|
||||
|
||||
def render_template(text, **context_args):
|
||||
"""
|
||||
Create a template ``text`` that first loads bootstrap3.
|
||||
"""
|
||||
template = Template("{% load bootstrap3 %}" + text)
|
||||
if 'form' not in context_args:
|
||||
context_args['form'] = TestForm()
|
||||
return template.render(Context(context_args))
|
||||
|
||||
|
||||
def render_formset(formset=None, **context_args):
|
||||
"""
|
||||
Create a template that renders a formset
|
||||
"""
|
||||
context_args['formset'] = formset
|
||||
return render_template('{% bootstrap_formset formset %}', **context_args)
|
||||
|
||||
|
||||
def render_form(form=None, **context_args):
|
||||
"""
|
||||
Create a template that renders a form
|
||||
"""
|
||||
if form:
|
||||
context_args['form'] = form
|
||||
return render_template('{% bootstrap_form form %}', **context_args)
|
||||
|
||||
|
||||
def render_form_field(field, **context_args):
|
||||
"""
|
||||
Create a template that renders a field
|
||||
"""
|
||||
form_field = 'form.%s' % field
|
||||
return render_template(
|
||||
'{% bootstrap_field ' + form_field + ' %}', **context_args)
|
||||
|
||||
|
||||
def render_field(field, **context_args):
|
||||
"""
|
||||
Create a template that renders a field
|
||||
"""
|
||||
context_args['field'] = field
|
||||
return render_template('{% bootstrap_field field %}', **context_args)
|
||||
|
||||
|
||||
class SettingsTest(TestCase):
|
||||
def test_settings(self):
|
||||
from .bootstrap import BOOTSTRAP3
|
||||
self.assertTrue(BOOTSTRAP3)
|
||||
|
||||
def test_settings_filter(self):
|
||||
res = render_template(
|
||||
'{% load bootstrap3 %}' +
|
||||
'{{ "required_css_class"|bootstrap_setting }}')
|
||||
self.assertEqual(res.strip(), 'bootstrap3-req')
|
||||
res = render_template(
|
||||
'{% load bootstrap3 %}' +
|
||||
'{% if "javascript_in_head"|bootstrap_setting %}' +
|
||||
'head{% else %}body{% endif %}'
|
||||
)
|
||||
self.assertEqual(res.strip(), 'head')
|
||||
|
||||
def test_required_class(self):
|
||||
form = TestForm()
|
||||
res = render_template('{% bootstrap_form form %}', form=form)
|
||||
self.assertIn('bootstrap3-req', res)
|
||||
|
||||
def test_error_class(self):
|
||||
form = TestForm({})
|
||||
res = render_template('{% bootstrap_form form %}', form=form)
|
||||
self.assertIn('bootstrap3-err', res)
|
||||
|
||||
def test_bound_class(self):
|
||||
form = TestForm({'sender': 'sender'})
|
||||
res = render_template('{% bootstrap_form form %}', form=form)
|
||||
self.assertIn('bootstrap3-bound', res)
|
||||
|
||||
|
||||
class TemplateTest(TestCase):
|
||||
def test_empty_template(self):
|
||||
res = render_template('')
|
||||
self.assertEqual(res.strip(), '')
|
||||
|
||||
def test_text_template(self):
|
||||
res = render_template('some text')
|
||||
self.assertEqual(res.strip(), 'some text')
|
||||
|
||||
def test_bootstrap_template(self):
|
||||
template = Template((
|
||||
'{% extends "bootstrap3/bootstrap3.html" %}' +
|
||||
'{% block bootstrap3_content %}' +
|
||||
'test_bootstrap3_content' +
|
||||
'{% endblock %}'
|
||||
))
|
||||
res = template.render(Context({}))
|
||||
self.assertIn('test_bootstrap3_content', res)
|
||||
|
||||
def test_javascript_without_jquery(self):
|
||||
res = render_template('{% bootstrap_javascript jquery=0 %}')
|
||||
self.assertIn('bootstrap', res)
|
||||
self.assertNotIn('jquery', res)
|
||||
|
||||
def test_javascript_with_jquery(self):
|
||||
res = render_template('{% bootstrap_javascript jquery=1 %}')
|
||||
self.assertIn('bootstrap', res)
|
||||
self.assertIn('jquery', res)
|
||||
|
||||
|
||||
class FormSetTest(TestCase):
|
||||
def test_illegal_formset(self):
|
||||
with self.assertRaises(BootstrapError):
|
||||
render_formset(formset='illegal')
|
||||
|
||||
|
||||
class FormTest(TestCase):
|
||||
def test_illegal_form(self):
|
||||
with self.assertRaises(BootstrapError):
|
||||
render_form(form='illegal')
|
||||
|
||||
def test_field_names(self):
|
||||
form = TestForm()
|
||||
res = render_form(form)
|
||||
for field in form:
|
||||
self.assertIn('name="%s"' % field.name, res)
|
||||
|
||||
def test_field_addons(self):
|
||||
form = TestForm()
|
||||
res = render_form(form)
|
||||
self.assertIn('<div class="input-group"><span class="input-group-addon">before</span><input', res)
|
||||
self.assertIn('/><span class="input-group-addon">after</span></div>', res)
|
||||
|
||||
def test_exclude(self):
|
||||
form = TestForm()
|
||||
res = render_template(
|
||||
'{% bootstrap_form form exclude="cc_myself" %}', form=form)
|
||||
self.assertNotIn('cc_myself', res)
|
||||
|
||||
def test_layout_horizontal(self):
|
||||
form = TestForm()
|
||||
res = render_template(
|
||||
'{% bootstrap_form form layout="horizontal" %}', form=form)
|
||||
self.assertIn('col-md-2', res)
|
||||
self.assertIn('col-md-4', res)
|
||||
res = render_template(
|
||||
'{% bootstrap_form form layout="horizontal" ' +
|
||||
'horizontal_label_class="hlabel" ' +
|
||||
'horizontal_field_class="hfield" %}',
|
||||
form=form
|
||||
)
|
||||
self.assertIn('hlabel', res)
|
||||
self.assertIn('hfield', res)
|
||||
|
||||
def test_buttons_tag(self):
|
||||
form = TestForm()
|
||||
res = render_template(
|
||||
'{% buttons layout="horizontal" %}{% endbuttons %}', form=form)
|
||||
self.assertIn('col-md-2', res)
|
||||
self.assertIn('col-md-4', res)
|
||||
|
||||
|
||||
class FieldTest(TestCase):
|
||||
def test_illegal_field(self):
|
||||
with self.assertRaises(BootstrapError):
|
||||
render_field(field='illegal')
|
||||
|
||||
def test_show_help(self):
|
||||
res = render_form_field('subject')
|
||||
self.assertIn('my_help_text', res)
|
||||
self.assertNotIn('<i>my_help_text</i>', res)
|
||||
res = render_template('{% bootstrap_field form.subject show_help=0 %}')
|
||||
self.assertNotIn('my_help_text', res)
|
||||
|
||||
def test_subject(self):
|
||||
res = render_form_field('subject')
|
||||
self.assertIn('type="text"', res)
|
||||
self.assertIn('placeholder="placeholdertest"', res)
|
||||
|
||||
def test_required_field(self):
|
||||
required_field = render_form_field('subject')
|
||||
self.assertIn('required', required_field)
|
||||
self.assertIn('bootstrap3-req', required_field)
|
||||
not_required_field = render_form_field('message')
|
||||
self.assertNotIn('required', not_required_field)
|
||||
# Required field with required=0
|
||||
form_field = 'form.subject'
|
||||
rendered = render_template(
|
||||
'{% bootstrap_field ' + form_field + ' set_required=0 %}')
|
||||
self.assertNotIn('required', rendered)
|
||||
# Required settings in field
|
||||
form_field = 'form.subject'
|
||||
rendered = render_template(
|
||||
'{% bootstrap_field ' +
|
||||
form_field +
|
||||
' required_css_class="test-required" %}')
|
||||
self.assertIn('test-required', rendered)
|
||||
|
||||
def test_empty_permitted(self):
|
||||
form = TestForm()
|
||||
res = render_form_field('subject', form=form)
|
||||
self.assertIn('required', res)
|
||||
form.empty_permitted = True
|
||||
res = render_form_field('subject', form=form)
|
||||
self.assertNotIn('required', res)
|
||||
|
||||
def test_input_group(self):
|
||||
res = render_template(
|
||||
'{% bootstrap_field form.subject addon_before="$" ' +
|
||||
'addon_after=".00" %}'
|
||||
)
|
||||
self.assertIn('class="input-group"', res)
|
||||
self.assertIn('class="input-group-addon">$', res)
|
||||
self.assertIn('class="input-group-addon">.00', res)
|
||||
|
||||
def test_size(self):
|
||||
def _test_size(param, klass):
|
||||
res = render_template(
|
||||
'{% bootstrap_field form.subject size="' + param + '" %}')
|
||||
self.assertIn(klass, res)
|
||||
|
||||
def _test_size_medium(param):
|
||||
res = render_template(
|
||||
'{% bootstrap_field form.subject size="' + param + '" %}')
|
||||
self.assertNotIn('input-lg', res)
|
||||
self.assertNotIn('input-sm', res)
|
||||
self.assertNotIn('input-md', res)
|
||||
_test_size('sm', 'input-sm')
|
||||
_test_size('small', 'input-sm')
|
||||
_test_size('lg', 'input-lg')
|
||||
_test_size('large', 'input-lg')
|
||||
_test_size_medium('md')
|
||||
_test_size_medium('medium')
|
||||
_test_size_medium('')
|
||||
|
||||
|
||||
class ComponentsTest(TestCase):
|
||||
def test_icon(self):
|
||||
res = render_template('{% bootstrap_icon "star" %}')
|
||||
self.assertEqual(
|
||||
res.strip(), '<span class="glyphicon glyphicon-star"></span>')
|
||||
res = render_template(
|
||||
'{% bootstrap_icon "star" title="alpha centauri" %}')
|
||||
self.assertEqual(
|
||||
res.strip(),
|
||||
'<span class="glyphicon glyphicon-star" ' +
|
||||
'title="alpha centauri"></span>')
|
||||
|
||||
def test_alert(self):
|
||||
res = render_template(
|
||||
'{% bootstrap_alert "content" alert_type="danger" %}')
|
||||
self.assertEqual(
|
||||
res.strip(),
|
||||
'<div class="alert alert-danger alert-dismissable">' +
|
||||
'<button type="button" class="close" data-dismiss="alert" ' +
|
||||
'aria-hidden="true">' +
|
||||
'×</button>content</div>'
|
||||
)
|
||||
|
||||
|
||||
class MessagesTest(TestCase):
|
||||
def test_messages(self):
|
||||
class FakeMessage(object):
|
||||
"""
|
||||
Follows the `django.contrib.messages.storage.base.Message` API.
|
||||
"""
|
||||
|
||||
def __init__(self, message, tags):
|
||||
self.tags = tags
|
||||
self.message = message
|
||||
|
||||
def __str__(self):
|
||||
return self.message
|
||||
|
||||
pattern = re.compile(r'\s+')
|
||||
messages = [FakeMessage("hello", "warning")]
|
||||
res = render_template(
|
||||
'{% bootstrap_messages messages %}', messages=messages)
|
||||
expected = """
|
||||
<div class="alert alert-warning alert-dismissable">
|
||||
<button type="button" class="close" data-dismiss="alert"
|
||||
aria-hidden="true">×</button>
|
||||
hello
|
||||
</div>
|
||||
"""
|
||||
self.assertEqual(
|
||||
re.sub(pattern, '', res),
|
||||
re.sub(pattern, '', expected)
|
||||
)
|
||||
|
||||
messages = [FakeMessage("hello", "error")]
|
||||
res = render_template(
|
||||
'{% bootstrap_messages messages %}', messages=messages)
|
||||
expected = """
|
||||
<div class="alert alert-danger alert-dismissable">
|
||||
<button type="button" class="close" data-dismiss="alert"
|
||||
aria-hidden="true">×</button>
|
||||
hello
|
||||
</div>
|
||||
"""
|
||||
self.assertEqual(
|
||||
re.sub(pattern, '', res),
|
||||
re.sub(pattern, '', expected)
|
||||
)
|
||||
|
||||
messages = [FakeMessage("hello", None)]
|
||||
res = render_template(
|
||||
'{% bootstrap_messages messages %}', messages=messages)
|
||||
expected = """
|
||||
<div class="alert alert-dismissable">
|
||||
<button type="button" class="close" data-dismiss="alert"
|
||||
aria-hidden="true">×</button>
|
||||
hello
|
||||
</div>
|
||||
"""
|
||||
|
||||
self.assertEqual(
|
||||
re.sub(pattern, '', res),
|
||||
re.sub(pattern, '', expected)
|
||||
)
|
||||
|
||||
|
||||
class TextTest(TestCase):
|
||||
def test_add_css_class(self):
|
||||
css_classes = "one two"
|
||||
css_class = "three four"
|
||||
classes = add_css_class(css_classes, css_class)
|
||||
self.assertEqual(classes, "one two three four")
|
||||
|
||||
classes = add_css_class(css_classes, css_class, prepend=True)
|
||||
self.assertEqual(classes, "three four one two")
|
||||
|
||||
|
||||
class HtmlTest(TestCase):
|
||||
def test_text_value(self):
|
||||
self.assertEqual(text_value(''), "")
|
||||
self.assertEqual(text_value(' '), " ")
|
||||
self.assertEqual(text_value(None), "")
|
||||
self.assertEqual(text_value(1), "1")
|
||||
|
||||
def test_text_concat(self):
|
||||
self.assertEqual(text_concat(1, 2), "12")
|
||||
self.assertEqual(text_concat(1, 2, separator='='), "1=2")
|
||||
self.assertEqual(text_concat(None, 2, separator='='), "2")
|
||||
|
||||
|
||||
class ButtonTest(TestCase):
|
||||
def test_button(self):
|
||||
res = render_template(
|
||||
"{% bootstrap_button 'button' size='lg' %}")
|
||||
self.assertEqual(
|
||||
res.strip(), '<button class="btn btn-lg">button</button>')
|
||||
res = render_template(
|
||||
"{% bootstrap_button 'button' size='lg' href='#' %}")
|
||||
self.assertIn(
|
||||
res.strip(),
|
||||
'<a class="btn btn-lg" href="#">button</a><a href="#" ' +
|
||||
'class="btn btn-lg">button</a>')
|
26
bootstrap3/text.py
Normal file
26
bootstrap3/text.py
Normal file
|
@ -0,0 +1,26 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
||||
try:
|
||||
from django.utils.encoding import force_text
|
||||
except ImportError:
|
||||
from django.utils.encoding import force_unicode as force_text
|
||||
|
||||
|
||||
def text_value(value):
|
||||
"""
|
||||
Force a value to text, render None as an empty string
|
||||
"""
|
||||
if value is None:
|
||||
return ''
|
||||
return force_text(value)
|
||||
|
||||
|
||||
def text_concat(*args, **kwargs):
|
||||
"""
|
||||
Concatenate several values as a text string with an optional separator
|
||||
"""
|
||||
separator = text_value(kwargs.get('separator', ''))
|
||||
values = filter(None, [text_value(v) for v in args])
|
||||
return separator.join(values)
|
65
bootstrap3/utils.py
Normal file
65
bootstrap3/utils.py
Normal file
|
@ -0,0 +1,65 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.forms.widgets import flatatt
|
||||
|
||||
from .text import text_value
|
||||
|
||||
|
||||
# Handle HTML and CSS manipulation
|
||||
|
||||
|
||||
def split_css_classes(css_classes):
|
||||
"""
|
||||
Turn string into a list of CSS classes
|
||||
"""
|
||||
classes_list = text_value(css_classes).split(' ')
|
||||
return [c for c in classes_list if c]
|
||||
|
||||
|
||||
def add_css_class(css_classes, css_class, prepend=False):
|
||||
"""
|
||||
Add a CSS class to a string of CSS classes
|
||||
"""
|
||||
classes_list = split_css_classes(css_classes)
|
||||
classes_to_add = [c for c in split_css_classes(css_class)
|
||||
if c not in classes_list]
|
||||
if prepend:
|
||||
classes_list = classes_to_add + classes_list
|
||||
else:
|
||||
classes_list += classes_to_add
|
||||
return ' '.join(classes_list)
|
||||
|
||||
|
||||
def remove_css_class(css_classes, css_class):
|
||||
"""
|
||||
Remove a CSS class from a string of CSS classes
|
||||
"""
|
||||
remove = set(split_css_classes(css_class))
|
||||
classes_list = [c for c in split_css_classes(css_classes)
|
||||
if c not in remove]
|
||||
return ' '.join(classes_list)
|
||||
|
||||
|
||||
def render_link_tag(url, rel='stylesheet', media='all'):
|
||||
"""
|
||||
Build a link tag
|
||||
"""
|
||||
return render_tag(
|
||||
'link',
|
||||
attrs={'href': url, 'rel': rel, 'media': media},
|
||||
close=False)
|
||||
|
||||
|
||||
def render_tag(tag, attrs=None, content=None, close=True):
|
||||
"""
|
||||
Render a HTML tag
|
||||
"""
|
||||
builder = '<{tag}{attrs}>{content}'
|
||||
if content or close:
|
||||
builder += '</{tag}>'
|
||||
return builder.format(
|
||||
tag=tag,
|
||||
attrs=flatatt(attrs) if attrs else '',
|
||||
content=text_value(content),
|
||||
)
|
|
@ -1,5 +1,6 @@
|
|||
from django import template
|
||||
from django.template.loader import render_to_string
|
||||
from django.conf import settings
|
||||
|
||||
from ietf.community.models import CommunityList
|
||||
from ietf.group.models import Role
|
||||
|
@ -7,47 +8,24 @@ from ietf.group.models import Role
|
|||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
class CommunityListNode(template.Node):
|
||||
|
||||
def __init__(self, user, var_name):
|
||||
self.user = user
|
||||
self.var_name = var_name
|
||||
|
||||
def render(self, context):
|
||||
user = self.user.resolve(context)
|
||||
if not (user and hasattr(user, "is_authenticated") and user.is_authenticated() ):
|
||||
return ''
|
||||
lists = {'personal': CommunityList.objects.get_or_create(user=user)[0]}
|
||||
try:
|
||||
person = user.person
|
||||
groups = []
|
||||
managed_areas = [i.group for i in Role.objects.filter(name__slug='ad', email__in=person.email_set.all())]
|
||||
for area in managed_areas:
|
||||
groups.append(CommunityList.objects.get_or_create(group=area)[0])
|
||||
managed_wg = [i.group for i in Role.objects.filter(name__slug='chair', group__type__slug='wg', email__in=person.email_set.all())]
|
||||
for wg in managed_wg:
|
||||
groups.append(CommunityList.objects.get_or_create(group=wg)[0])
|
||||
lists['group'] = groups
|
||||
except:
|
||||
pass
|
||||
context.update({self.var_name: lists})
|
||||
@register.assignment_tag
|
||||
def get_user_managed_lists(user):
|
||||
if not (user and hasattr(user, "is_authenticated") and user.is_authenticated()):
|
||||
return ''
|
||||
|
||||
|
||||
@register.tag
|
||||
def get_user_managed_lists(parser, token):
|
||||
firstbits = token.contents.split(None, 2)
|
||||
if len(firstbits) != 3:
|
||||
raise template.TemplateSyntaxError("'get_user_managed_lists' tag takes three arguments")
|
||||
user = parser.compile_filter(firstbits[1])
|
||||
lastbits_reversed = firstbits[2][::-1].split(None, 2)
|
||||
if lastbits_reversed[1][::-1] != 'as':
|
||||
raise template.TemplateSyntaxError("next-to-last argument to 'get_user_managed_lists' tag must"
|
||||
" be 'as'")
|
||||
var_name = lastbits_reversed[0][::-1]
|
||||
return CommunityListNode(user, var_name)
|
||||
|
||||
lists = {'personal': CommunityList.objects.get_or_create(user=user)[0]}
|
||||
try:
|
||||
person = user.person
|
||||
groups = []
|
||||
managed_areas = [i.group for i in Role.objects.filter(name__slug='ad', email__in=person.email_set.all())]
|
||||
for area in managed_areas:
|
||||
groups.append(CommunityList.objects.get_or_create(group=area)[0])
|
||||
managed_wg = [i.group for i in Role.objects.filter(name__slug='chair', group__type__slug='wg', email__in=person.email_set.all())]
|
||||
for wg in managed_wg:
|
||||
groups.append(CommunityList.objects.get_or_create(group=wg)[0])
|
||||
lists['group'] = groups
|
||||
except:
|
||||
pass
|
||||
return lists
|
||||
|
||||
@register.inclusion_tag('community/display_field.html', takes_context=False)
|
||||
def show_field(field, doc):
|
||||
|
@ -56,25 +34,11 @@ def show_field(field, doc):
|
|||
}
|
||||
|
||||
|
||||
class CommunityListViewNode(template.Node):
|
||||
|
||||
def __init__(self, clist):
|
||||
self.clist = clist
|
||||
|
||||
def render(self, context):
|
||||
clist = self.clist.resolve(context)
|
||||
if not clist.cached:
|
||||
@register.simple_tag
|
||||
def get_clist_view(clist):
|
||||
if settings.DEBUG or not clist.cached:
|
||||
clist.cached = render_to_string('community/raw_view.html',
|
||||
{'cl': clist,
|
||||
'dc': clist.get_display_config()})
|
||||
clist.save()
|
||||
return clist.cached
|
||||
|
||||
|
||||
@register.tag
|
||||
def get_clist_view(parser, token):
|
||||
firstbits = token.contents.split(None, 1)
|
||||
if len(firstbits) != 2:
|
||||
raise template.TemplateSyntaxError("'get_clist_view' tag takes one argument")
|
||||
clist = parser.compile_filter(firstbits[1])
|
||||
return CommunityListViewNode(clist)
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
{% extends "base.html" %}
|
||||
{% extends "ietf.html" %}
|
||||
|
||||
{% load bootstrap3 %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Template: {{ template }}</h1>
|
||||
|
@ -12,15 +14,15 @@
|
|||
<dt>Template type</dt>
|
||||
<dd>{{ template.type.name }}
|
||||
{% if template.type.slug == "rst" %}
|
||||
<p>This template uses the syntax of reStructuredText. Get a quick reference at <a href="http://docutils.sourceforge.net/docs/user/rst/quickref.html">http://docutils.sourceforge.net/docs/user/rst/quickref.html</a>.</p>
|
||||
<p>You can do variable interpolation with $varialbe if the template allows any variable.</p>
|
||||
<p class="help-block">This template uses the syntax of reStructuredText. Get a quick reference at <a href="http://docutils.sourceforge.net/docs/user/rst/quickref.html">http://docutils.sourceforge.net/docs/user/rst/quickref.html</a>.</p>
|
||||
<p class="help-block">You can do variable interpolation with $varialbe if the template allows any variable.</p>
|
||||
{% endif %}
|
||||
{% if template.type.slug == "django" %}
|
||||
<p>This template uses the syntax of the default django template framework. Get more info at <a href="https://docs.djangoproject.com/en/dev/topics/templates/">https://docs.djangoproject.com/en/dev/topics/templates/</a>.</p>
|
||||
<p>You can do variable interpolation with the current django markup {{variable}} if the template allows any variable.</p>
|
||||
<p class="help-block">This template uses the syntax of the default django template framework. Get more info at <a href="https://docs.djangoproject.com/en/dev/topics/templates/">https://docs.djangoproject.com/en/dev/topics/templates/</a>.</p>
|
||||
<p class="help-block">You can do variable interpolation with the current django markup {{variable}} if the template allows any variable.</p>
|
||||
{% endif %}
|
||||
{% if template.type.slug == "plain" %}
|
||||
<p>This template uses plain text, so no markup is used. You can do variable interpolation with $variable if the template allows any variable.</p>
|
||||
<p class="help-block">This template uses plain text, so no markup is used. You can do variable interpolation with $variable if the template allows any variable.</p>
|
||||
{% endif %}
|
||||
</dd>
|
||||
{% if template.variables %}
|
||||
|
@ -30,8 +32,13 @@
|
|||
</dl>
|
||||
|
||||
<h2>Edit template content</h2>
|
||||
<form action="" method="post">{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<input type="submit" value="Submit changes" />
|
||||
<form role="form" method="post">
|
||||
{% csrf_token %}
|
||||
|
||||
{% bootstrap_form form %}
|
||||
|
||||
{% buttons %}
|
||||
<button class="btn btn-default" type="submit">Save changes</button>
|
||||
{% endbuttons %}
|
||||
</form>
|
||||
{% endblock content %}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends "base.html" %}
|
||||
{% extends "ietf.html" %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Defined templates for group {{ group }}</h1>
|
||||
|
@ -6,7 +6,7 @@
|
|||
{% if template_list %}
|
||||
<ul>
|
||||
{% for template in template_list %}
|
||||
<li><a href="{% url template_edit group.acronym template.id %}">{{ template }}</a></li>
|
||||
<li><a href="{% url "template_edit" group.acronym template.id %}">{{ template }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
from django.http import HttpResponseForbidden, HttpResponseRedirect
|
||||
from django.shortcuts import get_object_or_404, render_to_response
|
||||
from django.template import RequestContext
|
||||
from django.shortcuts import get_object_or_404, render
|
||||
|
||||
from ietf.dbtemplate.models import DBTemplate
|
||||
from ietf.dbtemplate.forms import DBTemplateForm
|
||||
|
@ -15,10 +14,10 @@ def template_list(request, acronym):
|
|||
return HttpResponseForbidden("You are not authorized to access this view")
|
||||
|
||||
template_list = DBTemplate.objects.filter(group=group)
|
||||
return render_to_response('dbtemplate/template_list.html',
|
||||
return render(request, 'dbtemplate/template_list.html',
|
||||
{'template_list': template_list,
|
||||
'group': group,
|
||||
}, RequestContext(request))
|
||||
})
|
||||
|
||||
|
||||
def template_edit(request, acronym, template_id, base_template='dbtemplate/template_edit.html', formclass=DBTemplateForm, extra_context=None):
|
||||
|
@ -43,4 +42,4 @@ def template_edit(request, acronym, template_id, base_template='dbtemplate/templ
|
|||
'form': form,
|
||||
}
|
||||
context.update(extra_context)
|
||||
return render_to_response(base_template, context, RequestContext(request))
|
||||
return render(request, base_template, context)
|
||||
|
|
113
ietf/doc/fields.py
Normal file
113
ietf/doc/fields.py
Normal file
|
@ -0,0 +1,113 @@
|
|||
import json
|
||||
|
||||
from django.utils.html import escape
|
||||
from django import forms
|
||||
from django.core.urlresolvers import reverse as urlreverse
|
||||
|
||||
from ietf.doc.models import Document, DocAlias
|
||||
from ietf.doc.utils import uppercase_std_abbreviated_name
|
||||
|
||||
def select2_id_doc_name_json(objs):
|
||||
return json.dumps([{ "id": o.pk, "text": escape(uppercase_std_abbreviated_name(o.name)) } for o in objs])
|
||||
|
||||
# FIXME: select2 version 4 uses a standard select for the AJAX case -
|
||||
# switching to that would allow us to derive from the standard
|
||||
# multi-select machinery in Django instead of the manual CharField
|
||||
# stuff below
|
||||
|
||||
class SearchableDocumentsField(forms.CharField):
|
||||
"""Server-based multi-select field for choosing documents using
|
||||
select2.js.
|
||||
|
||||
The field uses a comma-separated list of primary keys in a
|
||||
CharField element as its API with some extra attributes used by
|
||||
the Javascript part."""
|
||||
|
||||
def __init__(self,
|
||||
max_entries=None, # max number of selected objs
|
||||
model=Document,
|
||||
hint_text="Type in name to search for document",
|
||||
doc_type="draft",
|
||||
*args, **kwargs):
|
||||
kwargs["max_length"] = 10000
|
||||
self.max_entries = max_entries
|
||||
self.doc_type = doc_type
|
||||
self.model = model
|
||||
|
||||
super(SearchableDocumentsField, self).__init__(*args, **kwargs)
|
||||
|
||||
self.widget.attrs["class"] = "select2-field form-control"
|
||||
self.widget.attrs["data-placeholder"] = hint_text
|
||||
if self.max_entries != None:
|
||||
self.widget.attrs["data-max-entries"] = self.max_entries
|
||||
|
||||
def parse_select2_value(self, value):
|
||||
return [x.strip() for x in value.split(",") if x.strip()]
|
||||
|
||||
def prepare_value(self, value):
|
||||
if not value:
|
||||
value = ""
|
||||
if isinstance(value, (int, long)):
|
||||
value = str(value)
|
||||
if isinstance(value, basestring):
|
||||
pks = self.parse_select2_value(value)
|
||||
value = self.model.objects.filter(pk__in=pks)
|
||||
filter_args = {}
|
||||
if self.model == DocAlias:
|
||||
filter_args["document__type"] = self.doc_type
|
||||
else:
|
||||
filter_args["type"] = self.doc_type
|
||||
value = value.filter(**filter_args)
|
||||
if isinstance(value, self.model):
|
||||
value = [value]
|
||||
|
||||
self.widget.attrs["data-pre"] = select2_id_doc_name_json(value)
|
||||
|
||||
# doing this in the constructor is difficult because the URL
|
||||
# patterns may not have been fully constructed there yet
|
||||
self.widget.attrs["data-ajax-url"] = urlreverse("ajax_select2_search_docs", kwargs={
|
||||
"doc_type": self.doc_type,
|
||||
"model_name": self.model.__name__.lower()
|
||||
})
|
||||
|
||||
return u",".join(unicode(o.pk) for o in value)
|
||||
|
||||
def clean(self, value):
|
||||
value = super(SearchableDocumentsField, self).clean(value)
|
||||
pks = self.parse_select2_value(value)
|
||||
|
||||
objs = self.model.objects.filter(pk__in=pks)
|
||||
|
||||
found_pks = [str(o.pk) for o in objs]
|
||||
failed_pks = [x for x in pks if x not in found_pks]
|
||||
if failed_pks:
|
||||
raise forms.ValidationError(u"Could not recognize the following documents: {pks}. You can only input documents already registered in the Datatracker.".format(pks=", ".join(failed_pks)))
|
||||
|
||||
if self.max_entries != None and len(objs) > self.max_entries:
|
||||
raise forms.ValidationError(u"You can select at most %s entries only." % self.max_entries)
|
||||
|
||||
return objs
|
||||
|
||||
class SearchableDocumentField(SearchableDocumentsField):
|
||||
"""Specialized to only return one Document."""
|
||||
def __init__(self, model=Document, *args, **kwargs):
|
||||
kwargs["max_entries"] = 1
|
||||
super(SearchableDocumentField, self).__init__(model=model, *args, **kwargs)
|
||||
|
||||
def clean(self, value):
|
||||
return super(SearchableDocumentField, self).clean(value).first()
|
||||
|
||||
class SearchableDocAliasesField(SearchableDocumentsField):
|
||||
def __init__(self, model=DocAlias, *args, **kwargs):
|
||||
super(SearchableDocAliasesField, self).__init__(model=model, *args, **kwargs)
|
||||
|
||||
class SearchableDocAliasField(SearchableDocumentsField):
|
||||
"""Specialized to only return one DocAlias."""
|
||||
def __init__(self, model=DocAlias, *args, **kwargs):
|
||||
kwargs["max_entries"] = 1
|
||||
super(SearchableDocAliasField, self).__init__(model=model, *args, **kwargs)
|
||||
|
||||
def clean(self, value):
|
||||
return super(SearchableDocAliasField, self).clean(value).first()
|
||||
|
||||
|
|
@ -34,7 +34,7 @@ class AdForm(forms.Form):
|
|||
self.fields['ad'].choices = list(choices) + [("", "-------"), (ad_pk, Person.objects.get(pk=ad_pk).plain_name())]
|
||||
|
||||
class NotifyForm(forms.Form):
|
||||
notify = forms.CharField(max_length=255, help_text="List of email addresses to receive state notifications, separated by comma", label="Notification list", required=False)
|
||||
notify = forms.CharField(max_length=255, help_text="List of email addresses to receive state notifications, separated by comma.", label="Notification list", required=False)
|
||||
|
||||
def clean_notify(self):
|
||||
addrspecs = [x.strip() for x in self.cleaned_data["notify"].split(',')]
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
from south.v2 import DataMigration
|
||||
|
||||
class Migration(DataMigration):
|
||||
|
|
|
@ -87,35 +87,32 @@ def ballot_icon(context, doc):
|
|||
positions = list(doc.active_ballot().active_ad_positions().items())
|
||||
positions.sort(key=sort_key)
|
||||
|
||||
edit_position_url = ""
|
||||
if has_role(user, "Area Director"):
|
||||
edit_position_url = urlreverse('ietf.doc.views_ballot.edit_position', kwargs=dict(name=doc.name, ballot_id=ballot.pk))
|
||||
|
||||
title = "IESG positions (click to show more%s)" % (", right-click to edit position" if edit_position_url else "")
|
||||
|
||||
res = ['<a href="%s" data-popup="%s" data-edit="%s" title="%s" class="ballot-icon"><table>' % (
|
||||
urlreverse("doc_ballot", kwargs=dict(name=doc.name, ballot_id=ballot.pk)),
|
||||
res = ['<a href="%s" data-toggle="modal" data-target="#modal-%d" title="IESG positions (click to show more)" class="ballot-icon"><table>' % (
|
||||
urlreverse("ietf.doc.views_doc.ballot_popup", kwargs=dict(name=doc.name, ballot_id=ballot.pk)),
|
||||
edit_position_url,
|
||||
title
|
||||
)]
|
||||
ballot.pk)]
|
||||
|
||||
res.append("<tr>")
|
||||
|
||||
for i, (ad, pos) in enumerate(positions):
|
||||
if i > 0 and i % 5 == 0:
|
||||
res.append("</tr>")
|
||||
res.append("<tr>")
|
||||
res.append("</tr><tr>")
|
||||
|
||||
c = "position-%s" % (pos.pos.slug if pos else "norecord")
|
||||
|
||||
if user_is_person(user, ad):
|
||||
c += " my"
|
||||
|
||||
res.append('<td class="%s" />' % c)
|
||||
res.append('<td class="%s"></td>' % c)
|
||||
|
||||
res.append("</tr>")
|
||||
res.append("</table></a>")
|
||||
# add sufficient table calls to last row to avoid HTML validation warning
|
||||
while (i + 1) % 5 != 0:
|
||||
res.append('<td class="empty"></td>')
|
||||
i = i + 1
|
||||
|
||||
res.append("</tr></table></a>")
|
||||
# XXX FACELIFT: Loading via href will go away in bootstrap 4.
|
||||
# See http://getbootstrap.com/javascript/#modals-usage
|
||||
res.append('<div id="modal-%d" class="modal fade" tabindex="-1" role="dialog" aria-hidden="true"><div class="modal-dialog modal-lg"><div class="modal-content"></div></div></div>' % ballot.pk)
|
||||
|
||||
return "".join(res)
|
||||
|
||||
|
@ -156,7 +153,7 @@ def state_age_colored(doc):
|
|||
except IndexError:
|
||||
state_date = datetime.date(1990,1,1)
|
||||
days = (datetime.date.today() - state_date).days
|
||||
# loosely based on
|
||||
# loosely based on
|
||||
# http://trac.tools.ietf.org/group/iesg/trac/wiki/PublishPath
|
||||
if iesg_state == "lc":
|
||||
goal1 = 30
|
||||
|
@ -180,16 +177,17 @@ def state_age_colored(doc):
|
|||
goal1 = 14
|
||||
goal2 = 28
|
||||
if days > goal2:
|
||||
class_name = "ietf-small ietf-highlight-r"
|
||||
class_name = "label label-danger"
|
||||
elif days > goal1:
|
||||
class_name = "ietf-small ietf-highlight-y"
|
||||
class_name = "label label-warning"
|
||||
else:
|
||||
class_name = "ietf-small"
|
||||
if days > goal1:
|
||||
title = ' title="Goal is <%d days"' % (goal1,)
|
||||
else:
|
||||
title = ''
|
||||
return mark_safe('<span class="%s"%s>(for %d day%s)</span>' % (
|
||||
class_name, title, days, 's' if days != 1 else ''))
|
||||
return mark_safe('<span class="%s"%s>for %d day%s</span>' % (
|
||||
class_name, title, days,
|
||||
's' if days != 1 else ''))
|
||||
else:
|
||||
return ""
|
||||
|
|
|
@ -8,9 +8,9 @@ from email.utils import parseaddr
|
|||
|
||||
from ietf.doc.models import ConsensusDocEvent
|
||||
from django import template
|
||||
from django.conf import settings
|
||||
from django.utils.html import escape, fix_ampersands
|
||||
from django.utils.text import wrap
|
||||
from django.template.defaultfilters import truncatewords_html, linebreaksbr, stringfilter, urlize
|
||||
from django.template.defaultfilters import truncatewords_html, linebreaksbr, stringfilter, striptags, urlize
|
||||
from django.template import resolve_variable
|
||||
from django.utils.safestring import mark_safe, SafeData
|
||||
from django.utils.html import strip_tags
|
||||
|
@ -52,12 +52,12 @@ def parse_email_list(value):
|
|||
u'<a href="mailto:joe@example.org">joe@example.org</a>, <a href="mailto:fred@example.com">fred@example.com</a>'
|
||||
|
||||
Parsing a non-string should return the input value, rather than fail:
|
||||
|
||||
|
||||
>>> parse_email_list(['joe@example.org', 'fred@example.com'])
|
||||
['joe@example.org', 'fred@example.com']
|
||||
|
||||
|
||||
Null input values should pass through silently:
|
||||
|
||||
|
||||
>>> parse_email_list('')
|
||||
''
|
||||
|
||||
|
@ -91,7 +91,7 @@ def fix_angle_quotes(value):
|
|||
if "<" in value:
|
||||
value = re.sub("<([\w\-\.]+@[\w\-\.]+)>", "<\1>", value)
|
||||
return value
|
||||
|
||||
|
||||
# there's an "ahref -> a href" in GEN_UTIL
|
||||
# but let's wait until we understand what that's for.
|
||||
@register.filter(name='make_one_per_line')
|
||||
|
@ -103,7 +103,7 @@ def make_one_per_line(value):
|
|||
'a\\nb\\nc'
|
||||
|
||||
Pass through non-strings:
|
||||
|
||||
|
||||
>>> make_one_per_line([1, 2])
|
||||
[1, 2]
|
||||
|
||||
|
@ -114,7 +114,7 @@ def make_one_per_line(value):
|
|||
return re.sub(", ?", "\n", value)
|
||||
else:
|
||||
return value
|
||||
|
||||
|
||||
@register.filter(name='timesum')
|
||||
def timesum(value):
|
||||
"""
|
||||
|
@ -203,7 +203,7 @@ def rfcspace(string):
|
|||
"""
|
||||
string = str(string)
|
||||
if string[:3].lower() == "rfc" and string[3] != " ":
|
||||
return string[:3] + " " + string[3:]
|
||||
return string[:3].upper() + " " + string[3:]
|
||||
else:
|
||||
return string
|
||||
|
||||
|
@ -226,7 +226,7 @@ def rfclink(string):
|
|||
URL for that RFC.
|
||||
"""
|
||||
string = str(string);
|
||||
return "http://tools.ietf.org/html/rfc" + string;
|
||||
return "//tools.ietf.org/html/rfc" + string;
|
||||
|
||||
@register.filter(name='urlize_ietf_docs', is_safe=True, needs_autoescape=True)
|
||||
def urlize_ietf_docs(string, autoescape=None):
|
||||
|
@ -278,7 +278,7 @@ def truncate_ellipsis(text, arg):
|
|||
return escape(text[:num-1])+"…"
|
||||
else:
|
||||
return escape(text)
|
||||
|
||||
|
||||
@register.filter
|
||||
def split(text, splitter=None):
|
||||
return text.split(splitter)
|
||||
|
@ -379,7 +379,7 @@ def linebreaks_lf(text):
|
|||
@register.filter(name='clean_whitespace')
|
||||
def clean_whitespace(text):
|
||||
"""
|
||||
Map all ASCII control characters (0x00-0x1F) to spaces, and
|
||||
Map all ASCII control characters (0x00-0x1F) to spaces, and
|
||||
remove unnecessary spaces.
|
||||
"""
|
||||
text = re.sub("[\000-\040]+", " ", text)
|
||||
|
@ -388,7 +388,7 @@ def clean_whitespace(text):
|
|||
@register.filter(name='unescape')
|
||||
def unescape(text):
|
||||
"""
|
||||
Unescape />/<
|
||||
Unescape />/<
|
||||
"""
|
||||
text = text.replace(">", ">")
|
||||
text = text.replace("<", "<")
|
||||
|
@ -427,7 +427,7 @@ def has_role(user, role_names):
|
|||
@register.filter
|
||||
def stable_dictsort(value, arg):
|
||||
"""
|
||||
Like dictsort, except it's stable (preserves the order of items
|
||||
Like dictsort, except it's stable (preserves the order of items
|
||||
whose sort key is the same). See also bug report
|
||||
http://code.djangoproject.com/ticket/12110
|
||||
"""
|
||||
|
@ -459,21 +459,14 @@ def format_snippet(text, trunc_words=25):
|
|||
full = mark_safe(keep_spacing(collapsebr(linebreaksbr(urlize(sanitize_html(text))))))
|
||||
snippet = truncatewords_html(full, trunc_words)
|
||||
if snippet != full:
|
||||
return mark_safe(u'<div class="snippet">%s<span class="show-all">[show all]</span></div><div style="display:none" class="full">%s</div>' % (snippet, full))
|
||||
return mark_safe(u'<div class="snippet">%s<button class="btn btn-xs btn-default show-all"><span class="fa fa-caret-down"></span></button></div><div class="hidden full">%s</div>' % (snippet, full))
|
||||
return full
|
||||
|
||||
@register.filter
|
||||
def format_editable_snippet(text,link):
|
||||
full = mark_safe(keep_spacing(collapsebr(linebreaksbr(urlize(sanitize_html(text))))))
|
||||
snippet = truncatewords_html(full, 25)
|
||||
if snippet != full:
|
||||
return mark_safe(u'<div class="snippet">%s<span class="show-all">[show all]</span></div><div style="display:none" class="full">%s' % (format_editable(snippet,link),format_editable(full,link)) )
|
||||
else:
|
||||
return format_editable(full,link)
|
||||
|
||||
@register.filter
|
||||
def format_editable(text,link):
|
||||
return mark_safe(u'<a class="editlink" href="%s">%s</a>' % (link,text))
|
||||
@register.simple_tag
|
||||
def doc_edit_button(url_name, *args, **kwargs):
|
||||
"""Given URL name/args/kwargs, looks up the URL just like "url" tag and returns a properly formatted button for the document material tables."""
|
||||
from django.core.urlresolvers import reverse as urlreverse
|
||||
return mark_safe(u'<a class="btn btn-default btn-xs" href="%s">Edit</a>' % (urlreverse(url_name, args=args, kwargs=kwargs)))
|
||||
|
||||
@register.filter
|
||||
def textify(text):
|
||||
|
@ -533,33 +526,60 @@ def consensus(doc):
|
|||
else:
|
||||
return "Unknown"
|
||||
|
||||
# The function and class below provides a tag version of the builtin wordwrap filter
|
||||
# https://djangosnippets.org/snippets/134/
|
||||
@register.tag
|
||||
def wordwrap(parser, token):
|
||||
"""
|
||||
This is a tag version of the Django builtin 'wordwrap' filter. This is useful
|
||||
if you need to wrap a combination of fixed template text and template variables.
|
||||
@register.filter
|
||||
def pos_to_label(text):
|
||||
"""Return a valid Bootstrap3 label type for a ballot position."""
|
||||
return {
|
||||
'Yes': 'success',
|
||||
'No Objection': 'info',
|
||||
'Abstain': 'warning',
|
||||
'Discuss': 'danger',
|
||||
'Block': 'danger',
|
||||
'Recuse': 'default',
|
||||
}.get(str(text), 'blank')
|
||||
|
||||
Usage:
|
||||
|
||||
{% wordwrap 80 %}
|
||||
some really long text here, including template variable expansion, etc.
|
||||
{% endwordwrap %}
|
||||
@register.filter
|
||||
def capfirst_allcaps(text):
|
||||
"""Like capfirst, except it doesn't lowercase words in ALL CAPS."""
|
||||
result = text
|
||||
i = False
|
||||
for token in re.split("(\W+)", striptags(text)):
|
||||
if not re.match("^[A-Z]+$", token):
|
||||
if not i:
|
||||
result = result.replace(token, token.capitalize())
|
||||
i = True
|
||||
else:
|
||||
result = result.replace(token, token.lower())
|
||||
return result
|
||||
|
||||
@register.filter
|
||||
def lower_allcaps(text):
|
||||
"""Like lower, except it doesn't lowercase words in ALL CAPS."""
|
||||
result = text
|
||||
for token in re.split("(\W+)", striptags(text)):
|
||||
if not re.match("^[A-Z]+$", token):
|
||||
result = result.replace(token, token.lower())
|
||||
return result
|
||||
|
||||
# See https://djangosnippets.org/snippets/2072/ and
|
||||
# https://stackoverflow.com/questions/9939248/how-to-prevent-django-basic-inlines-from-autoescaping
|
||||
@register.filter
|
||||
def urlize_html(html, autoescape=False):
|
||||
"""
|
||||
Returns urls found in an (X)HTML text node element as urls via Django urlize filter.
|
||||
"""
|
||||
try:
|
||||
tag_name, len = token.split_contents()
|
||||
except ValueError:
|
||||
raise template.TemplateSyntaxError, "The wordwrap tag requires exactly one argument: width."
|
||||
nodelist = parser.parse(('endwordwrap',))
|
||||
parser.delete_first_token()
|
||||
return WordWrapNode(nodelist, len)
|
||||
from BeautifulSoup import BeautifulSoup
|
||||
except ImportError:
|
||||
if settings.DEBUG:
|
||||
raise template.TemplateSyntaxError, "Error in urlize_html The Python BeautifulSoup libraries aren't installed."
|
||||
return html
|
||||
else:
|
||||
soup = BeautifulSoup(html)
|
||||
|
||||
class WordWrapNode(template.Node):
|
||||
def __init__(self, nodelist, len):
|
||||
self.nodelist = nodelist
|
||||
self.len = len
|
||||
|
||||
def render(self, context):
|
||||
return wrap(str(self.nodelist.render(context)), int(self.len))
|
||||
textNodes = soup.findAll(text=True)
|
||||
for textNode in textNodes:
|
||||
urlizedText = urlize(textNode, autoescape=autoescape)
|
||||
textNode.replaceWith(BeautifulSoup(urlizedText))
|
||||
|
||||
return str(soup)
|
||||
|
|
|
@ -44,23 +44,27 @@ area_short_names = {
|
|||
}
|
||||
|
||||
@register.simple_tag
|
||||
def wg_menu():
|
||||
res = cache.get('base_left_wgmenu')
|
||||
def wg_menu(flavor=""):
|
||||
res = cache.get('wgmenu' + flavor)
|
||||
if res:
|
||||
return res
|
||||
|
||||
areas = Group.objects.filter(type="area", state="active").order_by('acronym')
|
||||
groups = Group.objects.filter(type="wg", state="active", parent__in=areas).order_by("acronym")
|
||||
wgs = Group.objects.filter(type="wg", state="active", parent__in=areas).order_by("acronym")
|
||||
rgs = Group.objects.filter(type="rg", state="active").order_by("acronym")
|
||||
|
||||
for a in areas:
|
||||
a.short_area_name = area_short_names.get(a.acronym) or a.name
|
||||
if a.short_area_name.endswith(" Area"):
|
||||
a.short_area_name = a.short_area_name[:-len(" Area")]
|
||||
|
||||
a.active_groups = [g for g in groups if g.parent_id == a.id]
|
||||
a.active_groups = [g for g in wgs if g.parent_id == a.id]
|
||||
|
||||
areas = [a for a in areas if a.active_groups]
|
||||
|
||||
res = render_to_string('base/wg_menu.html', {'areas':areas})
|
||||
cache.set('base_left_wgmenu', res, 30*60)
|
||||
if flavor == "modal":
|
||||
res = render_to_string('base/menu_wg_modal.html', {'areas':areas, 'rgs':rgs})
|
||||
else:
|
||||
res = render_to_string('base/menu_wg.html', {'areas':areas, 'rgs':rgs})
|
||||
cache.set('wgmenu' + flavor, res, 30*60)
|
||||
return res
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import datetime
|
||||
import json
|
||||
import sys
|
||||
if sys.version_info[0] == 2 and sys.version_info[1] < 7:
|
||||
import unittest2 as unittest
|
||||
|
@ -97,7 +98,7 @@ class SearchTestCase(TestCase):
|
|||
make_test_data()
|
||||
r = self.client.get("/")
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertTrue("Search Internet-Drafts" in r.content)
|
||||
self.assertTrue("Search Documents" in r.content)
|
||||
|
||||
def test_drafts_pages(self):
|
||||
draft = make_test_data()
|
||||
|
@ -121,6 +122,32 @@ class SearchTestCase(TestCase):
|
|||
r = self.client.get(urlreverse("index_active_drafts"))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertTrue(draft.title in r.content)
|
||||
|
||||
def test_ajax_search_docs(self):
|
||||
draft = make_test_data()
|
||||
|
||||
# Document
|
||||
url = urlreverse("ajax_select2_search_docs", kwargs={
|
||||
"model_name": "document",
|
||||
"doc_type": "draft",
|
||||
})
|
||||
r = self.client.get(url, dict(q=draft.name))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
data = json.loads(r.content)
|
||||
self.assertEqual(data[0]["id"], draft.pk)
|
||||
|
||||
# DocAlias
|
||||
doc_alias = draft.docalias_set.get()
|
||||
|
||||
url = urlreverse("ajax_select2_search_docs", kwargs={
|
||||
"model_name": "docalias",
|
||||
"doc_type": "draft",
|
||||
})
|
||||
|
||||
r = self.client.get(url, dict(q=doc_alias.name))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
data = json.loads(r.content)
|
||||
self.assertEqual(data[0]["id"], doc_alias.pk)
|
||||
|
||||
|
||||
class DocTestCase(TestCase):
|
||||
|
@ -402,14 +429,12 @@ class DocTestCase(TestCase):
|
|||
self.client.login(username='iab-chair', password='iab-chair+password')
|
||||
r = self.client.get(urlreverse("doc_view", kwargs=dict(name=doc.name)))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertFalse(q('.actions'))
|
||||
self.assertTrue("Request publication" not in r.content)
|
||||
|
||||
Document.objects.filter(pk=doc.pk).update(stream='iab')
|
||||
r = self.client.get(urlreverse("doc_view", kwargs=dict(name=doc.name)))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertTrue('IESG state' in q('.actions').html())
|
||||
self.assertTrue("Request publication" in r.content)
|
||||
|
||||
|
||||
class AddCommentTestCase(TestCase):
|
||||
|
|
|
@ -174,9 +174,9 @@ class BallotWriteupsTests(TestCase):
|
|||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertEqual(len(q('textarea[name=last_call_text]')), 1)
|
||||
self.assertEqual(len(q('input[type=submit][value*="Save Last Call"]')), 1)
|
||||
# we're secretariat, so we got The Link
|
||||
self.assertEqual(len(q('a:contains("Make Last Call")')), 1)
|
||||
self.assertTrue(q('[type=submit]:contains("Save")'))
|
||||
# we're Secretariat, so we got The Link
|
||||
self.assertEqual(len(q('a:contains("Issue last call")')), 1)
|
||||
|
||||
# subject error
|
||||
r = self.client.post(url, dict(
|
||||
|
@ -184,7 +184,7 @@ class BallotWriteupsTests(TestCase):
|
|||
save_last_call_text="1"))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertTrue(len(q('ul.errorlist')) > 0)
|
||||
self.assertTrue(len(q('form .has-error')) > 0)
|
||||
|
||||
# save
|
||||
r = self.client.post(url, dict(
|
||||
|
@ -243,7 +243,7 @@ class BallotWriteupsTests(TestCase):
|
|||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertEqual(len(q('textarea[name=ballot_writeup]')), 1)
|
||||
self.assertEqual(len(q('input[type=submit][value*="Save Ballot Writeup"]')), 1)
|
||||
self.assertTrue(q('[type=submit]:contains("Save")'))
|
||||
self.assertTrue("IANA does not" in r.content)
|
||||
|
||||
# save
|
||||
|
@ -317,7 +317,7 @@ class BallotWriteupsTests(TestCase):
|
|||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertEqual(len(q('textarea[name=approval_text]')), 1)
|
||||
self.assertEqual(len(q('input[type=submit][value*="Save Approval"]')), 1)
|
||||
self.assertTrue(q('[type=submit]:contains("Save")'))
|
||||
|
||||
# save
|
||||
r = self.client.post(url, dict(
|
||||
|
@ -365,8 +365,8 @@ class ApproveBallotTests(TestCase):
|
|||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertTrue("send out the announcement" in q('.actions input[type=submit]')[0].get('value').lower())
|
||||
self.assertEqual(len(q('.announcement pre:contains("Subject: Protocol Action")')), 1)
|
||||
self.assertTrue(q('[type=submit]:contains("send announcement")'))
|
||||
self.assertEqual(len(q('form pre:contains("Subject: Protocol Action")')), 1)
|
||||
|
||||
# approve
|
||||
mailbox_before = len(outbox)
|
||||
|
@ -466,7 +466,7 @@ class DeferUndeferTestCase(TestCase):
|
|||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertEqual(len(q('form.defer')),1)
|
||||
self.assertEqual(len(q('[type=submit]:contains("Defer ballot")')),1)
|
||||
|
||||
# defer
|
||||
mailbox_before = len(outbox)
|
||||
|
@ -521,7 +521,7 @@ class DeferUndeferTestCase(TestCase):
|
|||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertEqual(len(q('form.undefer')),1)
|
||||
self.assertEqual(len(q('[type=submit]:contains("Undefer ballot")')),1)
|
||||
|
||||
# undefer
|
||||
mailbox_before = len(outbox)
|
||||
|
|
|
@ -71,7 +71,7 @@ class EditCharterTests(TestCase):
|
|||
r = self.client.post(url, dict(charter_state="-12345"))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertTrue(len(q('form ul.errorlist')) > 0)
|
||||
self.assertTrue(len(q('form .has-error')) > 0)
|
||||
self.assertEqual(charter.get_state(), first_state)
|
||||
|
||||
# change state
|
||||
|
@ -370,18 +370,16 @@ class EditCharterTests(TestCase):
|
|||
desc="Has been copied",
|
||||
due=due_date,
|
||||
resolved="")
|
||||
# m2 isn't used -- missing test?
|
||||
m2 = GroupMilestone.objects.create(group=group, # pyflakes:ignore
|
||||
state_id="active",
|
||||
desc="To be deleted",
|
||||
due=due_date,
|
||||
resolved="")
|
||||
# m3 isn't used -- missing test?
|
||||
m3 = GroupMilestone.objects.create(group=group, # pyflakes:ignore
|
||||
state_id="charter",
|
||||
desc="Has been copied",
|
||||
due=due_date,
|
||||
resolved="")
|
||||
GroupMilestone.objects.create(group=group,
|
||||
state_id="active",
|
||||
desc="To be deleted",
|
||||
due=due_date,
|
||||
resolved="")
|
||||
GroupMilestone.objects.create(group=group,
|
||||
state_id="charter",
|
||||
desc="Has been copied",
|
||||
due=due_date,
|
||||
resolved="")
|
||||
m4 = GroupMilestone.objects.create(group=group,
|
||||
state_id="charter",
|
||||
desc="New charter milestone",
|
||||
|
@ -392,7 +390,7 @@ class EditCharterTests(TestCase):
|
|||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertTrue("Send out the announcement" in q('input[type=submit]')[0].get('value'))
|
||||
self.assertTrue(q('[type=submit]:contains("Send announcement")'))
|
||||
self.assertEqual(len(q('pre')), 1)
|
||||
|
||||
# approve
|
||||
|
|
|
@ -45,13 +45,13 @@ class ConflictReviewTests(TestCase):
|
|||
r = self.client.post(url,dict(create_in_state=""))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertTrue(len(q('form ul.errorlist')) > 0)
|
||||
self.assertTrue(len(q('form .has-error')) > 0)
|
||||
self.assertEqual(Document.objects.filter(name='conflict-review-imaginary-independent-submission').count() , 0)
|
||||
|
||||
r = self.client.post(url,dict(ad=""))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertTrue(len(q('form ul.errorlist')) > 0)
|
||||
self.assertTrue(len(q('form .has-error')) > 0)
|
||||
self.assertEqual(Document.objects.filter(name='conflict-review-imaginary-independent-submission').count() , 0)
|
||||
|
||||
# successful review start
|
||||
|
@ -139,7 +139,7 @@ class ConflictReviewTests(TestCase):
|
|||
r = self.client.post(url,dict(review_state=""))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertTrue(len(q('form ul.errorlist')) > 0)
|
||||
self.assertTrue(len(q('form .has-error')) > 0)
|
||||
|
||||
# successful change to AD Review
|
||||
adrev_pk = str(State.objects.get(used=True, slug='adrev',type__slug='conflrev').pk)
|
||||
|
@ -274,7 +274,7 @@ class ConflictReviewTests(TestCase):
|
|||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertEqual(len(q('form.approve')),1)
|
||||
self.assertEqual(len(q('[type=submit]:contains("Send announcement")')), 1)
|
||||
if approve_type == 'appr-noprob':
|
||||
self.assertTrue( 'IESG has no problem' in ''.join(wrap(r.content,2**16)))
|
||||
else:
|
||||
|
|
|
@ -33,7 +33,7 @@ class ChangeStateTests(TestCase):
|
|||
login_testing_unauthorized(self, "secretary", url)
|
||||
|
||||
first_state = draft.get_state("draft-iesg")
|
||||
next_states = first_state.next_states
|
||||
next_states = first_state.next_states.all()
|
||||
|
||||
# normal get
|
||||
r = self.client.get(url)
|
||||
|
@ -42,14 +42,14 @@ class ChangeStateTests(TestCase):
|
|||
self.assertEqual(len(q('form select[name=state]')), 1)
|
||||
|
||||
if next_states:
|
||||
self.assertTrue(len(q('.next-states form input[type=hidden]')) > 0)
|
||||
self.assertEqual(len(q('[type=submit][value="%s"]' % next_states[0].name)), 1)
|
||||
|
||||
|
||||
# faulty post
|
||||
r = self.client.post(url, dict(state=State.objects.get(used=True, type="draft", slug="active").pk))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertTrue(len(q('form ul.errorlist')) > 0)
|
||||
self.assertTrue(len(q('form .has-error')) > 0)
|
||||
draft = Document.objects.get(name=draft.name)
|
||||
self.assertEqual(draft.get_state("draft-iesg"), first_state)
|
||||
|
||||
|
@ -81,7 +81,7 @@ class ChangeStateTests(TestCase):
|
|||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertEqual(len(q('.prev-state form input[name="state"]')), 1)
|
||||
self.assertEqual(len(q('form [type=submit][value="%s"]' % first_state.name)), 1)
|
||||
|
||||
def test_pull_from_rfc_queue(self):
|
||||
draft = make_test_data()
|
||||
|
@ -127,7 +127,7 @@ class ChangeStateTests(TestCase):
|
|||
r = self.client.post(url, dict(state="foobarbaz"))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertTrue(len(q('form ul.errorlist')) > 0)
|
||||
self.assertTrue(len(q('form .has-error')) > 0)
|
||||
draft = Document.objects.get(name=draft.name)
|
||||
self.assertEqual(draft.get_state("draft-iana-review"), first_state)
|
||||
|
||||
|
@ -149,7 +149,7 @@ class ChangeStateTests(TestCase):
|
|||
|
||||
self.assertTrue(not draft.latest_event(type="changed_ballot_writeup_text"))
|
||||
r = self.client.post(url, dict(state=State.objects.get(used=True, type="draft-iesg", slug="lc-req").pk))
|
||||
self.assertContains(r, "Your request to issue the Last Call")
|
||||
self.assertTrue("Your request to issue" in r.content)
|
||||
|
||||
# last call text
|
||||
e = draft.latest_event(WriteupDocEvent, type="changed_last_call_text")
|
||||
|
@ -195,7 +195,7 @@ class EditInfoTests(TestCase):
|
|||
r = self.client.post(url, dict(ad="123456789"))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertTrue(len(q('form ul.errorlist')) > 0)
|
||||
self.assertTrue(len(q('form .has-error')) > 0)
|
||||
draft = Document.objects.get(name=draft.name)
|
||||
self.assertEqual(draft.ad, prev_ad)
|
||||
|
||||
|
@ -689,7 +689,7 @@ class IndividualInfoFormsTests(TestCase):
|
|||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code,200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertEqual(len(q('form.change-stream')),1)
|
||||
self.assertEqual(len(q('[type=submit]:contains("Save")')), 1)
|
||||
|
||||
# shift to ISE stream
|
||||
messages_before = len(outbox)
|
||||
|
@ -742,13 +742,13 @@ class IndividualInfoFormsTests(TestCase):
|
|||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code,200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertEqual(len(q('form.change-intended-status')),1)
|
||||
self.assertEqual(len(q('[type=submit]:contains("Save")')), 1)
|
||||
|
||||
# don't allow status level to be cleared
|
||||
r = self.client.post(url,dict(intended_std_level=""))
|
||||
self.assertEqual(r.status_code,200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertTrue(len(q('form ul.errorlist')) > 0)
|
||||
self.assertTrue(len(q('form .has-error')) > 0)
|
||||
|
||||
# change intended status level
|
||||
messages_before = len(outbox)
|
||||
|
@ -768,7 +768,7 @@ class IndividualInfoFormsTests(TestCase):
|
|||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code,200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertEqual(len(q('form.telechat-date')),1)
|
||||
self.assertEqual(len(q('[type=submit]:contains("Save")')), 1)
|
||||
|
||||
# set a date
|
||||
self.assertFalse(self.doc.latest_event(TelechatDocEvent, "scheduled_for_telechat"))
|
||||
|
@ -791,7 +791,7 @@ class IndividualInfoFormsTests(TestCase):
|
|||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code,200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertEqual(len(q('form.edit-iesg-note')),1)
|
||||
self.assertEqual(len(q('[type=submit]:contains("Save")')),1)
|
||||
|
||||
# post
|
||||
r = self.client.post(url,dict(note='ZpyQFGmA\r\nZpyQFGmA'))
|
||||
|
@ -868,7 +868,7 @@ class IndividualInfoFormsTests(TestCase):
|
|||
r = self.client.post(url, dict(shepherd=two_answers))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertTrue(len(q('form ul.errorlist')) > 0)
|
||||
self.assertTrue(len(q('form .has-error')) > 0)
|
||||
|
||||
def test_doc_change_shepherd_email(self):
|
||||
self.doc.shepherd = None
|
||||
|
@ -914,15 +914,16 @@ class IndividualInfoFormsTests(TestCase):
|
|||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code,200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertEqual(len(q('span[id=doc_edit_shepherd_writeup]')),1)
|
||||
self.assertEqual(len(q('.content-wrapper a:contains("Edit")')), 1)
|
||||
|
||||
# Try again when no longer a shepherd.
|
||||
|
||||
self.doc.shepherd = None
|
||||
self.doc.save()
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code,200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertEqual(len(q('span[id=doc_edit_shepherd_writeup]')),1)
|
||||
self.assertEqual(len(q('.content-wrapper a:contains("Edit")')), 0)
|
||||
|
||||
def test_doc_change_shepherd_writeup(self):
|
||||
url = urlreverse('doc_edit_shepherd_writeup',kwargs=dict(name=self.docname))
|
||||
|
@ -1037,7 +1038,7 @@ class RequestPublicationTests(TestCase):
|
|||
q = PyQuery(r.content)
|
||||
subject = q('input#id_subject')[0].get("value")
|
||||
self.assertTrue("Document Action" in subject)
|
||||
body = q('.request-publication #id_body').text()
|
||||
body = q('#id_body').text()
|
||||
self.assertTrue("Informational" in body)
|
||||
self.assertTrue("IAB" in body)
|
||||
|
||||
|
@ -1246,38 +1247,34 @@ class ChangeReplacesTests(TestCase):
|
|||
|
||||
# normal get
|
||||
r = self.client.get(url)
|
||||
self.assertEquals(r.status_code, 200)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertEquals(len(q('form[class=change-replaces]')), 1)
|
||||
self.assertEqual(len(q('[type=submit]:contains("Save")')), 1)
|
||||
|
||||
# Post that says replacea replaces base a
|
||||
self.assertEquals(self.basea.get_state().slug,'active')
|
||||
repljson='{"%d":"%s"}'%(DocAlias.objects.get(name=self.basea.name).id,self.basea.name)
|
||||
r = self.client.post(url, dict(replaces=repljson))
|
||||
self.assertEquals(r.status_code, 302)
|
||||
self.assertEqual(self.basea.get_state().slug,'active')
|
||||
r = self.client.post(url, dict(replaces=str(DocAlias.objects.get(name=self.basea.name).id)))
|
||||
self.assertEqual(r.status_code, 302)
|
||||
self.assertEqual(RelatedDocument.objects.filter(relationship__slug='replaces',source=self.replacea).count(),1)
|
||||
self.assertEquals(Document.objects.get(name='draft-test-base-a').get_state().slug,'repl')
|
||||
self.assertEqual(Document.objects.get(name='draft-test-base-a').get_state().slug,'repl')
|
||||
|
||||
# Post that says replaceboth replaces both base a and base b
|
||||
url = urlreverse('doc_change_replaces', kwargs=dict(name=self.replaceboth.name))
|
||||
self.assertEquals(self.baseb.get_state().slug,'expired')
|
||||
repljson='{"%d":"%s","%d":"%s"}'%(DocAlias.objects.get(name=self.basea.name).id,self.basea.name,
|
||||
DocAlias.objects.get(name=self.baseb.name).id,self.baseb.name)
|
||||
r = self.client.post(url, dict(replaces=repljson))
|
||||
self.assertEquals(r.status_code, 302)
|
||||
self.assertEquals(Document.objects.get(name='draft-test-base-a').get_state().slug,'repl')
|
||||
self.assertEquals(Document.objects.get(name='draft-test-base-b').get_state().slug,'repl')
|
||||
self.assertEqual(self.baseb.get_state().slug,'expired')
|
||||
r = self.client.post(url, dict(replaces=str(DocAlias.objects.get(name=self.basea.name).id) + "," + str(DocAlias.objects.get(name=self.baseb.name).id)))
|
||||
self.assertEqual(r.status_code, 302)
|
||||
self.assertEqual(Document.objects.get(name='draft-test-base-a').get_state().slug,'repl')
|
||||
self.assertEqual(Document.objects.get(name='draft-test-base-b').get_state().slug,'repl')
|
||||
|
||||
# Post that undoes replaceboth
|
||||
repljson='{}'
|
||||
r = self.client.post(url, dict(replaces=repljson))
|
||||
self.assertEquals(r.status_code, 302)
|
||||
self.assertEquals(Document.objects.get(name='draft-test-base-a').get_state().slug,'repl') # Because A is still also replaced by replacea
|
||||
self.assertEquals(Document.objects.get(name='draft-test-base-b').get_state().slug,'expired')
|
||||
r = self.client.post(url, dict(replaces=""))
|
||||
self.assertEqual(r.status_code, 302)
|
||||
self.assertEqual(Document.objects.get(name='draft-test-base-a').get_state().slug,'repl') # Because A is still also replaced by replacea
|
||||
self.assertEqual(Document.objects.get(name='draft-test-base-b').get_state().slug,'expired')
|
||||
|
||||
# Post that undoes replacea
|
||||
url = urlreverse('doc_change_replaces', kwargs=dict(name=self.replacea.name))
|
||||
r = self.client.post(url, dict(replaces=repljson))
|
||||
self.assertEquals(r.status_code, 302)
|
||||
self.assertEquals(Document.objects.get(name='draft-test-base-a').get_state().slug,'active')
|
||||
r = self.client.post(url, dict(replaces=""))
|
||||
self.assertEqual(r.status_code, 302)
|
||||
self.assertEqual(Document.objects.get(name='draft-test-base-a').get_state().slug,'active')
|
||||
|
||||
|
|
|
@ -60,7 +60,7 @@ class GroupMaterialTests(TestCase):
|
|||
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertTrue(len(q('form ul.errorlist')) > 0)
|
||||
self.assertTrue(len(q('.has-error')) > 0)
|
||||
|
||||
test_file.seek(0)
|
||||
|
||||
|
@ -88,7 +88,7 @@ class GroupMaterialTests(TestCase):
|
|||
state=State.objects.get(type="slides", slug="active").pk,
|
||||
material=test_file))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertTrue(len(q('form ul.errorlist')) > 0)
|
||||
self.assertTrue(len(q('.has-error')) > 0)
|
||||
|
||||
def test_change_state(self):
|
||||
doc = self.create_slides()
|
||||
|
|
|
@ -41,25 +41,25 @@ class StatusChangeTests(TestCase):
|
|||
r = self.client.post(url,dict(document_name="bogus",title="Bogus Title",ad="",create_in_state=state_strpk,notify='ipu@ietf.org'))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertTrue(len(q('form ul.errorlist')) > 0)
|
||||
self.assertTrue(len(q('form .has-error')) > 0)
|
||||
|
||||
## Must set a name
|
||||
r = self.client.post(url,dict(document_name="",title="Bogus Title",ad=ad_strpk,create_in_state=state_strpk,notify='ipu@ietf.org'))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertTrue(len(q('form ul.errorlist')) > 0)
|
||||
self.assertTrue(len(q('form .has-error')) > 0)
|
||||
|
||||
## Must not choose a document name that already exists
|
||||
r = self.client.post(url,dict(document_name="imaginary-mid-review",title="Bogus Title",ad=ad_strpk,create_in_state=state_strpk,notify='ipu@ietf.org'))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertTrue(len(q('form ul.errorlist')) > 0)
|
||||
self.assertTrue(len(q('form .has-error')) > 0)
|
||||
|
||||
## Must set a title
|
||||
r = self.client.post(url,dict(document_name="bogus",title="",ad=ad_strpk,create_in_state=state_strpk,notify='ipu@ietf.org'))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertTrue(len(q('form ul.errorlist')) > 0)
|
||||
self.assertTrue(len(q('form .has-error')) > 0)
|
||||
|
||||
# successful status change start
|
||||
r = self.client.post(url,dict(document_name="imaginary-new",title="A new imaginary status change",ad=ad_strpk,
|
||||
|
@ -90,7 +90,7 @@ class StatusChangeTests(TestCase):
|
|||
r = self.client.post(url,dict(new_state=""))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertTrue(len(q('form ul.errorlist')) > 0)
|
||||
self.assertTrue(len(q('form .has-error')) > 0)
|
||||
|
||||
# successful change to AD Review
|
||||
adrev_pk = str(State.objects.get(slug='adrev',type__slug='statchg').pk)
|
||||
|
@ -283,7 +283,7 @@ class StatusChangeTests(TestCase):
|
|||
messages_before = len(outbox)
|
||||
r = self.client.post(url,dict(last_call_text='stuff',send_last_call_request='Save+and+Request+Last+Call'))
|
||||
self.assertEqual(r.status_code,200)
|
||||
self.assertTrue( 'Last Call Requested' in ''.join(wrap(r.content,2**16)))
|
||||
self.assertTrue( 'Last call requested' in ''.join(wrap(r.content,2**16)))
|
||||
self.assertEqual(len(outbox), messages_before + 1)
|
||||
self.assertTrue('iesg-secretary' in outbox[-1]['To'])
|
||||
self.assertTrue('Last Call:' in outbox[-1]['Subject'])
|
||||
|
@ -307,7 +307,7 @@ class StatusChangeTests(TestCase):
|
|||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertEqual(len(q('form.approve')),1)
|
||||
self.assertEqual(len(q('[type=submit]:contains("Send announcement")')), 1)
|
||||
# There should be two messages to edit
|
||||
self.assertEqual(q('input#id_form-TOTAL_FORMS').val(),'2')
|
||||
self.assertTrue( '(rfc9999) to Internet Standard' in ''.join(wrap(r.content,2**16)))
|
||||
|
@ -345,30 +345,27 @@ class StatusChangeTests(TestCase):
|
|||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertEqual(len(q('form.edit-status-change-rfcs')),1)
|
||||
self.assertEqual(len(q('.content-wrapper [type=submit]:contains("Save")')),1)
|
||||
# There should be three rows on the form
|
||||
self.assertEqual(len(q('tr[id^=relation_row]')),3)
|
||||
self.assertEqual(len(q('.content-wrapper .row')),3)
|
||||
|
||||
# Try to add a relation to an RFC that doesn't exist
|
||||
r = self.client.post(url,dict(new_relation_row_blah="rfc9997",
|
||||
statchg_relation_row_blah="tois",
|
||||
Submit="Submit"))
|
||||
statchg_relation_row_blah="tois"))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertTrue(len(q('form ul.errorlist')) > 0)
|
||||
|
||||
# Try to add a relation leaving the relation type blank
|
||||
r = self.client.post(url,dict(new_relation_row_blah="rfc9999",
|
||||
statchg_relation_row_blah="",
|
||||
Submit="Submit"))
|
||||
statchg_relation_row_blah=""))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertTrue(len(q('form ul.errorlist')) > 0)
|
||||
|
||||
# Try to add a relation with an unknown relationship type
|
||||
r = self.client.post(url,dict(new_relation_row_blah="rfc9999",
|
||||
statchg_relation_row_blah="badslug",
|
||||
Submit="Submit"))
|
||||
statchg_relation_row_blah="badslug"))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertTrue(len(q('form ul.errorlist')) > 0)
|
||||
|
@ -379,8 +376,7 @@ class StatusChangeTests(TestCase):
|
|||
new_relation_row_foo="rfc9998",
|
||||
statchg_relation_row_foo="tobcp",
|
||||
new_relation_row_nob="rfc14",
|
||||
statchg_relation_row_nob="tohist",
|
||||
Submit="Submit"))
|
||||
statchg_relation_row_nob="tohist"))
|
||||
self.assertEqual(r.status_code, 302)
|
||||
doc = Document.objects.get(name='status-change-imaginary-mid-review')
|
||||
self.assertEqual(doc.relateddocument_set.count(),3)
|
||||
|
|
|
@ -49,6 +49,7 @@ urlpatterns = patterns('',
|
|||
|
||||
url(r'^all/$', views_search.index_all_drafts, name="index_all_drafts"),
|
||||
url(r'^active/$', views_search.index_active_drafts, name="index_active_drafts"),
|
||||
url(r'^select2search/(?P<model_name>(document|docalias))/(?P<doc_type>draft)/$', views_search.ajax_select2_search_docs, name="ajax_select2_search_docs"),
|
||||
|
||||
url(r'^(?P<name>[A-Za-z0-9._+-]+)/(?:(?P<rev>[0-9-]+)/)?$', views_doc.document_main, name="doc_view"),
|
||||
url(r'^(?P<name>[A-Za-z0-9._+-]+)/history/$', views_doc.document_history, name="doc_history"),
|
||||
|
|
|
@ -519,3 +519,9 @@ def get_initial_notify(doc,extra=None):
|
|||
receivers.extend(extra)
|
||||
|
||||
return ", ".join(set([x.strip() for x in receivers]))
|
||||
|
||||
def uppercase_std_abbreviated_name(name):
|
||||
if re.match('(rfc|bcp|std|fyi) ?[0-9]+$', name):
|
||||
return name.upper()
|
||||
else:
|
||||
return name
|
||||
|
|
|
@ -116,7 +116,7 @@ def edit_position(request, name, ballot_id):
|
|||
if has_role(request.user, "Secretariat"):
|
||||
ad_id = request.GET.get('ad')
|
||||
if not ad_id:
|
||||
raise Http404()
|
||||
raise Http404
|
||||
ad = get_object_or_404(Person, pk=ad_id)
|
||||
|
||||
old_pos = doc.latest_event(BallotPositionDocEvent, type="changed_ballot_position", ad=ad, ballot=ballot)
|
||||
|
@ -251,12 +251,12 @@ def send_ballot_comment(request, name, ballot_id):
|
|||
if not has_role(request.user, "Area Director"):
|
||||
ad_id = request.GET.get('ad')
|
||||
if not ad_id:
|
||||
raise Http404()
|
||||
raise Http404
|
||||
ad = get_object_or_404(Person, pk=ad_id)
|
||||
|
||||
pos = doc.latest_event(BallotPositionDocEvent, type="changed_ballot_position", ad=ad, ballot=ballot)
|
||||
if not pos:
|
||||
raise Http404()
|
||||
raise Http404
|
||||
|
||||
subj = []
|
||||
d = ""
|
||||
|
@ -330,11 +330,11 @@ def defer_ballot(request, name):
|
|||
"""Signal post-pone of ballot, notifying relevant parties."""
|
||||
doc = get_object_or_404(Document, docalias__name=name)
|
||||
if doc.type_id not in ('draft','conflrev','statchg'):
|
||||
raise Http404()
|
||||
raise Http404
|
||||
interesting_state = dict(draft='draft-iesg',conflrev='conflrev',statchg='statchg')
|
||||
state = doc.get_state(interesting_state[doc.type_id])
|
||||
if not state or state.slug=='defer' or not doc.telechat_date():
|
||||
raise Http404()
|
||||
raise Http404
|
||||
|
||||
login = request.user.person
|
||||
telechat_date = TelechatDate.objects.active().order_by("date")[1].date
|
||||
|
@ -380,13 +380,13 @@ def undefer_ballot(request, name):
|
|||
"""undo deferral of ballot ballot."""
|
||||
doc = get_object_or_404(Document, docalias__name=name)
|
||||
if doc.type_id not in ('draft','conflrev','statchg'):
|
||||
raise Http404()
|
||||
raise Http404
|
||||
if doc.type_id == 'draft' and not doc.get_state("draft-iesg"):
|
||||
raise Http404()
|
||||
raise Http404
|
||||
interesting_state = dict(draft='draft-iesg',conflrev='conflrev',statchg='statchg')
|
||||
state = doc.get_state(interesting_state[doc.type_id])
|
||||
if not state or state.slug!='defer':
|
||||
raise Http404()
|
||||
raise Http404
|
||||
|
||||
telechat_date = TelechatDate.objects.active().order_by("date")[0].date
|
||||
|
||||
|
@ -417,7 +417,7 @@ def lastcalltext(request, name):
|
|||
"""Editing of the last call text"""
|
||||
doc = get_object_or_404(Document, docalias__name=name)
|
||||
if not doc.get_state("draft-iesg"):
|
||||
raise Http404()
|
||||
raise Http404
|
||||
|
||||
login = request.user.person
|
||||
|
||||
|
@ -581,7 +581,7 @@ def ballot_approvaltext(request, name):
|
|||
"""Editing of approval text"""
|
||||
doc = get_object_or_404(Document, docalias__name=name)
|
||||
if not doc.get_state("draft-iesg"):
|
||||
raise Http404()
|
||||
raise Http404
|
||||
|
||||
login = request.user.person
|
||||
|
||||
|
@ -629,7 +629,7 @@ def approve_ballot(request, name):
|
|||
"""Approve ballot, sending out announcement, changing state."""
|
||||
doc = get_object_or_404(Document, docalias__name=name)
|
||||
if not doc.get_state("draft-iesg"):
|
||||
raise Http404()
|
||||
raise Http404
|
||||
|
||||
login = request.user.person
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ from django.contrib.auth.decorators import login_required
|
|||
import debug # pyflakes:ignore
|
||||
|
||||
from ietf.doc.models import ( Document, DocHistory, State, DocEvent, BallotDocEvent,
|
||||
BallotPositionDocEvent, InitialReviewDocEvent, NewRevisionDocEvent,
|
||||
BallotPositionDocEvent, InitialReviewDocEvent, NewRevisionDocEvent,
|
||||
WriteupDocEvent, save_document_in_history )
|
||||
from ietf.doc.utils import ( add_state_change_event, close_open_ballots,
|
||||
create_ballot_if_not_open, get_chartering_type )
|
||||
|
@ -33,8 +33,8 @@ from ietf.group.mails import email_iesg_secretary_re_charter
|
|||
class ChangeStateForm(forms.Form):
|
||||
charter_state = forms.ModelChoiceField(State.objects.filter(used=True, type="charter"), label="Charter state", empty_label=None, required=False)
|
||||
initial_time = forms.IntegerField(initial=0, label="Review time", help_text="(in weeks)", required=False)
|
||||
message = forms.CharField(widget=forms.Textarea, help_text="Leave blank to change state without notifying the Secretariat", required=False, label=mark_safe("Message to<br> Secretariat"))
|
||||
comment = forms.CharField(widget=forms.Textarea, help_text="Optional comment for the charter history", required=False)
|
||||
message = forms.CharField(widget=forms.Textarea, help_text="Leave blank to change state without notifying the Secretariat.", required=False, label=mark_safe("Message to the Secretariat"))
|
||||
comment = forms.CharField(widget=forms.Textarea, help_text="Optional comment for the charter history.", required=False)
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.hide = kwargs.pop('hide', None)
|
||||
group = kwargs.pop('group')
|
||||
|
@ -47,7 +47,7 @@ class ChangeStateForm(forms.Form):
|
|||
# hide requested fields
|
||||
if self.hide:
|
||||
for f in self.hide:
|
||||
self.fields[f].widget = forms.HiddenInput
|
||||
self.fields[f].widget = forms.HiddenInput()
|
||||
|
||||
@login_required
|
||||
def change_state(request, name, option=None):
|
||||
|
@ -101,7 +101,7 @@ def change_state(request, name, option=None):
|
|||
e.state_id = group.state.slug
|
||||
e.desc = "Group state changed to %s from %s" % (group.state, oldstate)
|
||||
e.save()
|
||||
|
||||
|
||||
else:
|
||||
charter_state = State.objects.get(used=True, type="charter", slug="approved")
|
||||
charter_rev = approved_revision(charter.rev)
|
||||
|
@ -225,9 +225,9 @@ def change_state(request, name, option=None):
|
|||
context_instance=RequestContext(request))
|
||||
|
||||
class ChangeTitleForm(forms.Form):
|
||||
charter_title = forms.CharField(widget=forms.TextInput, label="Charter title", help_text="Enter new charter title", required=True)
|
||||
message = forms.CharField(widget=forms.Textarea, help_text="Leave blank to change the title without notifying the Secretariat", required=False, label=mark_safe("Message to<br> Secretariat"))
|
||||
comment = forms.CharField(widget=forms.Textarea, help_text="Optional comment for the charter history", required=False)
|
||||
charter_title = forms.CharField(widget=forms.TextInput, label="Charter title", help_text="Enter new charter title.", required=True)
|
||||
message = forms.CharField(widget=forms.Textarea, help_text="Leave blank to change the title without notifying the Secretariat.", required=False, label=mark_safe("Message to Secretariat"))
|
||||
comment = forms.CharField(widget=forms.Textarea, help_text="Optional comment for the charter history.", required=False)
|
||||
def __init__(self, *args, **kwargs):
|
||||
charter = kwargs.pop('charter')
|
||||
super(ChangeTitleForm, self).__init__(*args, **kwargs)
|
||||
|
@ -328,8 +328,8 @@ def edit_ad(request, name):
|
|||
|
||||
|
||||
class UploadForm(forms.Form):
|
||||
content = forms.CharField(widget=forms.Textarea, label="Charter text", help_text="Edit the charter text", required=False)
|
||||
txt = forms.FileField(label=".txt format", help_text="Or upload a .txt file", required=False)
|
||||
content = forms.CharField(widget=forms.Textarea, label="Charter text", help_text="Edit the charter text.", required=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", "")
|
||||
|
@ -382,7 +382,7 @@ def submit(request, name=None, option=None):
|
|||
e.desc = "New version available: <b>%s-%s.txt</b>" % (charter.canonical_name(), charter.rev)
|
||||
e.rev = charter.rev
|
||||
e.save()
|
||||
|
||||
|
||||
# Save file on disk
|
||||
form.save(group, charter.rev)
|
||||
|
||||
|
@ -460,7 +460,7 @@ def announcement_text(request, name, ann):
|
|||
e.desc = "%s %s text was changed" % (group.type.name, ann)
|
||||
e.text = t
|
||||
e.save()
|
||||
|
||||
|
||||
charter.time = e.time
|
||||
charter.save()
|
||||
|
||||
|
@ -495,7 +495,7 @@ class BallotWriteupForm(forms.Form):
|
|||
|
||||
def clean_ballot_writeup(self):
|
||||
return self.cleaned_data["ballot_writeup"].replace("\r", "")
|
||||
|
||||
|
||||
@role_required('Area Director','Secretariat')
|
||||
def ballot_writeupnotes(request, name):
|
||||
"""Editing of ballot write-up and notes"""
|
||||
|
@ -508,13 +508,13 @@ def ballot_writeupnotes(request, name):
|
|||
login = request.user.person
|
||||
|
||||
approval = charter.latest_event(WriteupDocEvent, type="changed_action_announcement")
|
||||
|
||||
|
||||
existing = charter.latest_event(WriteupDocEvent, type="changed_ballot_writeup_text")
|
||||
if not existing:
|
||||
existing = generate_ballot_writeup(request, charter)
|
||||
|
||||
reissue = charter.latest_event(DocEvent, type="sent_ballot_announcement")
|
||||
|
||||
|
||||
form = BallotWriteupForm(initial=dict(ballot_writeup=existing.text))
|
||||
|
||||
if request.method == 'POST' and ("save_ballot_writeup" in request.POST or "send_ballot" in request.POST):
|
||||
|
@ -699,7 +699,7 @@ def approve(request, name):
|
|||
send_mail_preformatted(request, announcement)
|
||||
|
||||
return HttpResponseRedirect(charter.get_absolute_url())
|
||||
|
||||
|
||||
return render_to_response('doc/charter/approve.html',
|
||||
dict(charter=charter,
|
||||
announcement=announcement),
|
||||
|
|
|
@ -23,7 +23,7 @@ from ietf.utils.textupload import get_cleaned_text_file_content
|
|||
|
||||
class ChangeStateForm(forms.Form):
|
||||
review_state = forms.ModelChoiceField(State.objects.filter(used=True, type="conflrev"), label="Conflict review state", empty_label=None, required=True)
|
||||
comment = forms.CharField(widget=forms.Textarea, help_text="Optional comment for the review history", required=False)
|
||||
comment = forms.CharField(widget=forms.Textarea, help_text="Optional comment for the review history.", required=False)
|
||||
|
||||
@role_required("Area Director", "Secretariat")
|
||||
def change_state(request, name, option=None):
|
||||
|
@ -114,8 +114,8 @@ def send_conflict_eval_email(request,review):
|
|||
msg)
|
||||
|
||||
class UploadForm(forms.Form):
|
||||
content = forms.CharField(widget=forms.Textarea, label="Conflict review response", help_text="Edit the conflict review response", required=False)
|
||||
txt = forms.FileField(label=".txt format", help_text="Or upload a .txt file", required=False)
|
||||
content = forms.CharField(widget=forms.Textarea, label="Conflict review response", help_text="Edit the conflict review response.", required=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", "")
|
||||
|
@ -265,7 +265,7 @@ def default_approval_text(review):
|
|||
|
||||
|
||||
class AnnouncementForm(forms.Form):
|
||||
announcement_text = forms.CharField(widget=forms.Textarea, label="IETF Conflict Review Announcement", help_text="Edit the announcement message", required=True)
|
||||
announcement_text = forms.CharField(widget=forms.Textarea, label="IETF Conflict Review Announcement", help_text="Edit the announcement message.", required=True)
|
||||
|
||||
@role_required("Secretariat")
|
||||
def approve(request, name):
|
||||
|
@ -324,13 +324,13 @@ def approve(request, name):
|
|||
context_instance=RequestContext(request))
|
||||
|
||||
class SimpleStartReviewForm(forms.Form):
|
||||
notify = forms.CharField(max_length=255, label="Notice emails", help_text="Separate email addresses with commas", required=False)
|
||||
notify = forms.CharField(max_length=255, label="Notice emails", help_text="Separate email addresses with commas.", required=False)
|
||||
|
||||
class StartReviewForm(forms.Form):
|
||||
ad = forms.ModelChoiceField(Person.objects.filter(role__name="ad", role__group__state="active",role__group__type='area').order_by('name'),
|
||||
label="Shepherding AD", empty_label="(None)", required=True)
|
||||
create_in_state = forms.ModelChoiceField(State.objects.filter(used=True, type="conflrev", slug__in=("needshep", "adrev")), empty_label=None, required=False)
|
||||
notify = forms.CharField(max_length=255, label="Notice emails", help_text="Separate email addresses with commas", required=False)
|
||||
notify = forms.CharField(max_length=255, label="Notice emails", help_text="Separate email addresses with commas.", required=False)
|
||||
telechat_date = forms.TypedChoiceField(coerce=lambda x: datetime.datetime.strptime(x, '%Y-%m-%d').date(), empty_value=None, required=False, widget=forms.Select(attrs={'onchange':'make_bold()'}))
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
import os, datetime, urllib, json, glob
|
||||
|
||||
from django.http import HttpResponse, Http404 , HttpResponseForbidden
|
||||
from django.shortcuts import render_to_response, get_object_or_404, redirect
|
||||
from django.shortcuts import render_to_response, get_object_or_404, redirect, render
|
||||
from django.template import RequestContext
|
||||
from django.template.loader import render_to_string
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
|
@ -869,20 +869,17 @@ def telechat_date(request, name):
|
|||
e = doc.latest_event(TelechatDocEvent, type="scheduled_for_telechat")
|
||||
initial_returning_item = bool(e and e.returning_item)
|
||||
|
||||
prompts = []
|
||||
warnings = []
|
||||
if e and e.telechat_date and doc.type.slug != 'charter':
|
||||
if e.telechat_date==datetime.date.today():
|
||||
prompts.append( "This document is currently scheduled for today's telechat. "
|
||||
+"Please set the returning item bit carefully.")
|
||||
warnings.append( "This document is currently scheduled for today's telechat. "
|
||||
+"Please set the returning item bit carefully.")
|
||||
|
||||
elif e.telechat_date<datetime.date.today() and has_same_ballot(doc,e.telechat_date):
|
||||
initial_returning_item = True
|
||||
prompts.append( "This document appears to have been on a previous telechat with the same ballot, "
|
||||
warnings.append( "This document appears to have been on a previous telechat with the same ballot, "
|
||||
+"so the returning item bit has been set. Clear it if that is not appropriate.")
|
||||
|
||||
else:
|
||||
pass
|
||||
|
||||
initial = dict(telechat_date=e.telechat_date if e else None,
|
||||
returning_item = initial_returning_item,
|
||||
)
|
||||
|
@ -901,13 +898,12 @@ def telechat_date(request, name):
|
|||
if doc.type.slug=='charter':
|
||||
del form.fields['returning_item']
|
||||
|
||||
return render_to_response('doc/edit_telechat_date.html',
|
||||
return render(request, 'doc/edit_telechat_date.html',
|
||||
dict(doc=doc,
|
||||
form=form,
|
||||
user=request.user,
|
||||
prompts=prompts,
|
||||
login=login),
|
||||
context_instance=RequestContext(request))
|
||||
warnings=warnings,
|
||||
login=login))
|
||||
|
||||
def edit_notify(request, name):
|
||||
"""Change the set of email addresses document change notificaitions go to."""
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# changing state and metadata on Internet Drafts
|
||||
|
||||
import datetime, json
|
||||
import datetime
|
||||
|
||||
from django import forms
|
||||
from django.http import HttpResponseRedirect, HttpResponseForbidden, Http404
|
||||
|
@ -24,13 +24,14 @@ from ietf.doc.utils import ( add_state_change_event, can_adopt_draft,
|
|||
get_tags_for_stream_id, nice_consensus,
|
||||
update_reminder, update_telechat, make_notify_changed_event, get_initial_notify )
|
||||
from ietf.doc.lastcall import request_last_call
|
||||
from ietf.doc.fields import SearchableDocAliasesField
|
||||
from ietf.group.models import Group, Role
|
||||
from ietf.iesg.models import TelechatDate
|
||||
from ietf.ietfauth.utils import has_role, is_authorized_in_doc_stream, user_is_person
|
||||
from ietf.ietfauth.utils import role_required
|
||||
from ietf.message.models import Message
|
||||
from ietf.name.models import IntendedStdLevelName, DocTagName, StreamName
|
||||
from ietf.person.fields import AutocompletedEmailField
|
||||
from ietf.person.fields import SearchableEmailField
|
||||
from ietf.person.models import Person, Email
|
||||
from ietf.secr.lib.template import jsonapi
|
||||
from ietf.utils.mail import send_mail, send_mail_message
|
||||
|
@ -64,7 +65,7 @@ def change_state(request, name):
|
|||
and logging the change as a comment."""
|
||||
doc = get_object_or_404(Document, docalias__name=name)
|
||||
if (not doc.latest_event(type="started_iesg_process")) or doc.get_state_slug() == "expired":
|
||||
raise Http404()
|
||||
raise Http404
|
||||
|
||||
login = request.user.person
|
||||
|
||||
|
@ -214,7 +215,7 @@ def change_stream(request, name):
|
|||
and logging the change as a comment."""
|
||||
doc = get_object_or_404(Document, docalias__name=name)
|
||||
if not doc.type_id=='draft':
|
||||
raise Http404()
|
||||
raise Http404
|
||||
|
||||
if not (has_role(request.user, ("Area Director", "Secretariat")) or
|
||||
(request.user.is_authenticated() and
|
||||
|
@ -307,35 +308,21 @@ def collect_email_addresses(emails, doc):
|
|||
return emails
|
||||
|
||||
class ReplacesForm(forms.Form):
|
||||
replaces = forms.CharField(max_length=512,widget=forms.HiddenInput)
|
||||
replaces = SearchableDocAliasesField(required=False)
|
||||
comment = forms.CharField(widget=forms.Textarea, required=False)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.doc = kwargs.pop('doc')
|
||||
super(ReplacesForm, self).__init__(*args, **kwargs)
|
||||
drafts = {}
|
||||
for d in self.doc.related_that_doc("replaces"):
|
||||
drafts[d.id] = d.document.name
|
||||
self.initial['replaces'] = json.dumps(drafts)
|
||||
self.initial['replaces'] = self.doc.related_that_doc("replaces")
|
||||
|
||||
def clean_replaces(self):
|
||||
data = self.cleaned_data['replaces'].strip()
|
||||
if data:
|
||||
ids = [int(x) for x in json.loads(data)]
|
||||
else:
|
||||
return []
|
||||
objects = []
|
||||
for id in ids:
|
||||
try:
|
||||
d = DocAlias.objects.get(pk=id)
|
||||
except DocAlias.DoesNotExist:
|
||||
raise forms.ValidationError("ERROR: %s not found for id %d" % DocAlias._meta.verbos_name, id)
|
||||
for d in self.cleaned_data['replaces']:
|
||||
if d.document == self.doc:
|
||||
raise forms.ValidationError("ERROR: A draft can't replace itself")
|
||||
raise forms.ValidationError("A draft can't replace itself")
|
||||
if d.document.type_id == "draft" and d.document.get_state_slug() == "rfc":
|
||||
raise forms.ValidationError("ERROR: A draft can't replace an RFC")
|
||||
objects.append(d)
|
||||
return objects
|
||||
raise forms.ValidationError("A draft can't replace an RFC")
|
||||
return self.cleaned_data['replaces']
|
||||
|
||||
def replaces(request, name):
|
||||
"""Change 'replaces' set of a Document of type 'draft' , notifying parties
|
||||
|
@ -472,7 +459,7 @@ class EditInfoForm(forms.Form):
|
|||
area = forms.ModelChoiceField(Group.objects.filter(type="area", state="active"), empty_label="(None - individual submission)", required=False, label="Assigned to area")
|
||||
ad = forms.ModelChoiceField(Person.objects.filter(role__name="ad", role__group__state="active",role__group__type='area').order_by('name'), label="Responsible AD", empty_label="(None)", required=True)
|
||||
create_in_state = forms.ModelChoiceField(State.objects.filter(used=True, type="draft-iesg", slug__in=("pub-req", "watching")), empty_label=None, required=False)
|
||||
notify = forms.CharField(max_length=255, label="Notice emails", help_text="Separate email addresses with commas", required=False)
|
||||
notify = forms.CharField(max_length=255, label="Notice emails", help_text="Separate email addresses with commas.", required=False)
|
||||
note = forms.CharField(widget=forms.Textarea, label="IESG note", required=False)
|
||||
telechat_date = forms.TypedChoiceField(coerce=lambda x: datetime.datetime.strptime(x, '%Y-%m-%d').date(), empty_value=None, required=False, widget=forms.Select(attrs={'onchange':'make_bold()'}))
|
||||
returning_item = forms.BooleanField(required=False)
|
||||
|
@ -505,10 +492,10 @@ def to_iesg(request,name):
|
|||
doc = get_object_or_404(Document, docalias__name=name, stream='ietf')
|
||||
|
||||
if doc.get_state_slug('draft') == "expired" or doc.get_state_slug('draft-iesg') == 'pub-req' :
|
||||
raise Http404()
|
||||
raise Http404
|
||||
|
||||
if not is_authorized_in_doc_stream(request.user, doc):
|
||||
raise Http404()
|
||||
raise Http404
|
||||
|
||||
target_state={
|
||||
'iesg' : State.objects.get(type='draft-iesg',slug='pub-req'),
|
||||
|
@ -614,7 +601,7 @@ def edit_info(request, name):
|
|||
necessary and logging changes as document events."""
|
||||
doc = get_object_or_404(Document, docalias__name=name)
|
||||
if doc.get_state_slug() == "expired":
|
||||
raise Http404()
|
||||
raise Http404
|
||||
|
||||
login = request.user.person
|
||||
|
||||
|
@ -764,7 +751,7 @@ def request_resurrect(request, name):
|
|||
"""Request resurrect of expired Internet Draft."""
|
||||
doc = get_object_or_404(Document, docalias__name=name)
|
||||
if doc.get_state_slug() != "expired":
|
||||
raise Http404()
|
||||
raise Http404
|
||||
|
||||
login = request.user.person
|
||||
|
||||
|
@ -788,7 +775,7 @@ def resurrect(request, name):
|
|||
"""Resurrect expired Internet Draft."""
|
||||
doc = get_object_or_404(Document, docalias__name=name)
|
||||
if doc.get_state_slug() != "expired":
|
||||
raise Http404()
|
||||
raise Http404
|
||||
|
||||
login = request.user.person
|
||||
|
||||
|
@ -863,8 +850,8 @@ def edit_iesg_note(request, name):
|
|||
context_instance=RequestContext(request))
|
||||
|
||||
class ShepherdWriteupUploadForm(forms.Form):
|
||||
content = forms.CharField(widget=forms.Textarea, label="Shepherd writeup", help_text="Edit the shepherd writeup", required=False)
|
||||
txt = forms.FileField(label=".txt format", help_text="Or upload a .txt file", required=False)
|
||||
content = forms.CharField(widget=forms.Textarea, label="Shepherd writeup", help_text="Edit the shepherd writeup.", required=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", "")
|
||||
|
@ -942,7 +929,7 @@ def edit_shepherd_writeup(request, name):
|
|||
context_instance=RequestContext(request))
|
||||
|
||||
class ShepherdForm(forms.Form):
|
||||
shepherd = AutocompletedEmailField(required=False, only_users=True)
|
||||
shepherd = SearchableEmailField(required=False, only_users=True)
|
||||
|
||||
def edit_shepherd(request, name):
|
||||
"""Change the shepherd for a Document"""
|
||||
|
@ -1210,7 +1197,7 @@ def request_publication(request, name):
|
|||
class AdoptDraftForm(forms.Form):
|
||||
group = forms.ModelChoiceField(queryset=Group.objects.filter(type__in=["wg", "rg"], state="active").order_by("-type", "acronym"), required=True, empty_label=None)
|
||||
newstate = forms.ModelChoiceField(queryset=State.objects.filter(type__in=['draft-stream-ietf','draft-stream-irtf'],slug__in=['wg-cand', 'c-adopt', 'adopt-wg', 'info', 'wg-doc', 'candidat','active']),required=True,label="State")
|
||||
comment = forms.CharField(widget=forms.Textarea, required=False, label="Comment", help_text="Optional comment explaining the reasons for the adoption")
|
||||
comment = forms.CharField(widget=forms.Textarea, required=False, label="Comment", help_text="Optional comment explaining the reasons for the adoption.")
|
||||
weeks = forms.IntegerField(required=False, label="Expected weeks in adoption state")
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
@ -1323,7 +1310,7 @@ def adopt_draft(request, name):
|
|||
class ChangeStreamStateForm(forms.Form):
|
||||
new_state = forms.ModelChoiceField(queryset=State.objects.filter(used=True), label='State', help_text=u"Only select 'Submitted to IESG for Publication' to correct errors. Use the document's main page to request publication.")
|
||||
weeks = forms.IntegerField(label='Expected weeks in state',required=False)
|
||||
comment = forms.CharField(widget=forms.Textarea, required=False, help_text="Optional comment for the document history")
|
||||
comment = forms.CharField(widget=forms.Textarea, required=False, help_text="Optional comment for the document history.")
|
||||
tags = forms.ModelMultipleChoiceField(queryset=DocTagName.objects.filter(used=True), widget=forms.CheckboxSelectMultiple, required=False)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
|
|
@ -270,24 +270,23 @@ def edit_material_presentations(request, name, acronym=None, date=None, seq=None
|
|||
if request.method == 'POST':
|
||||
form = MaterialVersionForm(request.POST,choices=choices)
|
||||
if form.is_valid():
|
||||
if request.POST.get("action", "") == "Save":
|
||||
new_selection = form.cleaned_data['version']
|
||||
if initial['version'] != new_selection:
|
||||
if initial['version'] == 'notpresented':
|
||||
doc.sessionpresentation_set.create(session=session,rev=new_selection)
|
||||
c = DocEvent(type="added_comment", doc=doc, by=request.user.person)
|
||||
c.desc = "Added version %s to session: %s" % (new_selection,session)
|
||||
c.save()
|
||||
elif new_selection == 'notpresented':
|
||||
doc.sessionpresentation_set.filter(session=session).delete()
|
||||
c = DocEvent(type="added_comment", doc=doc, by=request.user.person)
|
||||
c.desc = "Removed from session: %s" % (session)
|
||||
c.save()
|
||||
else:
|
||||
doc.sessionpresentation_set.filter(session=session).update(rev=new_selection)
|
||||
c = DocEvent(type="added_comment", doc=doc, by=request.user.person)
|
||||
c.desc = "Revision for session %s changed to %s" % (session,new_selection)
|
||||
c.save()
|
||||
new_selection = form.cleaned_data['version']
|
||||
if initial['version'] != new_selection:
|
||||
if initial['version'] == 'notpresented':
|
||||
doc.sessionpresentation_set.create(session=session,rev=new_selection)
|
||||
c = DocEvent(type="added_comment", doc=doc, by=request.user.person)
|
||||
c.desc = "Added version %s to session: %s" % (new_selection,session)
|
||||
c.save()
|
||||
elif new_selection == 'notpresented':
|
||||
doc.sessionpresentation_set.filter(session=session).delete()
|
||||
c = DocEvent(type="added_comment", doc=doc, by=request.user.person)
|
||||
c.desc = "Removed from session: %s" % (session)
|
||||
c.save()
|
||||
else:
|
||||
doc.sessionpresentation_set.filter(session=session).update(rev=new_selection)
|
||||
c = DocEvent(type="added_comment", doc=doc, by=request.user.person)
|
||||
c.desc = "Revision for session %s changed to %s" % (session,new_selection)
|
||||
c.save()
|
||||
return redirect('doc_view',name=doc.name)
|
||||
else:
|
||||
form = MaterialVersionForm(choices=choices,initial=initial)
|
||||
|
|
|
@ -37,7 +37,7 @@ from django.core.exceptions import ObjectDoesNotExist
|
|||
from django.shortcuts import render_to_response
|
||||
from django.db.models import Q
|
||||
from django.template import RequestContext
|
||||
from django.http import Http404, HttpResponseBadRequest
|
||||
from django.http import Http404, HttpResponseBadRequest, HttpResponse
|
||||
|
||||
import debug # pyflakes:ignore
|
||||
|
||||
|
@ -45,6 +45,7 @@ from ietf.community.models import CommunityList
|
|||
from ietf.doc.models import ( Document, DocAlias, State, RelatedDocument, DocEvent,
|
||||
LastCallDocEvent, TelechatDocEvent, IESG_SUBSTATE_TAGS )
|
||||
from ietf.doc.expire import expirable_draft
|
||||
from ietf.doc.fields import select2_id_doc_name_json
|
||||
from ietf.group.models import Group
|
||||
from ietf.idindex.index import active_drafts_index_by_group
|
||||
from ietf.ipr.models import IprDocAlias
|
||||
|
@ -52,6 +53,7 @@ from ietf.name.models import DocTagName, DocTypeName, StreamName
|
|||
from ietf.person.models import Person
|
||||
from ietf.utils.draft_search import normalize_draftname
|
||||
|
||||
|
||||
class SearchForm(forms.Form):
|
||||
name = forms.CharField(required=False)
|
||||
rfcs = forms.BooleanField(required=False, initial=True)
|
||||
|
@ -148,7 +150,7 @@ def fill_in_search_attributes(docs):
|
|||
for d in docs:
|
||||
if isinstance(d,DocAlias):
|
||||
d = d.document
|
||||
rel_this_doc = d.all_related_that_doc(['replaces','obs'])
|
||||
rel_this_doc = d.all_related_that_doc(['replaces','obs'])
|
||||
for rel in rel_this_doc:
|
||||
rel_id_camefrom.setdefault(rel.document.pk,[]).append(d.pk)
|
||||
rel_docs += [x.document for x in rel_this_doc]
|
||||
|
@ -240,7 +242,7 @@ def retrieve_search_results(form, all_types=False):
|
|||
"""Takes a validated SearchForm and return the results."""
|
||||
if not form.is_valid():
|
||||
raise ValueError("SearchForm doesn't validate: %s" % form.errors)
|
||||
|
||||
|
||||
query = form.cleaned_data
|
||||
|
||||
types=[];
|
||||
|
@ -282,7 +284,7 @@ def retrieve_search_results(form, all_types=False):
|
|||
if query["olddrafts"]:
|
||||
allowed_draft_states.extend(['repl', 'expired', 'auth-rm', 'ietf-rm'])
|
||||
|
||||
docs = docs.filter(Q(states__slug__in=allowed_draft_states) |
|
||||
docs = docs.filter(Q(states__slug__in=allowed_draft_states) |
|
||||
~Q(type__slug='draft')).distinct()
|
||||
|
||||
# radio choices
|
||||
|
@ -362,11 +364,10 @@ def retrieve_search_results(form, all_types=False):
|
|||
meta['by'] = query['by']
|
||||
meta['advanced'] = bool(query['by'] or len(meta['checked']))
|
||||
|
||||
meta['headers'] = [{'title': 'Add', 'key':'add'},
|
||||
{'title': 'Document', 'key':'document'},
|
||||
meta['headers'] = [{'title': 'Document', 'key':'document'},
|
||||
{'title': 'Title', 'key':'title'},
|
||||
{'title': 'Date', 'key':'date'},
|
||||
{'title': 'Status', 'key':'status', 'colspan':'2'},
|
||||
{'title': 'Status', 'key':'status'},
|
||||
{'title': 'IPR', 'key':'ipr'},
|
||||
{'title': 'AD / Shepherd', 'key':'ad'}]
|
||||
|
||||
|
@ -441,14 +442,14 @@ def ad_dashboard_group(doc):
|
|||
return '%s Internet-Draft' % doc.get_state('draft').name
|
||||
elif doc.type.slug=='conflrev':
|
||||
if doc.get_state_slug('conflrev') in ('appr-reqnopub-sent','appr-noprob-sent'):
|
||||
return 'Approved Conflict Review'
|
||||
return 'Approved Conflict Review'
|
||||
elif doc.get_state_slug('conflrev') in ('appr-reqnopub-pend','appr-noprob-pend','appr-reqnopub-pr','appr-noprob-pr'):
|
||||
return "%s Conflict Review" % State.objects.get(type__slug='draft-iesg',slug='approved')
|
||||
else:
|
||||
return '%s Conflict Review' % doc.get_state('conflrev')
|
||||
elif doc.type.slug=='statchg':
|
||||
if doc.get_state_slug('statchg') in ('appr-sent',):
|
||||
return 'Approved Status Change'
|
||||
return 'Approved Status Change'
|
||||
if doc.get_state_slug('statchg') in ('appr-pend','appr-pr'):
|
||||
return '%s Status Change' % State.objects.get(type__slug='draft-iesg',slug='approved')
|
||||
else:
|
||||
|
@ -462,7 +463,7 @@ def ad_dashboard_group(doc):
|
|||
return "Document"
|
||||
|
||||
def ad_dashboard_sort_key(doc):
|
||||
|
||||
|
||||
if doc.type.slug=='draft' and doc.get_state_slug('draft') == 'rfc':
|
||||
return "21%04d" % int(doc.rfc_number())
|
||||
if doc.type.slug=='statchg' and doc.get_state_slug('statchg') == 'appr-sent':
|
||||
|
@ -475,26 +476,26 @@ def ad_dashboard_sort_key(doc):
|
|||
seed = ad_dashboard_group(doc)
|
||||
|
||||
if doc.type.slug=='conflrev' and doc.get_state_slug('conflrev') == 'adrev':
|
||||
state = State.objects.get(type__slug='draft-iesg',slug='ad-eval')
|
||||
state = State.objects.get(type__slug='draft-iesg',slug='ad-eval')
|
||||
return "1%d%s" % (state.order,seed)
|
||||
|
||||
if doc.type.slug=='charter':
|
||||
if doc.get_state_slug('charter') in ('notrev','infrev'):
|
||||
return "100%s" % seed
|
||||
elif doc.get_state_slug('charter') == 'intrev':
|
||||
state = State.objects.get(type__slug='draft-iesg',slug='ad-eval')
|
||||
state = State.objects.get(type__slug='draft-iesg',slug='ad-eval')
|
||||
return "1%d%s" % (state.order,seed)
|
||||
elif doc.get_state_slug('charter') == 'extrev':
|
||||
state = State.objects.get(type__slug='draft-iesg',slug='lc')
|
||||
state = State.objects.get(type__slug='draft-iesg',slug='lc')
|
||||
return "1%d%s" % (state.order,seed)
|
||||
elif doc.get_state_slug('charter') == 'iesgrev':
|
||||
state = State.objects.get(type__slug='draft-iesg',slug='iesg-eva')
|
||||
state = State.objects.get(type__slug='draft-iesg',slug='iesg-eva')
|
||||
return "1%d%s" % (state.order,seed)
|
||||
|
||||
if doc.type.slug=='statchg' and doc.get_state_slug('statchg') == 'adrev':
|
||||
state = State.objects.get(type__slug='draft-iesg',slug='ad-eval')
|
||||
state = State.objects.get(type__slug='draft-iesg',slug='ad-eval')
|
||||
return "1%d%s" % (state.order,seed)
|
||||
|
||||
|
||||
if seed.startswith('Needs Shepherd'):
|
||||
return "100%s" % seed
|
||||
if seed.endswith(' Document'):
|
||||
|
@ -627,3 +628,28 @@ def index_active_drafts(request):
|
|||
groups = active_drafts_index_by_group()
|
||||
|
||||
return render_to_response("doc/index_active_drafts.html", { 'groups': groups }, context_instance=RequestContext(request))
|
||||
|
||||
def ajax_select2_search_docs(request, model_name, doc_type):
|
||||
if model_name == "docalias":
|
||||
model = DocAlias
|
||||
else:
|
||||
model = Document
|
||||
|
||||
q = [w.strip() for w in request.GET.get('q', '').split() if w.strip()]
|
||||
|
||||
if not q:
|
||||
objs = model.objects.none()
|
||||
else:
|
||||
qs = model.objects.all()
|
||||
|
||||
if model == Document:
|
||||
qs = qs.filter(type=doc_type)
|
||||
elif model == DocAlias:
|
||||
qs = qs.filter(document__type=doc_type)
|
||||
|
||||
for t in q:
|
||||
qs = qs.filter(name__icontains=t)
|
||||
|
||||
objs = qs.distinct().order_by("name")[:20]
|
||||
|
||||
return HttpResponse(select2_id_doc_name_json(objs), content_type='application/json')
|
||||
|
|
|
@ -25,7 +25,7 @@ from ietf.utils.textupload import get_cleaned_text_file_content
|
|||
|
||||
class ChangeStateForm(forms.Form):
|
||||
new_state = forms.ModelChoiceField(State.objects.filter(type="statchg", used=True), label="Status Change Evaluation State", empty_label=None, required=True)
|
||||
comment = forms.CharField(widget=forms.Textarea, help_text="Optional comment for the review history", required=False)
|
||||
comment = forms.CharField(widget=forms.Textarea, help_text="Optional comment for the review history.", required=False)
|
||||
|
||||
|
||||
@role_required("Area Director", "Secretariat")
|
||||
|
@ -109,8 +109,8 @@ def send_status_change_eval_email(request,doc):
|
|||
send_mail_preformatted(request,msg)
|
||||
|
||||
class UploadForm(forms.Form):
|
||||
content = forms.CharField(widget=forms.Textarea, label="Status change text", help_text="Edit the status change text", required=False)
|
||||
txt = forms.FileField(label=".txt format", help_text="Or upload a .txt file", required=False)
|
||||
content = forms.CharField(widget=forms.Textarea, label="Status change text", help_text="Edit the status change text.", required=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", "")
|
||||
|
@ -306,7 +306,7 @@ def default_approval_text(status_change,relateddoc):
|
|||
from django.forms.formsets import formset_factory
|
||||
|
||||
class AnnouncementForm(forms.Form):
|
||||
announcement_text = forms.CharField(widget=forms.Textarea, label="Status Change Announcement", help_text="Edit the announcement message", required=True)
|
||||
announcement_text = forms.CharField(widget=forms.Textarea, label="Status Change Announcement", help_text="Edit the announcement message.", required=True)
|
||||
label = None
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
@ -399,11 +399,11 @@ def clean_helper(form, formtype):
|
|||
elif k.startswith('statchg_relation_row'):
|
||||
status_fields[k[21:]]=v
|
||||
for key in rfc_fields:
|
||||
if rfc_fields[key]!="":
|
||||
if key in status_fields:
|
||||
new_relations[rfc_fields[key]]=status_fields[key]
|
||||
else:
|
||||
new_relations[rfc_fields[key]]=None
|
||||
if rfc_fields[key]!="":
|
||||
if key in status_fields:
|
||||
new_relations[rfc_fields[key]]=status_fields[key]
|
||||
else:
|
||||
new_relations[rfc_fields[key]]=None
|
||||
|
||||
form.relations = new_relations
|
||||
|
||||
|
@ -436,12 +436,12 @@ class EditStatusChangeForm(forms.Form):
|
|||
return clean_helper(self,EditStatusChangeForm)
|
||||
|
||||
class StartStatusChangeForm(forms.Form):
|
||||
document_name = forms.CharField(max_length=255, label="Document name", help_text="A descriptive name such as status-change-md2-to-historic is better than status-change-rfc1319", required=True)
|
||||
document_name = forms.CharField(max_length=255, label="Document name", help_text="A descriptive name such as status-change-md2-to-historic is better than status-change-rfc1319.", required=True)
|
||||
title = forms.CharField(max_length=255, label="Title", required=True)
|
||||
ad = forms.ModelChoiceField(Person.objects.filter(role__name="ad", role__group__state="active",role__group__type='area').order_by('name'),
|
||||
label="Shepherding AD", empty_label="(None)", required=True)
|
||||
create_in_state = forms.ModelChoiceField(State.objects.filter(type="statchg", slug__in=("needshep", "adrev")), empty_label=None, required=False)
|
||||
notify = forms.CharField(max_length=255, label="Notice emails", help_text="Separate email addresses with commas", required=False)
|
||||
notify = forms.CharField(max_length=255, label="Notice emails", help_text="Separate email addresses with commas.", required=False)
|
||||
telechat_date = forms.TypedChoiceField(coerce=lambda x: datetime.datetime.strptime(x, '%Y-%m-%d').date(), empty_value=None, required=False, widget=forms.Select(attrs={'onchange':'make_bold()'}))
|
||||
relations={}
|
||||
|
||||
|
@ -568,7 +568,7 @@ def edit_relations(request, name):
|
|||
|
||||
if request.method == 'POST':
|
||||
form = EditStatusChangeForm(request.POST)
|
||||
if 'Submit' in request.POST and form.is_valid():
|
||||
if form.is_valid():
|
||||
|
||||
old_relations={}
|
||||
for rel in status_change.relateddocument_set.filter(relationship__slug__in=STATUSCHANGE_RELATIONS):
|
||||
|
@ -590,9 +590,6 @@ def edit_relations(request, name):
|
|||
|
||||
return HttpResponseRedirect(status_change.get_absolute_url())
|
||||
|
||||
elif 'Cancel' in request.POST:
|
||||
return HttpResponseRedirect(status_change.get_absolute_url())
|
||||
|
||||
else:
|
||||
relations={}
|
||||
for rel in status_change.relateddocument_set.filter(relationship__slug__in=STATUSCHANGE_RELATIONS):
|
||||
|
|
|
@ -19,9 +19,10 @@ from ietf.group.models import ( Group, Role, GroupEvent, GroupHistory, GroupStat
|
|||
from ietf.group.utils import save_group_in_history, can_manage_group_type
|
||||
from ietf.group.utils import get_group_or_404
|
||||
from ietf.ietfauth.utils import has_role
|
||||
from ietf.person.fields import AutocompletedEmailsField
|
||||
from ietf.person.fields import SearchableEmailsField
|
||||
from ietf.person.models import Person, Email
|
||||
from ietf.group.mails import email_iesg_secretary_re_charter, email_iesg_secretary_personnel_change
|
||||
from ietf.utils.ordereddict import insert_after_in_ordered_dict
|
||||
|
||||
MAX_GROUP_DELEGATES = 3
|
||||
|
||||
|
@ -29,21 +30,20 @@ class GroupForm(forms.Form):
|
|||
name = forms.CharField(max_length=255, label="Name", required=True)
|
||||
acronym = forms.CharField(max_length=10, label="Acronym", required=True)
|
||||
state = forms.ModelChoiceField(GroupStateName.objects.all(), label="State", required=True)
|
||||
chairs = AutocompletedEmailsField(required=False, only_users=True)
|
||||
secretaries = AutocompletedEmailsField(required=False, only_users=True)
|
||||
techadv = AutocompletedEmailsField(label="Technical Advisors", required=False, only_users=True)
|
||||
delegates = AutocompletedEmailsField(required=False, only_users=True, max_entries=MAX_GROUP_DELEGATES,
|
||||
help_text=mark_safe("Chairs can delegate the authority to update the state of group documents - max %s persons at a given time" % MAX_GROUP_DELEGATES))
|
||||
chairs = SearchableEmailsField(label="Chairs", required=False, only_users=True)
|
||||
secretaries = SearchableEmailsField(label="Secretarias", required=False, only_users=True)
|
||||
techadv = SearchableEmailsField(label="Technical Advisors", required=False, only_users=True)
|
||||
delegates = SearchableEmailsField(label="Delegates", required=False, only_users=True, max_entries=MAX_GROUP_DELEGATES,
|
||||
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))
|
||||
ad = forms.ModelChoiceField(Person.objects.filter(role__name="ad", role__group__state="active", role__group__type='area').order_by('name'), label="Shepherding AD", empty_label="(None)", required=False)
|
||||
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)
|
||||
urls = forms.CharField(widget=forms.Textarea, label="Additional URLs", help_text="Format: http://site/path (Optional description). Separate multiple entries with newline.", required=False)
|
||||
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)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.group = kwargs.pop('group', None)
|
||||
self.confirmed = kwargs.pop('confirmed', False)
|
||||
self.group_type = kwargs.pop('group_type', False)
|
||||
|
||||
super(self.__class__, self).__init__(*args, **kwargs)
|
||||
|
@ -57,10 +57,8 @@ class GroupForm(forms.Form):
|
|||
if ad_pk and ad_pk not in [pk for pk, name in choices]:
|
||||
self.fields['ad'].choices = list(choices) + [("", "-------"), (ad_pk, Person.objects.get(pk=ad_pk).plain_name())]
|
||||
|
||||
self.confirm_msg = ""
|
||||
self.autoenable_confirm = False
|
||||
if self.group:
|
||||
self.fields['acronym'].widget.attrs['readonly'] = True
|
||||
self.fields['acronym'].widget.attrs['readonly'] = ""
|
||||
|
||||
if self.group_type == "rg":
|
||||
self.fields['ad'].widget = forms.HiddenInput()
|
||||
|
@ -71,9 +69,6 @@ class GroupForm(forms.Form):
|
|||
self.fields['parent'].label = "IETF Area"
|
||||
|
||||
def clean_acronym(self):
|
||||
self.confirm_msg = ""
|
||||
self.autoenable_confirm = False
|
||||
|
||||
# Changing the acronym of an already existing group will cause 404s all
|
||||
# over the place, loose history, and generally muck up a lot of
|
||||
# things, so we don't permit it
|
||||
|
@ -90,27 +85,41 @@ class GroupForm(forms.Form):
|
|||
if existing:
|
||||
existing = existing[0]
|
||||
|
||||
if existing and existing.type_id == self.group_type:
|
||||
if self.confirmed:
|
||||
return acronym # take over confirmed
|
||||
confirmed = self.data.get("confirm_acronym", False)
|
||||
|
||||
def insert_confirm_field(label, initial):
|
||||
# set required to false, we don't need it since we do the
|
||||
# validation of the field in here, and otherwise the
|
||||
# browser and Django may barf
|
||||
insert_after_in_ordered_dict(self.fields, "confirm_acronym", forms.BooleanField(label=label, required=False), after="acronym")
|
||||
# we can't set initial, it's ignored since the form is bound, instead mutate the data
|
||||
self.data = self.data.copy()
|
||||
self.data["confirm_acronym"] = initial
|
||||
|
||||
if existing and existing.type_id == self.group_type:
|
||||
if existing.state_id == "bof":
|
||||
self.confirm_msg = "Turn BoF %s into proposed %s and start chartering it" % (existing.acronym, existing.type.name)
|
||||
self.autoenable_confirm = True
|
||||
raise forms.ValidationError("Warning: Acronym used for an existing BoF (%s)." % existing.name)
|
||||
insert_confirm_field(label="Turn BoF %s into proposed %s and start chartering it" % (existing.acronym, existing.type.name), initial=True)
|
||||
if confirmed:
|
||||
return acronym
|
||||
else:
|
||||
raise forms.ValidationError("Warning: Acronym used for an existing BoF (%s)." % existing.name)
|
||||
else:
|
||||
self.confirm_msg = "Set state of %s %s to proposed and start chartering it" % (existing.acronym, existing.type.name)
|
||||
self.autoenable_confirm = False
|
||||
raise forms.ValidationError("Warning: Acronym used for an existing %s (%s, %s)." % (existing.type.name, existing.name, existing.state.name if existing.state else "unknown state"))
|
||||
insert_confirm_field(label="Set state of %s %s to proposed and start chartering it" % (existing.acronym, existing.type.name), initial=False)
|
||||
if confirmed:
|
||||
return acronym
|
||||
else:
|
||||
raise forms.ValidationError("Warning: Acronym used for an existing %s (%s, %s)." % (existing.type.name, existing.name, existing.state.name if existing.state else "unknown state"))
|
||||
|
||||
if existing:
|
||||
raise forms.ValidationError("Acronym used for an existing group (%s)." % existing.name)
|
||||
|
||||
old = GroupHistory.objects.filter(acronym__iexact=acronym, type__in=("wg", "rg"))
|
||||
if old and not self.confirmed:
|
||||
self.confirm_msg = "Confirm reusing acronym %s" % old[0].acronym
|
||||
self.autoenable_confirm = False
|
||||
raise forms.ValidationError("Warning: Acronym used for a historic group.")
|
||||
if old:
|
||||
insert_confirm_field(label="Confirm reusing acronym %s" % old[0].acronym, initial=False)
|
||||
if confirmed:
|
||||
return acronym
|
||||
else:
|
||||
raise forms.ValidationError("Warning: Acronym used for a historic group.")
|
||||
|
||||
return acronym
|
||||
|
||||
|
@ -149,7 +158,7 @@ def get_or_create_initial_charter(group, group_type):
|
|||
)
|
||||
charter.save()
|
||||
charter.set_state(State.objects.get(used=True, type="charter", slug="notrev"))
|
||||
|
||||
|
||||
# Create an alias as well
|
||||
DocAlias.objects.create(name=charter.name, document=charter)
|
||||
|
||||
|
@ -190,7 +199,7 @@ def edit(request, group_type=None, acronym=None, action="edit"):
|
|||
group_type = group.type_id
|
||||
|
||||
if request.method == 'POST':
|
||||
form = GroupForm(request.POST, group=group, confirmed=request.POST.get("confirmed", False), group_type=group_type)
|
||||
form = GroupForm(request.POST, group=group, group_type=group_type)
|
||||
if form.is_valid():
|
||||
clean = form.cleaned_data
|
||||
if new_group:
|
||||
|
@ -220,12 +229,12 @@ def edit(request, group_type=None, acronym=None, action="edit"):
|
|||
group.charter = get_or_create_initial_charter(group, group_type)
|
||||
|
||||
changes = []
|
||||
|
||||
|
||||
def desc(attr, new, old):
|
||||
entry = "%(attr)s changed to <b>%(new)s</b> from %(old)s"
|
||||
if new_group:
|
||||
entry = "%(attr)s changed to <b>%(new)s</b>"
|
||||
|
||||
|
||||
return entry % dict(attr=attr, new=new, old=old)
|
||||
|
||||
def diff(attr, name):
|
||||
|
|
|
@ -305,7 +305,7 @@ def construct_group_menu_context(request, group, selected, group_type, others):
|
|||
|
||||
if group.features.has_milestones:
|
||||
if group.state_id != "proposed" and (is_chair or can_manage):
|
||||
actions.append((u"Add or edit milestones", urlreverse("group_edit_milestones", kwargs=kwargs)))
|
||||
actions.append((u"Edit milestones", urlreverse("group_edit_milestones", kwargs=kwargs)))
|
||||
|
||||
if group.features.has_materials and can_manage_materials(request.user, group):
|
||||
actions.append((u"Upload material", urlreverse("ietf.doc.views_material.choose_material_type", kwargs=kwargs)))
|
||||
|
|
|
@ -2,102 +2,72 @@
|
|||
|
||||
import datetime
|
||||
import calendar
|
||||
import json
|
||||
|
||||
from django import forms
|
||||
from django.http import HttpResponse, HttpResponseForbidden, HttpResponseBadRequest, HttpResponseRedirect, Http404
|
||||
from django.http import HttpResponseForbidden, HttpResponseBadRequest, HttpResponseRedirect, Http404
|
||||
from django.shortcuts import render, redirect
|
||||
from django.contrib.auth.decorators import login_required
|
||||
|
||||
from ietf.doc.models import Document, DocEvent
|
||||
from ietf.doc.models import DocEvent
|
||||
from ietf.doc.utils import get_chartering_type
|
||||
from ietf.doc.fields import SearchableDocumentsField
|
||||
from ietf.group.models import GroupMilestone, MilestoneGroupEvent
|
||||
from ietf.group.utils import (save_milestone_in_history, can_manage_group_type, milestone_reviewer_for_group_type,
|
||||
get_group_or_404)
|
||||
from ietf.name.models import GroupMilestoneStateName
|
||||
from ietf.group.mails import email_milestones_changed
|
||||
|
||||
def json_doc_names(docs):
|
||||
return json.dumps([{"id": doc.pk, "name": doc.name } for doc in docs])
|
||||
|
||||
def parse_doc_names(s):
|
||||
return Document.objects.filter(pk__in=[x.strip() for x in s.split(",") if x.strip()], type="draft")
|
||||
from ietf.utils.fields import DatepickerDateField
|
||||
|
||||
class MilestoneForm(forms.Form):
|
||||
id = forms.IntegerField(required=True, widget=forms.HiddenInput)
|
||||
|
||||
desc = forms.CharField(max_length=500, label="Milestone:", required=True)
|
||||
due_month = forms.TypedChoiceField(choices=(), required=True, coerce=int)
|
||||
due_year = forms.TypedChoiceField(choices=(), required=True, coerce=int)
|
||||
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(max_length=50, required=False)
|
||||
resolved = forms.CharField(label="Resolved as", max_length=50, required=False)
|
||||
|
||||
delete = forms.BooleanField(required=False, initial=False)
|
||||
|
||||
docs = forms.CharField(max_length=10000, required=False)
|
||||
|
||||
accept = forms.ChoiceField(choices=(("accept", "Accept"), ("reject", "Reject and delete"), ("noaction", "No action")),
|
||||
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, *args, **kwargs):
|
||||
kwargs["label_suffix"] = ""
|
||||
|
||||
def __init__(self, needs_review, reviewer, *args, **kwargs):
|
||||
m = self.milestone = kwargs.pop("instance", None)
|
||||
|
||||
self.needs_review = kwargs.pop("needs_review", False)
|
||||
can_review = not self.needs_review
|
||||
can_review = not needs_review
|
||||
|
||||
if m:
|
||||
self.needs_review = m.state_id == "review"
|
||||
needs_review = m.state_id == "review"
|
||||
|
||||
if not "initial" in kwargs:
|
||||
kwargs["initial"] = {}
|
||||
kwargs["initial"].update(dict(id=m.pk,
|
||||
desc=m.desc,
|
||||
due_month=m.due.month,
|
||||
due_year=m.due.year,
|
||||
due=m.due,
|
||||
resolved_checkbox=bool(m.resolved),
|
||||
resolved=m.resolved,
|
||||
docs=",".join(m.docs.values_list("pk", flat=True)),
|
||||
docs=m.docs.all(),
|
||||
delete=False,
|
||||
accept="noaction" if can_review and self.needs_review else None,
|
||||
review="noaction" if can_review and needs_review else "",
|
||||
))
|
||||
|
||||
kwargs["prefix"] = "m%s" % m.pk
|
||||
|
||||
super(MilestoneForm, self).__init__(*args, **kwargs)
|
||||
|
||||
# set choices for due date
|
||||
this_year = datetime.date.today().year
|
||||
self.fields["resolved"].widget.attrs["data-default"] = "Done"
|
||||
|
||||
self.fields["due_month"].choices = [(month, datetime.date(this_year, month, 1).strftime("%B")) for month in range(1, 13)]
|
||||
if needs_review and self.milestone and self.milestone.state_id != "review":
|
||||
self.fields["desc"].widget.attrs["readonly"] = True
|
||||
|
||||
years = [ y for y in range(this_year, this_year + 10)]
|
||||
self.changed = False
|
||||
|
||||
initial = self.initial.get("due_year")
|
||||
if initial and initial not in years:
|
||||
years.insert(0, initial)
|
||||
if not (needs_review and can_review):
|
||||
self.fields["review"].widget = forms.HiddenInput()
|
||||
|
||||
self.fields["due_year"].choices = zip(years, map(str, years))
|
||||
|
||||
# figure out what to prepopulate many-to-many field with
|
||||
pre = ""
|
||||
if not self.is_bound:
|
||||
pre = self.initial.get("docs", "")
|
||||
else:
|
||||
pre = self["docs"].data or ""
|
||||
|
||||
# this is ugly, but putting it on self["docs"] is buggy with a
|
||||
# bound/unbound form in Django 1.2
|
||||
self.docs_names = parse_doc_names(pre)
|
||||
self.docs_prepopulate = json_doc_names(self.docs_names)
|
||||
|
||||
# calculate whether we've changed
|
||||
self.changed = self.is_bound and (not self.milestone or any(unicode(self[f].data) != unicode(self.initial[f]) for f in self.fields.iterkeys()))
|
||||
|
||||
def clean_docs(self):
|
||||
s = self.cleaned_data["docs"]
|
||||
return Document.objects.filter(pk__in=[x.strip() for x in s.split(",") if x.strip()], type="draft")
|
||||
self.needs_review = needs_review
|
||||
|
||||
def clean_resolved(self):
|
||||
r = self.cleaned_data["resolved"].strip()
|
||||
|
@ -137,14 +107,17 @@ def edit_milestones(request, acronym, group_type=None, milestone_set="current"):
|
|||
title = "Edit charter milestones for %s %s" % (group.acronym, group.type.name)
|
||||
milestones = group.groupmilestone_set.filter(state="charter")
|
||||
|
||||
reviewer = milestone_reviewer_for_group_type(group_type)
|
||||
|
||||
forms = []
|
||||
|
||||
milestones_dict = dict((str(m.id), m) for m in milestones)
|
||||
|
||||
def due_month_year_to_date(c):
|
||||
y = c["due_year"]
|
||||
m = c["due_month"]
|
||||
return datetime.date(y, m, calendar.monthrange(y, m)[1])
|
||||
y = c["due"].year
|
||||
m = c["due"].month
|
||||
first_day, last_day = calendar.monthrange(y, m)
|
||||
return datetime.date(y, m, last_day)
|
||||
|
||||
def set_attributes_from_form(f, m):
|
||||
c = f.cleaned_data
|
||||
|
@ -156,10 +129,24 @@ def edit_milestones(request, acronym, group_type=None, milestone_set="current"):
|
|||
m.state = GroupMilestoneStateName.objects.get(slug="active")
|
||||
elif milestone_set == "charter":
|
||||
m.state = GroupMilestoneStateName.objects.get(slug="charter")
|
||||
|
||||
m.desc = c["desc"]
|
||||
m.due = due_month_year_to_date(c)
|
||||
m.resolved = c["resolved"]
|
||||
|
||||
def milestone_changed(f, m):
|
||||
# we assume that validation has run
|
||||
if not m or not f.is_valid():
|
||||
return True
|
||||
|
||||
c = f.cleaned_data
|
||||
return (c["desc"] != m.desc or
|
||||
due_month_year_to_date(c) != m.due or
|
||||
c["resolved"] != m.resolved or
|
||||
set(c["docs"]) != set(m.docs.all()) or
|
||||
c.get("review") in ("accept", "reject")
|
||||
)
|
||||
|
||||
def save_milestone_form(f):
|
||||
c = f.cleaned_data
|
||||
|
||||
|
@ -183,14 +170,14 @@ def edit_milestones(request, acronym, group_type=None, milestone_set="current"):
|
|||
|
||||
changes = ['Changed %s' % named_milestone]
|
||||
|
||||
if m.state_id == "review" and not needs_review and c["accept"] != "noaction":
|
||||
if m.state_id == "review" and not needs_review and c["review"] != "noaction":
|
||||
if not history:
|
||||
history = save_milestone_in_history(m)
|
||||
|
||||
if c["accept"] == "accept":
|
||||
if c["review"] == "accept":
|
||||
m.state_id = "active"
|
||||
changes.append("set state to active from review, accepting new milestone")
|
||||
elif c["accept"] == "reject":
|
||||
elif c["review"] == "reject":
|
||||
m.state_id = "deleted"
|
||||
changes.append("set state to deleted from review, rejecting new milestone")
|
||||
|
||||
|
@ -260,8 +247,6 @@ def edit_milestones(request, acronym, group_type=None, milestone_set="current"):
|
|||
elif m.state_id == "review":
|
||||
return 'Added %s for review, due %s' % (named_milestone, m.due.strftime("%B %Y"))
|
||||
|
||||
finished_milestone_text = "Done"
|
||||
|
||||
form_errors = False
|
||||
|
||||
if request.method == 'POST':
|
||||
|
@ -272,22 +257,23 @@ def edit_milestones(request, acronym, group_type=None, milestone_set="current"):
|
|||
|
||||
# new milestones have non-existing ids so instance end up as None
|
||||
instance = milestones_dict.get(request.POST.get(prefix + "-id", ""), None)
|
||||
f = MilestoneForm(request.POST, prefix=prefix, instance=instance,
|
||||
needs_review=needs_review)
|
||||
f = MilestoneForm(needs_review, reviewer, request.POST, prefix=prefix, instance=instance)
|
||||
forms.append(f)
|
||||
|
||||
form_errors = form_errors or not f.is_valid()
|
||||
|
||||
f.changed = milestone_changed(f, f.milestone)
|
||||
if f.is_valid() and f.cleaned_data.get("review") in ("accept", "reject"):
|
||||
f.needs_review = False
|
||||
|
||||
action = request.POST.get("action", "review")
|
||||
if action == "review":
|
||||
for f in forms:
|
||||
if not f.is_valid():
|
||||
continue
|
||||
|
||||
# let's fill in the form milestone so we can output it in the template
|
||||
if not f.milestone:
|
||||
f.milestone = GroupMilestone()
|
||||
set_attributes_from_form(f, f.milestone)
|
||||
if f.is_valid():
|
||||
# let's fill in the form milestone so we can output it in the template
|
||||
if not f.milestone:
|
||||
f.milestone = GroupMilestone()
|
||||
set_attributes_from_form(f, f.milestone)
|
||||
elif action == "save" and not form_errors:
|
||||
changes = []
|
||||
for f in forms:
|
||||
|
@ -314,11 +300,11 @@ def edit_milestones(request, acronym, group_type=None, milestone_set="current"):
|
|||
return HttpResponseRedirect(group.about_url())
|
||||
else:
|
||||
for m in milestones:
|
||||
forms.append(MilestoneForm(instance=m, needs_review=needs_review))
|
||||
forms.append(MilestoneForm(needs_review, reviewer, instance=m))
|
||||
|
||||
can_reset = milestone_set == "charter" and get_chartering_type(group.charter) == "rechartering"
|
||||
|
||||
empty_form = MilestoneForm(needs_review=needs_review)
|
||||
empty_form = MilestoneForm(needs_review, reviewer)
|
||||
|
||||
forms.sort(key=lambda f: f.milestone.due if f.milestone else datetime.date.max)
|
||||
|
||||
|
@ -329,9 +315,8 @@ def edit_milestones(request, acronym, group_type=None, milestone_set="current"):
|
|||
form_errors=form_errors,
|
||||
empty_form=empty_form,
|
||||
milestone_set=milestone_set,
|
||||
finished_milestone_text=finished_milestone_text,
|
||||
needs_review=needs_review,
|
||||
reviewer=milestone_reviewer_for_group_type(group_type),
|
||||
reviewer=reviewer,
|
||||
can_reset=can_reset))
|
||||
|
||||
@login_required
|
||||
|
@ -391,8 +376,3 @@ def reset_charter_milestones(request, group_type, acronym):
|
|||
charter_milestones=charter_milestones,
|
||||
current_milestones=current_milestones,
|
||||
))
|
||||
|
||||
|
||||
def ajax_search_docs(request, group_type, acronym):
|
||||
docs = Document.objects.filter(name__icontains=request.GET.get('q',''), type="draft").order_by('name').distinct()[:20]
|
||||
return HttpResponse(json_doc_names(docs), content_type='application/json')
|
||||
|
|
|
@ -24,8 +24,8 @@ class GroupInfo(models.Model):
|
|||
list_archive = models.CharField(max_length=255, blank=True)
|
||||
comments = models.TextField(blank=True)
|
||||
|
||||
unused_states = models.ManyToManyField('doc.State', help_text="Document states that have been disabled for the group", blank=True)
|
||||
unused_tags = models.ManyToManyField(DocTagName, help_text="Document tags that have been disabled for the group", blank=True)
|
||||
unused_states = models.ManyToManyField('doc.State', help_text="Document states that have been disabled for the group.", blank=True)
|
||||
unused_tags = models.ManyToManyField(DocTagName, help_text="Document tags that have been disabled for the group.", blank=True)
|
||||
|
||||
def __unicode__(self):
|
||||
return self.name
|
||||
|
@ -170,7 +170,7 @@ class GroupMilestoneInfo(models.Model):
|
|||
state = models.ForeignKey(GroupMilestoneStateName)
|
||||
desc = models.CharField(verbose_name="Description", max_length=500)
|
||||
due = models.DateField()
|
||||
resolved = models.CharField(max_length=50, blank=True, help_text="Explanation of why milestone is resolved (usually \"Done\"), or empty if still due")
|
||||
resolved = models.CharField(max_length=50, blank=True, help_text="Explanation of why milestone is resolved (usually \"Done\"), or empty if still due.")
|
||||
|
||||
docs = models.ManyToManyField('doc.Document', blank=True)
|
||||
|
||||
|
@ -230,7 +230,7 @@ class Role(models.Model):
|
|||
name = models.ForeignKey(RoleName)
|
||||
group = models.ForeignKey(Group)
|
||||
person = models.ForeignKey(Person)
|
||||
email = models.ForeignKey(Email, help_text="Email address used by person for this role")
|
||||
email = models.ForeignKey(Email, help_text="Email address used by person for this role.")
|
||||
def __unicode__(self):
|
||||
return u"%s is %s in %s" % (self.person.plain_name(), self.name.name, self.group.acronym or self.group.name)
|
||||
|
||||
|
@ -245,7 +245,7 @@ class RoleHistory(models.Model):
|
|||
name = models.ForeignKey(RoleName)
|
||||
group = models.ForeignKey(GroupHistory)
|
||||
person = models.ForeignKey(Person)
|
||||
email = models.ForeignKey(Email, help_text="Email address used by person for this role")
|
||||
email = models.ForeignKey(Email, help_text="Email address used by person for this role.")
|
||||
def __unicode__(self):
|
||||
return u"%s is %s in %s" % (self.person.plain_name(), self.name.name, self.group.acronym)
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import os
|
||||
import shutil
|
||||
import calendar
|
||||
import json
|
||||
import datetime
|
||||
|
||||
from pyquery import PyQuery
|
||||
|
@ -99,7 +98,7 @@ class GroupPagesTests(TestCase):
|
|||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertEqual(len(q('table.ietf-doctable td.acronym a:contains("%s")' % group.acronym)), 1)
|
||||
self.assertEqual(len(q('.content-wrapper a:contains("%s")' % group.acronym)), 1)
|
||||
|
||||
def test_concluded_groups(self):
|
||||
draft = make_test_data()
|
||||
|
@ -111,7 +110,7 @@ class GroupPagesTests(TestCase):
|
|||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertEqual(len(q('table.concluded-groups a:contains("%s")' % group.acronym)), 1)
|
||||
self.assertEqual(len(q('.content-wrapper a:contains("%s")' % group.acronym)), 1)
|
||||
|
||||
def test_bofs(self):
|
||||
draft = make_test_data()
|
||||
|
@ -123,7 +122,7 @@ class GroupPagesTests(TestCase):
|
|||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertEqual(len(q('table.ietf-doctable td.acronym a:contains("%s")' % group.acronym)), 1)
|
||||
self.assertEqual(len(q('.content-wrapper a:contains("%s")' % group.acronym)), 1)
|
||||
|
||||
def test_group_documents(self):
|
||||
draft = make_test_data()
|
||||
|
@ -302,7 +301,7 @@ class GroupEditTests(TestCase):
|
|||
r = self.client.post(url, dict(acronym="foobarbaz")) # No name
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertTrue(len(q('form ul.errorlist')) > 0)
|
||||
self.assertTrue(len(q('form .has-error')) > 0)
|
||||
self.assertEqual(len(Group.objects.filter(type="wg")), num_wgs)
|
||||
|
||||
# acronym contains non-alphanumeric
|
||||
|
@ -330,7 +329,7 @@ class GroupEditTests(TestCase):
|
|||
self.assertEqual(group.charter.name, "charter-ietf-testwg")
|
||||
self.assertEqual(group.charter.rev, "00-00")
|
||||
|
||||
def test_create_based_on_existing(self):
|
||||
def test_create_based_on_existing_bof(self):
|
||||
make_test_data()
|
||||
|
||||
url = urlreverse('group_create', kwargs=dict(group_type="wg"))
|
||||
|
@ -342,8 +341,8 @@ class GroupEditTests(TestCase):
|
|||
r = self.client.post(url, dict(name="Test", acronym=group.parent.acronym))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertTrue(len(q('form ul.errorlist')) > 0)
|
||||
self.assertEqual(len(q('form input[name="confirmed"]')), 0) # can't confirm us out of this
|
||||
self.assertTrue(len(q('form .has-error')) > 0)
|
||||
self.assertEqual(len(q('form input[name="confirm_acronym"]')), 0) # can't confirm us out of this
|
||||
|
||||
# try elevating BoF to WG
|
||||
group.state_id = "bof"
|
||||
|
@ -352,14 +351,14 @@ class GroupEditTests(TestCase):
|
|||
r = self.client.post(url, dict(name="Test", acronym=group.acronym))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertTrue(len(q('form ul.errorlist')) > 0)
|
||||
self.assertEqual(len(q('form input[name="confirmed"]')), 1)
|
||||
self.assertTrue(len(q('form .has-error')) > 0)
|
||||
self.assertEqual(len(q('form input[name="confirm_acronym"]')), 1)
|
||||
|
||||
self.assertEqual(Group.objects.get(acronym=group.acronym).state_id, "bof")
|
||||
|
||||
# confirm elevation
|
||||
state = GroupStateName.objects.get(slug="proposed")
|
||||
r = self.client.post(url, dict(name="Test", acronym=group.acronym, confirmed="1",state=state.pk))
|
||||
r = self.client.post(url, dict(name="Test", acronym=group.acronym, confirm_acronym="1", state=state.pk))
|
||||
self.assertEqual(r.status_code, 302)
|
||||
self.assertEqual(Group.objects.get(acronym=group.acronym).state_id, "proposed")
|
||||
self.assertEqual(Group.objects.get(acronym=group.acronym).name, "Test")
|
||||
|
@ -383,7 +382,7 @@ class GroupEditTests(TestCase):
|
|||
r = self.client.post(url, dict(acronym="collide"))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertTrue(len(q('form ul.errorlist')) > 0)
|
||||
self.assertTrue(len(q('form .has-error')) > 0)
|
||||
|
||||
# create old acronym
|
||||
group.acronym = "oldmars"
|
||||
|
@ -396,7 +395,7 @@ class GroupEditTests(TestCase):
|
|||
r = self.client.post(url, dict(acronym="oldmars"))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertTrue(len(q('form ul.errorlist')) > 0)
|
||||
self.assertTrue(len(q('form .has-error')) > 0)
|
||||
|
||||
# edit info
|
||||
with open(os.path.join(self.charter_dir, "%s-%s.txt" % (group.charter.canonical_name(), group.charter.rev)), "w") as f:
|
||||
|
@ -453,7 +452,7 @@ class GroupEditTests(TestCase):
|
|||
r = self.client.post(url, dict(instructions="")) # No instructions
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertTrue(len(q('form ul.errorlist')) > 0)
|
||||
self.assertTrue(len(q('form .has-error')) > 0)
|
||||
|
||||
# request conclusion
|
||||
mailbox_before = len(outbox)
|
||||
|
@ -530,15 +529,14 @@ class MilestoneTests(TestCase):
|
|||
r = self.client.post(url, { 'prefix': "m-1",
|
||||
'm-1-id': "-1",
|
||||
'm-1-desc': "", # no description
|
||||
'm-1-due_month': str(due.month),
|
||||
'm-1-due_year': str(due.year),
|
||||
'm-1-due': due.strftime("%B %Y"),
|
||||
'm-1-resolved': "",
|
||||
'm-1-docs': ",".join(docs),
|
||||
'action': "save",
|
||||
})
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertTrue(len(q('form ul.errorlist')) > 0)
|
||||
self.assertTrue(len(q('form .has-error')) > 0)
|
||||
self.assertEqual(GroupMilestone.objects.count(), milestones_before)
|
||||
|
||||
# add
|
||||
|
@ -546,8 +544,7 @@ class MilestoneTests(TestCase):
|
|||
r = self.client.post(url, { 'prefix': "m-1",
|
||||
'm-1-id': "-1",
|
||||
'm-1-desc': "Test 3",
|
||||
'm-1-due_month': str(due.month),
|
||||
'm-1-due_year': str(due.year),
|
||||
'm-1-due': due.strftime("%B %Y"),
|
||||
'm-1-resolved': "",
|
||||
'm-1-docs': ",".join(docs),
|
||||
'action': "save",
|
||||
|
@ -584,8 +581,7 @@ class MilestoneTests(TestCase):
|
|||
r = self.client.post(url, { 'prefix': "m-1",
|
||||
'm-1-id': -1,
|
||||
'm-1-desc': "Test 3",
|
||||
'm-1-due_month': str(due.month),
|
||||
'm-1-due_year': str(due.year),
|
||||
'm-1-due': due.strftime("%B %Y"),
|
||||
'm-1-resolved': "",
|
||||
'm-1-docs': "",
|
||||
'action': "save",
|
||||
|
@ -619,11 +615,10 @@ class MilestoneTests(TestCase):
|
|||
r = self.client.post(url, { 'prefix': "m1",
|
||||
'm1-id': m1.id,
|
||||
'm1-desc': m1.desc,
|
||||
'm1-due_month': str(m1.due.month),
|
||||
'm1-due_year': str(m1.due.year),
|
||||
'm1-due': m1.due.strftime("%B %Y"),
|
||||
'm1-resolved': m1.resolved,
|
||||
'm1-docs': ",".join(m1.docs.values_list("name", flat=True)),
|
||||
'm1-accept': "accept",
|
||||
'm1-review': "accept",
|
||||
'action': "save",
|
||||
})
|
||||
self.assertEqual(r.status_code, 302)
|
||||
|
@ -646,8 +641,7 @@ class MilestoneTests(TestCase):
|
|||
r = self.client.post(url, { 'prefix': "m1",
|
||||
'm1-id': m1.id,
|
||||
'm1-desc': m1.desc,
|
||||
'm1-due_month': str(m1.due.month),
|
||||
'm1-due_year': str(m1.due.year),
|
||||
'm1-due': m1.due.strftime("%B %Y"),
|
||||
'm1-resolved': "",
|
||||
'm1-docs': ",".join(m1.docs.values_list("name", flat=True)),
|
||||
'm1-delete': "checked",
|
||||
|
@ -677,15 +671,14 @@ class MilestoneTests(TestCase):
|
|||
r = self.client.post(url, { 'prefix': "m1",
|
||||
'm1-id': m1.id,
|
||||
'm1-desc': "", # no description
|
||||
'm1-due_month': str(due.month),
|
||||
'm1-due_year': str(due.year),
|
||||
'm1-due': due.strftime("%B %Y"),
|
||||
'm1-resolved': "",
|
||||
'm1-docs': ",".join(docs),
|
||||
'action': "save",
|
||||
})
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertTrue(len(q('form ul.errorlist')) > 0)
|
||||
self.assertTrue(len(q('form .has-error')) > 0)
|
||||
m = GroupMilestone.objects.get(pk=m1.pk)
|
||||
self.assertEqual(GroupMilestone.objects.count(), milestones_before)
|
||||
self.assertEqual(m.due, m1.due)
|
||||
|
@ -695,8 +688,7 @@ class MilestoneTests(TestCase):
|
|||
r = self.client.post(url, { 'prefix': "m1",
|
||||
'm1-id': m1.id,
|
||||
'm1-desc': "Test 2 - changed",
|
||||
'm1-due_month': str(due.month),
|
||||
'm1-due_year': str(due.year),
|
||||
'm1-due': due.strftime("%B %Y"),
|
||||
'm1-resolved': "Done",
|
||||
'm1-resolved_checkbox': "checked",
|
||||
'm1-docs': ",".join(docs),
|
||||
|
@ -873,15 +865,6 @@ class MilestoneTests(TestCase):
|
|||
self.assertTrue(m1.desc in unicode(outbox[-1]))
|
||||
self.assertTrue(m2.desc in unicode(outbox[-1]))
|
||||
|
||||
def test_ajax_search_docs(self):
|
||||
draft = make_test_data()
|
||||
|
||||
r = self.client.get(urlreverse("group_ajax_search_docs", kwargs=dict(group_type=draft.group.type_id, acronym=draft.group.acronym)),
|
||||
dict(q=draft.name))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
data = json.loads(r.content)
|
||||
self.assertTrue(data[0]["id"], draft.name)
|
||||
|
||||
class CustomizeWorkflowTests(TestCase):
|
||||
def test_customize_workflow(self):
|
||||
make_test_data()
|
||||
|
|
|
@ -22,7 +22,6 @@ urlpatterns = patterns('',
|
|||
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/milestones/$', 'ietf.group.milestones.edit_milestones', {'milestone_set': "current"}, "group_edit_milestones"),
|
||||
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/milestones/charter/$', 'ietf.group.milestones.edit_milestones', {'milestone_set': "charter"}, "group_edit_charter_milestones"),
|
||||
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/milestones/charter/reset/$', 'ietf.group.milestones.reset_charter_milestones', None, "group_reset_charter_milestones"),
|
||||
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/ajax/searchdocs/$', 'ietf.group.milestones.ajax_search_docs', None, "group_ajax_search_docs"),
|
||||
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/workflow/$', 'ietf.group.edit.customize_workflow'),
|
||||
|
||||
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/about/(?P<group_type>.)?$', 'ietf.group.info.group_about', None, 'group_about'),
|
||||
|
|
|
@ -31,6 +31,5 @@ urlpatterns = patterns('',
|
|||
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/milestones/$', milestones.edit_milestones, {'milestone_set': "current"}, "group_edit_milestones"),
|
||||
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/milestones/charter/$', milestones.edit_milestones, {'milestone_set': "charter"}, "group_edit_charter_milestones"),
|
||||
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/milestones/charter/reset/$', milestones.reset_charter_milestones, None, "group_reset_charter_milestones"),
|
||||
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/ajax/searchdocs/$', milestones.ajax_search_docs, None, "group_ajax_search_docs"),
|
||||
(r'^(?P<acronym>[a-zA-Z0-9-._]+)/workflow/$', edit.customize_workflow),
|
||||
)
|
||||
|
|
|
@ -10,7 +10,7 @@ from ietf.group.models import Group, GroupEvent, Role
|
|||
from ietf.group.utils import save_group_in_history
|
||||
from ietf.ietfauth.utils import has_role
|
||||
from ietf.name.models import StreamName
|
||||
from ietf.person.fields import AutocompletedEmailsField
|
||||
from ietf.person.fields import SearchableEmailsField
|
||||
from ietf.person.models import Email
|
||||
|
||||
import debug # pyflakes:ignore
|
||||
|
@ -24,14 +24,16 @@ def stream_documents(request, acronym):
|
|||
streams = [ s.slug for s in StreamName.objects.all().exclude(slug__in=['ietf', 'legacy']) ]
|
||||
if not acronym in streams:
|
||||
raise Http404("No such stream: %s" % acronym)
|
||||
group = get_object_or_404(Group, acronym=acronym)
|
||||
editable = has_role(request.user, "Secretariat") or group.has_role(request.user, "chair")
|
||||
stream = StreamName.objects.get(slug=acronym)
|
||||
form = SearchForm({'by':'stream', 'stream':acronym,
|
||||
'rfcs':'on', 'activedrafts':'on'})
|
||||
docs, meta = retrieve_search_results(form)
|
||||
return render_to_response('group/stream_documents.html', {'stream':stream, 'docs':docs, 'meta':meta }, context_instance=RequestContext(request))
|
||||
return render_to_response('group/stream_documents.html', {'stream':stream, 'docs':docs, 'meta':meta, 'editable':editable }, context_instance=RequestContext(request))
|
||||
|
||||
class StreamEditForm(forms.Form):
|
||||
delegates = AutocompletedEmailsField(required=False, only_users=True)
|
||||
delegates = SearchableEmailsField(required=False, only_users=True)
|
||||
|
||||
def stream_edit(request, acronym):
|
||||
group = get_object_or_404(Group, acronym=acronym)
|
||||
|
@ -62,7 +64,7 @@ def stream_edit(request, acronym):
|
|||
for e in new:
|
||||
Role.objects.get_or_create(name_id=slug, email=e, group=group, person=e.person)
|
||||
|
||||
return redirect("ietf.group.views.streams")
|
||||
return redirect("ietf.group.views_stream.streams")
|
||||
else:
|
||||
form = StreamEditForm(initial=dict(delegates=Email.objects.filter(role__group=group, role__name="delegate")))
|
||||
|
||||
|
@ -72,4 +74,4 @@ def stream_edit(request, acronym):
|
|||
'form': form,
|
||||
},
|
||||
context_instance=RequestContext(request))
|
||||
|
||||
|
||||
|
|
|
@ -85,50 +85,50 @@ def get_doc_section(doc):
|
|||
def agenda_sections():
|
||||
return OrderedDict([
|
||||
('1', {'title':"Administrivia"}),
|
||||
('1.1', {'title':"Roll Call"}),
|
||||
('1.2', {'title':"Bash the Agenda"}),
|
||||
('1.3', {'title':"Approval of the Minutes of Past Telechats"}),
|
||||
('1.4', {'title':"List of Remaining Action Items from Last Telechat"}),
|
||||
('2', {'title':"Protocol Actions"}),
|
||||
('2.1', {'title':"WG Submissions"}),
|
||||
('2.1.1', {'title':"New Items", 'docs': []}),
|
||||
('2.1.2', {'title':"Returning Items", 'docs':[]}),
|
||||
('2.1.3', {'title':"For Action", 'docs':[]}),
|
||||
('2.2', {'title':"Individual Submissions"}),
|
||||
('2.2.1', {'title':"New Items", 'docs':[]}),
|
||||
('2.2.2', {'title':"Returning Items", 'docs':[]}),
|
||||
('2.2.3', {'title':"For Action", 'docs':[]}),
|
||||
('2.3', {'title':"Status Changes"}),
|
||||
('2.3.1', {'title':"New Items", 'docs':[]}),
|
||||
('2.3.2', {'title':"Returning Items", 'docs':[]}),
|
||||
('2.3.3', {'title':"For Action", 'docs':[]}),
|
||||
('3', {'title':"Document Actions"}),
|
||||
('3.1', {'title':"WG Submissions"}),
|
||||
('3.1.1', {'title':"New Items", 'docs':[]}),
|
||||
('3.1.2', {'title':"Returning Items", 'docs':[]}),
|
||||
('3.1.3', {'title':"For Action", 'docs':[]}),
|
||||
('3.2', {'title':"Individual Submissions Via AD"}),
|
||||
('3.2.1', {'title':"New Items", 'docs':[]}),
|
||||
('3.2.2', {'title':"Returning Items", 'docs':[]}),
|
||||
('3.2.3', {'title':"For Action", 'docs':[]}),
|
||||
('3.3', {'title':"Status Changes"}),
|
||||
('3.3.1', {'title':"New Items", 'docs':[]}),
|
||||
('3.3.2', {'title':"Returning Items", 'docs':[]}),
|
||||
('3.3.3', {'title':"For Action", 'docs':[]}),
|
||||
('3.4', {'title':"IRTF and Independent Submission Stream Documents"}),
|
||||
('3.4.1', {'title':"New Items", 'docs':[]}),
|
||||
('3.4.2', {'title':"Returning Items", 'docs':[]}),
|
||||
('3.4.3', {'title':"For Action", 'docs':[]}),
|
||||
('4', {'title':"Working Group Actions"}),
|
||||
('4.1', {'title':"WG Creation"}),
|
||||
('4.1.1', {'title':"Proposed for IETF Review", 'docs':[]}),
|
||||
('4.1.2', {'title':"Proposed for Approval", 'docs':[]}),
|
||||
('4.2', {'title':"WG Rechartering"}),
|
||||
('4.2.1', {'title':"Under Evaluation for IETF Review", 'docs':[]}),
|
||||
('4.2.2', {'title':"Proposed for Approval", 'docs':[]}),
|
||||
('5', {'title':"IAB News We Can Use"}),
|
||||
('6', {'title':"Management Issues"}),
|
||||
('7', {'title':"Working Group News"}),
|
||||
('1.1', {'title':"Roll call"}),
|
||||
('1.2', {'title':"Bash the agenda"}),
|
||||
('1.3', {'title':"Approval of the minutes of past telechats"}),
|
||||
('1.4', {'title':"List of remaining action items from last telechat"}),
|
||||
('2', {'title':"Protocol actions"}),
|
||||
('2.1', {'title':"WG submissions"}),
|
||||
('2.1.1', {'title':"New items", 'docs': []}),
|
||||
('2.1.2', {'title':"Returning items", 'docs':[]}),
|
||||
('2.1.3', {'title':"For action", 'docs':[]}),
|
||||
('2.2', {'title':"Individual submissions"}),
|
||||
('2.2.1', {'title':"New items", 'docs':[]}),
|
||||
('2.2.2', {'title':"Returning items", 'docs':[]}),
|
||||
('2.2.3', {'title':"For action", 'docs':[]}),
|
||||
('2.3', {'title':"Status changes"}),
|
||||
('2.3.1', {'title':"New items", 'docs':[]}),
|
||||
('2.3.2', {'title':"Returning items", 'docs':[]}),
|
||||
('2.3.3', {'title':"For action", 'docs':[]}),
|
||||
('3', {'title':"Document actions"}),
|
||||
('3.1', {'title':"WG submissions"}),
|
||||
('3.1.1', {'title':"New items", 'docs':[]}),
|
||||
('3.1.2', {'title':"Returning items", 'docs':[]}),
|
||||
('3.1.3', {'title':"For action", 'docs':[]}),
|
||||
('3.2', {'title':"Individual submissions via AD"}),
|
||||
('3.2.1', {'title':"New items", 'docs':[]}),
|
||||
('3.2.2', {'title':"Returning items", 'docs':[]}),
|
||||
('3.2.3', {'title':"For action", 'docs':[]}),
|
||||
('3.3', {'title':"Status changes"}),
|
||||
('3.3.1', {'title':"New items", 'docs':[]}),
|
||||
('3.3.2', {'title':"Returning items", 'docs':[]}),
|
||||
('3.3.3', {'title':"For action", 'docs':[]}),
|
||||
('3.4', {'title':"IRTF and Independent Submission stream documents"}),
|
||||
('3.4.1', {'title':"New items", 'docs':[]}),
|
||||
('3.4.2', {'title':"Returning items", 'docs':[]}),
|
||||
('3.4.3', {'title':"For action", 'docs':[]}),
|
||||
('4', {'title':"Working Group actions"}),
|
||||
('4.1', {'title':"WG creation"}),
|
||||
('4.1.1', {'title':"Proposed for IETF review", 'docs':[]}),
|
||||
('4.1.2', {'title':"Proposed for approval", 'docs':[]}),
|
||||
('4.2', {'title':"WG rechartering"}),
|
||||
('4.2.1', {'title':"Under evaluation for IETF review", 'docs':[]}),
|
||||
('4.2.2', {'title':"Proposed for approval", 'docs':[]}),
|
||||
('5', {'title':"IAB news we can use"}),
|
||||
('6', {'title':"Management issues"}),
|
||||
('7', {'title':"Working Group news"}),
|
||||
])
|
||||
|
||||
def fill_in_agenda_administrivia(date, sections):
|
||||
|
@ -185,7 +185,7 @@ def fill_in_agenda_docs(date, sections, matches=None):
|
|||
|
||||
# prune empty "For action" sections
|
||||
empty_for_action = [n for n, section in sections.iteritems()
|
||||
if section["title"] == "For Action" and not section["docs"]]
|
||||
if section["title"] == "For action" and not section["docs"]]
|
||||
for num in empty_for_action:
|
||||
del sections[num]
|
||||
|
||||
|
|
|
@ -2,24 +2,24 @@
|
|||
|
||||
# Portion Copyright (C) 2008-2009 Nokia Corporation and/or its subsidiary(-ies).
|
||||
# All rights reserved. Contact: Pasi Eronen <pasi.eronen@nokia.com>
|
||||
#
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions
|
||||
# modification, are permitted provided that the following conditions
|
||||
# are met:
|
||||
#
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
#
|
||||
#
|
||||
# * Redistributions in binary form must reproduce the above
|
||||
# copyright notice, this list of conditions and the following
|
||||
# disclaimer in the documentation and/or other materials provided
|
||||
# with the distribution.
|
||||
#
|
||||
#
|
||||
# * Neither the name of the Nokia Corporation and/or its
|
||||
# subsidiary(-ies) nor the names of its contributors may be used
|
||||
# to endorse or promote products derived from this software
|
||||
# without specific prior written permission.
|
||||
#
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
|
@ -57,6 +57,7 @@ from ietf.iesg.agenda import agenda_data, agenda_sections, fill_in_agenda_docs,
|
|||
from ietf.iesg.models import TelechatDate
|
||||
from ietf.ietfauth.utils import has_role, role_required, user_is_person
|
||||
from ietf.person.models import Person
|
||||
from ietf.doc.views_search import fill_in_search_attributes
|
||||
|
||||
def review_decisions(request, year=None):
|
||||
events = DocEvent.objects.filter(type__in=("iesg_disapproved", "iesg_approved"))
|
||||
|
@ -176,8 +177,8 @@ def agenda(request, date=None):
|
|||
data = agenda_data(date)
|
||||
|
||||
if has_role(request.user, ["Area Director", "IAB Chair", "Secretariat"]):
|
||||
data["sections"]["1.1"]["title"] = data["sections"]["1.1"]["title"].replace("Roll Call", '<a href="https://www.ietf.org/iesg/internal/rollcall.txt">Roll Call</a>')
|
||||
data["sections"]["1.3"]["title"] = data["sections"]["1.3"]["title"].replace("Minutes", '<a href="https://www.ietf.org/iesg/internal/minutes.txt">Minutes</a>')
|
||||
data["sections"]["1.1"]["title"] = data["sections"]["1.1"]["title"].replace("Roll call", '<a href="https://www.ietf.org/iesg/internal/rollcall.txt">Roll Call</a>')
|
||||
data["sections"]["1.3"]["title"] = data["sections"]["1.3"]["title"].replace("minutes", '<a href="https://www.ietf.org/iesg/internal/minutes.txt">Minutes</a>')
|
||||
|
||||
request.session['ballot_edit_return_point'] = request.path_info
|
||||
return render_to_response("iesg/agenda.html", {
|
||||
|
@ -303,7 +304,7 @@ class RescheduleForm(forms.Form):
|
|||
|
||||
def __init__(self, *args, **kwargs):
|
||||
dates = kwargs.pop('telechat_dates')
|
||||
|
||||
|
||||
super(self.__class__, self).__init__(*args, **kwargs)
|
||||
|
||||
# telechat choices
|
||||
|
@ -360,6 +361,9 @@ def agenda_documents(request):
|
|||
telechats = []
|
||||
for date in dates:
|
||||
sections = agenda_sections()
|
||||
# augment the docs with the search attributes, since we're using
|
||||
# the search_result_row view to display them (which expects them)
|
||||
fill_in_search_attributes(docs_by_date[date])
|
||||
fill_in_agenda_docs(date, sections, docs_by_date[date])
|
||||
|
||||
telechats.append({
|
||||
|
|
|
@ -217,7 +217,7 @@ class PersonForm(ModelForm):
|
|||
|
||||
# Make sure the alias table contains any new and/or old names.
|
||||
old_names = set([x.name for x in Alias.objects.filter(person=self.instance)])
|
||||
curr_names = set([x for x in [self.instance.name,
|
||||
curr_names = set([x for x in [self.instance.name,
|
||||
self.instance.ascii,
|
||||
self.instance.ascii_short,
|
||||
self.data['name'],
|
||||
|
|
|
@ -4,73 +4,58 @@ from django.utils.html import escape
|
|||
from django import forms
|
||||
from django.core.urlresolvers import reverse as urlreverse
|
||||
|
||||
import debug # pyflakes:ignore
|
||||
|
||||
from ietf.doc.models import DocAlias
|
||||
from ietf.ipr.models import IprDisclosureBase
|
||||
|
||||
def tokeninput_id_name_json(objs):
|
||||
"""Returns objects as JSON string.
|
||||
NOTE: double quotes in the object name are replaced with single quotes to avoid
|
||||
problems with representation of the JSON string in the HTML widget attribute"""
|
||||
def format_ipr(x):
|
||||
text = x.title.replace('"',"'")
|
||||
return escape(u"%s <%s>" % (text, x.time.date().isoformat()))
|
||||
def format_doc(x):
|
||||
return escape(x.name)
|
||||
def select2_id_ipr_title_json(value):
|
||||
return json.dumps([{ "id": o.pk, "text": escape(u"%s <%s>" % (o.title, o.time.date().isoformat())) } for o in value])
|
||||
|
||||
formatter = format_ipr if objs and isinstance(objs[0], IprDisclosureBase) else format_doc
|
||||
|
||||
return json.dumps([{ "id": o.pk, "name": formatter(o) } for o in objs])
|
||||
|
||||
class AutocompletedIprDisclosuresField(forms.CharField):
|
||||
"""Tokenizing autocompleted multi-select field for choosing
|
||||
IPR disclosures using jquery.tokeninput.js.
|
||||
class SearchableIprDisclosuresField(forms.CharField):
|
||||
"""Server-based multi-select field for choosing documents using
|
||||
select2.js.
|
||||
|
||||
The field uses a comma-separated list of primary keys in a
|
||||
CharField element as its API, the tokeninput Javascript adds some
|
||||
selection magic on top of this so we have to pass it a JSON
|
||||
representation of ids and user-understandable labels."""
|
||||
CharField element as its API with some extra attributes used by
|
||||
the Javascript part."""
|
||||
|
||||
def __init__(self,
|
||||
max_entries=None, # max number of selected objs
|
||||
model=IprDisclosureBase,
|
||||
hint_text="Type in term(s) to search disclosure title",
|
||||
hint_text="Type in terms to search disclosure title",
|
||||
*args, **kwargs):
|
||||
kwargs["max_length"] = 1000
|
||||
self.max_entries = max_entries
|
||||
self.model = model
|
||||
|
||||
super(AutocompletedIprDisclosuresField, self).__init__(*args, **kwargs)
|
||||
super(SearchableIprDisclosuresField, self).__init__(*args, **kwargs)
|
||||
|
||||
self.widget.attrs["class"] = "tokenized-field"
|
||||
self.widget.attrs["data-hint-text"] = hint_text
|
||||
self.widget.attrs["class"] = "select2-field form-control"
|
||||
self.widget.attrs["data-placeholder"] = hint_text
|
||||
if self.max_entries != None:
|
||||
self.widget.attrs["data-max-entries"] = self.max_entries
|
||||
|
||||
def parse_tokenized_value(self, value):
|
||||
def parse_select2_value(self, value):
|
||||
return [x.strip() for x in value.split(",") if x.strip()]
|
||||
|
||||
def prepare_value(self, value):
|
||||
if not value:
|
||||
value = ""
|
||||
if isinstance(value, basestring):
|
||||
pks = self.parse_tokenized_value(value)
|
||||
pks = self.parse_select2_value(value)
|
||||
value = self.model.objects.filter(pk__in=pks)
|
||||
if isinstance(value, self.model):
|
||||
value = [value]
|
||||
|
||||
self.widget.attrs["data-pre"] = tokeninput_id_name_json(value)
|
||||
self.widget.attrs["data-pre"] = select2_id_ipr_title_json(value)
|
||||
|
||||
# doing this in the constructor is difficult because the URL
|
||||
# patterns may not have been fully constructed there yet
|
||||
self.widget.attrs["data-ajax-url"] = urlreverse("ipr_ajax_search")
|
||||
|
||||
return ",".join(str(e.pk) for e in value)
|
||||
return u",".join(unicode(e.pk) for e in value)
|
||||
|
||||
def clean(self, value):
|
||||
value = super(AutocompletedIprDisclosuresField, self).clean(value)
|
||||
pks = self.parse_tokenized_value(value)
|
||||
value = super(SearchableIprDisclosuresField, self).clean(value)
|
||||
pks = self.parse_select2_value(value)
|
||||
|
||||
objs = self.model.objects.filter(pk__in=pks)
|
||||
|
||||
|
@ -83,75 +68,3 @@ class AutocompletedIprDisclosuresField(forms.CharField):
|
|||
raise forms.ValidationError(u"You can select at most %s entries only." % self.max_entries)
|
||||
|
||||
return objs
|
||||
|
||||
class AutocompletedDraftsField(AutocompletedIprDisclosuresField):
|
||||
"""Version of AutocompletedPersonsField with the defaults right for Drafts."""
|
||||
|
||||
def __init__(self, model=DocAlias, hint_text="Type in name to search draft name",
|
||||
*args, **kwargs):
|
||||
super(AutocompletedDraftsField, self).__init__(model=model, hint_text=hint_text, *args, **kwargs)
|
||||
|
||||
def prepare_value(self, value):
|
||||
if not value:
|
||||
value = ""
|
||||
if isinstance(value, basestring):
|
||||
pks = self.parse_tokenized_value(value)
|
||||
value = self.model.objects.filter(pk__in=pks)
|
||||
if isinstance(value, self.model):
|
||||
value = [value]
|
||||
if isinstance(value, long):
|
||||
value = self.model.objects.filter(pk=value)
|
||||
|
||||
self.widget.attrs["data-pre"] = tokeninput_id_name_json(value)
|
||||
|
||||
# doing this in the constructor is difficult because the URL
|
||||
# patterns may not have been fully constructed there yet
|
||||
self.widget.attrs["data-ajax-url"] = urlreverse("ipr_ajax_draft_search")
|
||||
|
||||
return ",".join(str(e.pk) for e in value)
|
||||
|
||||
class AutocompletedDraftField(AutocompletedDraftsField):
|
||||
"""Version of AutocompletedEmailsField specialized to a single object."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs["max_entries"] = 1
|
||||
super(AutocompletedDraftField, self).__init__(*args, **kwargs)
|
||||
|
||||
def clean(self, value):
|
||||
return super(AutocompletedDraftField, self).clean(value).first()
|
||||
|
||||
class AutocompletedRfcsField(AutocompletedIprDisclosuresField):
|
||||
"""Version of AutocompletedPersonsField with the defaults right for Drafts."""
|
||||
|
||||
def __init__(self, model=DocAlias, hint_text="Type in the RFC number",
|
||||
*args, **kwargs):
|
||||
super(AutocompletedRfcsField, self).__init__(model=model, hint_text=hint_text, *args, **kwargs)
|
||||
|
||||
def prepare_value(self, value):
|
||||
if not value:
|
||||
value = ""
|
||||
if isinstance(value, basestring):
|
||||
pks = self.parse_tokenized_value(value)
|
||||
value = self.model.objects.filter(pk__in=pks)
|
||||
if isinstance(value, self.model):
|
||||
value = [value]
|
||||
if isinstance(value, long):
|
||||
value = self.model.objects.filter(pk=value)
|
||||
|
||||
self.widget.attrs["data-pre"] = tokeninput_id_name_json(value)
|
||||
|
||||
# doing this in the constructor is difficult because the URL
|
||||
# patterns may not have been fully constructed there yet
|
||||
self.widget.attrs["data-ajax-url"] = urlreverse("ipr_ajax_rfc_search")
|
||||
|
||||
return ",".join(str(e.pk) for e in value)
|
||||
|
||||
class AutocompletedRfcField(AutocompletedRfcsField):
|
||||
"""Version of AutocompletedEmailsField specialized to a single object."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs["max_entries"] = 1
|
||||
super(AutocompletedRfcField, self).__init__(*args, **kwargs)
|
||||
|
||||
def clean(self, value):
|
||||
return super(AutocompletedRfcField, self).clean(value).first()
|
|
@ -1,16 +1,18 @@
|
|||
import datetime
|
||||
import email
|
||||
|
||||
from django.utils.safestring import mark_safe
|
||||
from django import forms
|
||||
|
||||
from ietf.group.models import Group
|
||||
from ietf.doc.fields import SearchableDocAliasField
|
||||
from ietf.ipr.mail import utc_from_string
|
||||
from ietf.ipr.fields import (AutocompletedIprDisclosuresField, AutocompletedDraftField,
|
||||
AutocompletedRfcField)
|
||||
from ietf.ipr.fields import SearchableIprDisclosuresField
|
||||
from ietf.ipr.models import (IprDocRel, IprDisclosureBase, HolderIprDisclosure,
|
||||
GenericIprDisclosure, ThirdPartyIprDisclosure, NonDocSpecificIprDisclosure,
|
||||
IprLicenseTypeName, IprDisclosureStateName)
|
||||
from ietf.message.models import Message
|
||||
from ietf.utils.fields import DatepickerDateField
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
# Globals
|
||||
|
@ -44,8 +46,8 @@ class MessageModelChoiceField(forms.ModelChoiceField):
|
|||
# Forms
|
||||
# ----------------------------------------------------------------
|
||||
class AddCommentForm(forms.Form):
|
||||
private = forms.BooleanField(required=False,help_text="If this box is checked the comment will not appear in the disclosure's public history view.")
|
||||
comment = forms.CharField(required=True, widget=forms.Textarea)
|
||||
private = forms.BooleanField(label="Private comment", required=False,help_text="If this box is checked the comment will not appear in the disclosure's public history view.")
|
||||
|
||||
class AddEmailForm(forms.Form):
|
||||
direction = forms.ChoiceField(choices=(("incoming", "Incoming"), ("outgoing", "Outgoing")),
|
||||
|
@ -89,7 +91,7 @@ class AddEmailForm(forms.Form):
|
|||
return self.cleaned_data
|
||||
|
||||
class DraftForm(forms.ModelForm):
|
||||
document = AutocompletedDraftField(required=False)
|
||||
document = SearchableDocAliasField(label="I-D name/RFC number", required=False, doc_type="draft")
|
||||
|
||||
class Meta:
|
||||
model = IprDocRel
|
||||
|
@ -102,20 +104,20 @@ class GenericDisclosureForm(forms.Form):
|
|||
"""Custom ModelForm-like form to use for new Generic or NonDocSpecific Iprs.
|
||||
If patent_info is submitted create a NonDocSpecificIprDisclosure object
|
||||
otherwise create a GenericIprDisclosure object."""
|
||||
compliant = forms.BooleanField(required=False)
|
||||
compliant = forms.BooleanField(label="This disclosure complies with RFC 3979", required=False)
|
||||
holder_legal_name = forms.CharField(max_length=255)
|
||||
notes = forms.CharField(max_length=255,widget=forms.Textarea,required=False)
|
||||
other_designations = forms.CharField(max_length=255,required=False)
|
||||
holder_contact_name = forms.CharField(max_length=255)
|
||||
holder_contact_email = forms.EmailField()
|
||||
holder_contact_info = forms.CharField(max_length=255,widget=forms.Textarea,required=False)
|
||||
notes = forms.CharField(label="Additional notes", max_length=255,widget=forms.Textarea,required=False)
|
||||
other_designations = forms.CharField(label="Designations for other contributions", max_length=255,required=False)
|
||||
holder_contact_name = forms.CharField(label="Name", max_length=255)
|
||||
holder_contact_email = forms.EmailField(label="Email")
|
||||
holder_contact_info = forms.CharField(label="Other Info (address, phone, etc.)", max_length=255,widget=forms.Textarea,required=False)
|
||||
submitter_name = forms.CharField(max_length=255,required=False)
|
||||
submitter_email = forms.EmailField(required=False)
|
||||
patent_info = forms.CharField(max_length=255,widget=forms.Textarea,required=False)
|
||||
patent_info = forms.CharField(max_length=255,widget=forms.Textarea, required=False, help_text="Patent, Serial, Publication, Registration, or Application/File number(s), Date(s) granted or applied for, Country, and any additional notes.")
|
||||
has_patent_pending = forms.BooleanField(required=False)
|
||||
statement = forms.CharField(max_length=255,widget=forms.Textarea,required=False)
|
||||
updates = AutocompletedIprDisclosuresField(required=False)
|
||||
same_as_ii_above = forms.BooleanField(required=False)
|
||||
updates = SearchableIprDisclosuresField(required=False, help_text="If this disclosure <strong>updates</strong> other disclosures identify here which ones. Leave this field blank if this disclosure does not update any prior disclosures. <strong>Note</strong>: Updates to IPR disclosures must only be made by authorized representatives of the original submitters. Updates will automatically be forwarded to the current Patent Holder's Contact and to the Submitter of the original IPR disclosure.")
|
||||
same_as_ii_above = forms.BooleanField(label="Same as in section II above", required=False)
|
||||
|
||||
def __init__(self,*args,**kwargs):
|
||||
super(GenericDisclosureForm, self).__init__(*args,**kwargs)
|
||||
|
@ -155,7 +157,7 @@ class GenericDisclosureForm(forms.Form):
|
|||
|
||||
class IprDisclosureFormBase(forms.ModelForm):
|
||||
"""Base form for Holder and ThirdParty disclosures"""
|
||||
updates = AutocompletedIprDisclosuresField(required=False)
|
||||
updates = SearchableIprDisclosuresField(required=False, help_text=mark_safe("If this disclosure <strong>updates</strong> other disclosures identify here which ones. Leave this field blank if this disclosure does not update any prior disclosures. Note: Updates to IPR disclosures must only be made by authorized representatives of the original submitters. Updates will automatically be forwarded to the current Patent Holder's Contact and to the Submitter of the original IPR disclosure."))
|
||||
same_as_ii_above = forms.BooleanField(required=False)
|
||||
|
||||
def __init__(self,*args,**kwargs):
|
||||
|
@ -163,6 +165,22 @@ class IprDisclosureFormBase(forms.ModelForm):
|
|||
self.fields['submitter_name'].required = False
|
||||
self.fields['submitter_email'].required = False
|
||||
self.fields['compliant'].initial = True
|
||||
self.fields['compliant'].label = "This disclosure complies with RFC 3979"
|
||||
if "ietfer_name" in self.fields:
|
||||
self.fields["ietfer_name"].label = "Name"
|
||||
if "ietfer_contact_email" in self.fields:
|
||||
self.fields["ietfer_contact_email"].label = "Email"
|
||||
if "ietfer_contact_info" in self.fields:
|
||||
self.fields["ietfer_contact_info"].label = "Other info"
|
||||
self.fields["ietfer_contact_info"].help_text = "Address, phone, etc."
|
||||
if "patent_info" in self.fields:
|
||||
self.fields["patent_info"].help_text = "Patent, Serial, Publication, Registration, or Application/File number(s), Date(s) granted or applied for, Country, and any additional notes"
|
||||
if "licensing" in self.fields:
|
||||
self.fields["licensing_comments"].label = "Licensing information, comments, notes, or URL for further information"
|
||||
if "submitter_claims_all_terms_disclosed" in self.fields:
|
||||
self.fields["submitter_claims_all_terms_disclosed"].label = "The individual submitting this template represents and warrants that all terms and conditions that must be satisfied for implementers of any covered IETF specification to obtain a license have been disclosed in this IPR disclosure statement"
|
||||
if "same_as_ii_above" in self.fields:
|
||||
self.fields["same_as_ii_above"].label = "Same as in section II above"
|
||||
|
||||
class Meta:
|
||||
"""This will be overridden"""
|
||||
|
@ -202,7 +220,7 @@ class HolderIprDisclosureForm(IprDisclosureFormBase):
|
|||
def clean(self):
|
||||
super(HolderIprDisclosureForm, self).clean()
|
||||
cleaned_data = self.cleaned_data
|
||||
if not self.data.get('draft-0-document') and not self.data.get('rfc-0-document') and not cleaned_data.get('other_designations'):
|
||||
if not self.data.get('iprdocrel_set-0-document') and not cleaned_data.get('other_designations'):
|
||||
raise forms.ValidationError('You need to specify a contribution in Section IV')
|
||||
return cleaned_data
|
||||
|
||||
|
@ -222,7 +240,7 @@ class GenericIprDisclosureForm(IprDisclosureFormBase):
|
|||
exclude = [ 'by','docs','state','rel' ]
|
||||
|
||||
class MessageModelForm(forms.ModelForm):
|
||||
response_due = forms.DateField(required=False,help_text='The date which a response is due')
|
||||
response_due = DatepickerDateField(date_format="yyyy-mm-dd", picker_settings={"autoclose": "1" }, required=False, help_text='The date which a response is due.')
|
||||
|
||||
class Meta:
|
||||
model = Message
|
||||
|
@ -244,12 +262,6 @@ class NotifyForm(forms.Form):
|
|||
type = forms.CharField(widget=forms.HiddenInput)
|
||||
text = forms.CharField(widget=forms.Textarea)
|
||||
|
||||
class RfcForm(DraftForm):
|
||||
document = AutocompletedRfcField(required=False)
|
||||
|
||||
class Meta(DraftForm.Meta):
|
||||
exclude = ('revisions',)
|
||||
|
||||
class ThirdPartyIprDisclosureForm(IprDisclosureFormBase):
|
||||
class Meta:
|
||||
model = ThirdPartyIprDisclosure
|
||||
|
@ -258,7 +270,7 @@ class ThirdPartyIprDisclosureForm(IprDisclosureFormBase):
|
|||
def clean(self):
|
||||
super(ThirdPartyIprDisclosureForm, self).clean()
|
||||
cleaned_data = self.cleaned_data
|
||||
if not self.data.get('draft-0-document') and not self.data.get('rfc-0-document') and not cleaned_data.get('other_designations'):
|
||||
if not self.data.get('iprdocrel_set-0-document') and not cleaned_data.get('other_designations'):
|
||||
raise forms.ValidationError('You need to specify a contribution in Section III')
|
||||
return cleaned_data
|
||||
|
||||
|
@ -274,15 +286,15 @@ class ThirdPartyIprDisclosureForm(IprDisclosureFormBase):
|
|||
|
||||
class SearchForm(forms.Form):
|
||||
state = forms.MultipleChoiceField(choices=STATE_CHOICES,widget=forms.CheckboxSelectMultiple,required=False)
|
||||
draft = forms.CharField(max_length=128,required=False)
|
||||
rfc = forms.IntegerField(required=False)
|
||||
holder = forms.CharField(max_length=128,required=False)
|
||||
patent = forms.CharField(max_length=128,required=False)
|
||||
group = GroupModelChoiceField(label="Working group name",queryset=Group.objects.filter(type='wg').order_by('acronym'),required=False)
|
||||
doctitle = forms.CharField(max_length=128,required=False)
|
||||
iprtitle = forms.CharField(max_length=128,required=False)
|
||||
draft = forms.CharField(label="Draft name", max_length=128, required=False)
|
||||
rfc = forms.IntegerField(label="RFC number", required=False)
|
||||
holder = forms.CharField(label="Name of patent owner/applicant", max_length=128,required=False)
|
||||
patent = forms.CharField(label="Text in patent information", max_length=128,required=False)
|
||||
group = GroupModelChoiceField(label="Working group",queryset=Group.objects.filter(type='wg').order_by('acronym'),required=False, empty_label="(Select WG)")
|
||||
doctitle = forms.CharField(label="Words in document title", max_length=128,required=False)
|
||||
iprtitle = forms.CharField(label="Words in IPR disclosure title", max_length=128,required=False)
|
||||
|
||||
class StateForm(forms.Form):
|
||||
state = forms.ModelChoiceField(queryset=IprDisclosureStateName.objects,label="New State",empty_label=None)
|
||||
private = forms.BooleanField(required=False,help_text="If this box is checked the comment will not appear in the disclosure's public history view.")
|
||||
comment = forms.CharField(required=False, widget=forms.Textarea)
|
||||
comment = forms.CharField(required=False, widget=forms.Textarea, help_text="You may add a comment to be included in the disclosure history.")
|
||||
private = forms.BooleanField(label="Private comment", required=False, help_text="If this box is checked the comment will not appear in the disclosure's public history view.")
|
||||
|
|
|
@ -12,8 +12,8 @@ LICENSE_CHOICES = (
|
|||
(3, 'c) Reasonable and Non-Discriminatory License to All Implementers with Possible Royalty/Fee.'),
|
||||
(4, 'd) Licensing Declaration to be Provided Later (implies a willingness'
|
||||
' to commit to the provisions of a), b), or c) above to all implementers;'
|
||||
' otherwise, the next option "Unwilling to Commit to the Provisions of'
|
||||
' a), b), or c) Above". - must be selected).'),
|
||||
' otherwise, the next option - "Unwilling to Commit to the Provisions of'
|
||||
' a), b), or c) Above" - must be selected).'),
|
||||
(5, 'e) Unwilling to Commit to the Provisions of a), b), or c) Above.'),
|
||||
(6, 'f) See Text Below for Licensing Declaration.'),
|
||||
)
|
||||
|
@ -27,10 +27,10 @@ SELECT_CHOICES = (
|
|||
(2, 'NO'),
|
||||
)
|
||||
STATUS_CHOICES = (
|
||||
( 0, "Waiting for approval" ),
|
||||
( 1, "Approved and Posted" ),
|
||||
( 2, "Rejected by Administrator" ),
|
||||
( 3, "Removed by Request" ),
|
||||
( 0, "Waiting for approval" ),
|
||||
( 1, "Approved and Posted" ),
|
||||
( 2, "Rejected by Administrator" ),
|
||||
( 3, "Removed by Request" ),
|
||||
)
|
||||
|
||||
class IprDetail(models.Model):
|
||||
|
@ -45,35 +45,35 @@ class IprDetail(models.Model):
|
|||
legacy_title_2 = models.CharField(blank=True, null=True, db_column="additional_old_title2", max_length=255)
|
||||
|
||||
# Patent holder fieldset
|
||||
legal_name = models.CharField("Legal Name", db_column="p_h_legal_name", max_length=255)
|
||||
legal_name = models.CharField("Legal name", db_column="p_h_legal_name", max_length=255)
|
||||
|
||||
# Patent Holder Contact fieldset
|
||||
# self.contact.filter(contact_type=1)
|
||||
|
||||
# IETF Contact fieldset
|
||||
# self.contact.filter(contact_type=3)
|
||||
|
||||
|
||||
# Related IETF Documents fieldset
|
||||
rfc_number = models.IntegerField(null=True, editable=False, blank=True) # always NULL
|
||||
id_document_tag = models.IntegerField(null=True, editable=False, blank=True) # always NULL
|
||||
other_designations = models.CharField(blank=True, max_length=255)
|
||||
other_designations = models.CharField("Designations for other contributions", blank=True, max_length=255)
|
||||
document_sections = models.TextField("Specific document sections covered", blank=True, max_length=255, db_column='disclouser_identify')
|
||||
|
||||
# Patent Information fieldset
|
||||
patents = models.TextField("Patent Applications", db_column="p_applications", max_length=255)
|
||||
date_applied = models.CharField(max_length=255)
|
||||
patents = models.TextField("Patent, serial, publication, registration, or application/file number(s)", db_column="p_applications", max_length=255)
|
||||
date_applied = models.CharField("Date(s) granted or applied for", max_length=255)
|
||||
country = models.CharField(max_length=255)
|
||||
notes = models.TextField("Additional notes", db_column="p_notes", blank=True)
|
||||
is_pending = models.IntegerField("Unpublished Pending Patent Application", blank=True, null=True, choices=SELECT_CHOICES, db_column="selecttype")
|
||||
applies_to_all = models.IntegerField("Applies to all IPR owned by Submitter", blank=True, null=True, choices=SELECT_CHOICES, db_column="selectowned")
|
||||
is_pending = models.IntegerField("Unpublished pending patent application", blank=True, null=True, choices=SELECT_CHOICES, db_column="selecttype")
|
||||
applies_to_all = models.IntegerField("Applies to all IPR owned by submitter", blank=True, null=True, choices=SELECT_CHOICES, db_column="selectowned")
|
||||
|
||||
# Licensing Declaration fieldset
|
||||
licensing_option = models.IntegerField(null=True, blank=True, choices=LICENSE_CHOICES)
|
||||
lic_opt_a_sub = models.IntegerField(null=True, editable=False, choices=STDONLY_CHOICES)
|
||||
lic_opt_b_sub = models.IntegerField(null=True, editable=False, choices=STDONLY_CHOICES)
|
||||
lic_opt_c_sub = models.IntegerField(null=True, editable=False, choices=STDONLY_CHOICES)
|
||||
comments = models.TextField("Licensing Comments", blank=True)
|
||||
lic_checkbox = models.BooleanField("All terms and conditions has been disclosed", default=False)
|
||||
comments = models.TextField("Licensing comments", blank=True)
|
||||
lic_checkbox = models.BooleanField("The individual submitting this template represents and warrants that all terms and conditions that must be satisfied for implementers of any covered IETF specification to obtain a license have been disclosed in this IPR disclosure statement.", default=False)
|
||||
|
||||
|
||||
# Other notes fieldset
|
||||
|
@ -119,8 +119,8 @@ class IprContact(models.Model):
|
|||
name = models.CharField(max_length=255)
|
||||
title = models.CharField(blank=True, max_length=255)
|
||||
department = models.CharField(blank=True, max_length=255)
|
||||
address1 = models.CharField(blank=True, max_length=255)
|
||||
address2 = models.CharField(blank=True, max_length=255)
|
||||
address1 = models.CharField("Address", blank=True, max_length=255)
|
||||
address2 = models.CharField("Address (continued)", blank=True, max_length=255)
|
||||
telephone = models.CharField(blank=True, max_length=25)
|
||||
fax = models.CharField(blank=True, max_length=25)
|
||||
email = models.EmailField(max_length=255)
|
||||
|
@ -182,11 +182,11 @@ from ietf.message.models import Message
|
|||
|
||||
class IprDisclosureBase(models.Model):
|
||||
by = models.ForeignKey(Person) # who was logged in, or System if nobody was logged in
|
||||
compliant = models.BooleanField(default=True) # complies to RFC3979
|
||||
compliant = models.BooleanField("Complies to RFC3979", default=True)
|
||||
docs = models.ManyToManyField(DocAlias, through='IprDocRel')
|
||||
holder_legal_name = models.CharField(max_length=255)
|
||||
notes = models.TextField(blank=True)
|
||||
other_designations = models.CharField(blank=True, max_length=255)
|
||||
notes = models.TextField("Additional notes", blank=True)
|
||||
other_designations = models.CharField("Designations for other contributions", blank=True, max_length=255)
|
||||
rel = models.ManyToManyField('self', through='RelatedIpr', symmetrical=False)
|
||||
state = models.ForeignKey(IprDisclosureStateName)
|
||||
submitter_name = models.CharField(max_length=255)
|
||||
|
@ -269,7 +269,7 @@ class HolderIprDisclosure(IprDisclosureBase):
|
|||
has_patent_pending = models.BooleanField(default=False)
|
||||
holder_contact_email = models.EmailField()
|
||||
holder_contact_name = models.CharField(max_length=255)
|
||||
holder_contact_info = models.TextField(blank=True)
|
||||
holder_contact_info = models.TextField(blank=True, help_text="Address, phone, etc.")
|
||||
licensing = models.ForeignKey(IprLicenseTypeName)
|
||||
licensing_comments = models.TextField(blank=True)
|
||||
submitter_claims_all_terms_disclosed = models.BooleanField(default=False)
|
||||
|
@ -277,7 +277,7 @@ class HolderIprDisclosure(IprDisclosureBase):
|
|||
class ThirdPartyIprDisclosure(IprDisclosureBase):
|
||||
ietfer_name = models.CharField(max_length=255) # "Whose Personal Belief Triggered..."
|
||||
ietfer_contact_email = models.EmailField()
|
||||
ietfer_contact_info = models.TextField(blank=True)
|
||||
ietfer_contact_info = models.TextField(blank=True, help_text="Address, phone, etc.")
|
||||
patent_info = models.TextField()
|
||||
has_patent_pending = models.BooleanField(default=False)
|
||||
|
||||
|
@ -285,7 +285,7 @@ class NonDocSpecificIprDisclosure(IprDisclosureBase):
|
|||
'''A Generic IPR Disclosure w/ patent information'''
|
||||
holder_contact_name = models.CharField(max_length=255)
|
||||
holder_contact_email = models.EmailField()
|
||||
holder_contact_info = models.TextField(blank=True)
|
||||
holder_contact_info = models.TextField(blank=True, help_text="Address, phone, etc.")
|
||||
patent_info = models.TextField()
|
||||
has_patent_pending = models.BooleanField(default=False)
|
||||
statement = models.TextField() # includes licensing info
|
||||
|
@ -293,7 +293,7 @@ class NonDocSpecificIprDisclosure(IprDisclosureBase):
|
|||
class GenericIprDisclosure(IprDisclosureBase):
|
||||
holder_contact_name = models.CharField(max_length=255)
|
||||
holder_contact_email = models.EmailField()
|
||||
holder_contact_info = models.TextField(blank=True)
|
||||
holder_contact_info = models.TextField(blank=True, help_text="Address, phone, etc.")
|
||||
statement = models.TextField() # includes licensing info
|
||||
|
||||
class IprDocRel(models.Model):
|
||||
|
|
|
@ -13,7 +13,7 @@ from ietf.ipr.models import (IprDisclosureBase,GenericIprDisclosure,HolderIprDis
|
|||
ThirdPartyIprDisclosure,RelatedIpr)
|
||||
from ietf.ipr.utils import get_genitive, get_ipr_summary
|
||||
from ietf.message.models import Message
|
||||
from ietf.utils.test_utils import TestCase
|
||||
from ietf.utils.test_utils import TestCase, login_testing_unauthorized
|
||||
from ietf.utils.test_data import make_test_data
|
||||
|
||||
|
||||
|
@ -247,7 +247,7 @@ class IprTests(TestCase):
|
|||
})
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertTrue(len(q("ul.errorlist")) > 0)
|
||||
self.assertTrue(len(q("form .has-error")) > 0)
|
||||
|
||||
# successful post
|
||||
r = self.client.post(url, {
|
||||
|
@ -283,13 +283,11 @@ class IprTests(TestCase):
|
|||
"holder_contact_info": "555-555-0100",
|
||||
"ietfer_name": "Test Participant",
|
||||
"ietfer_contact_info": "555-555-0101",
|
||||
"rfc-TOTAL_FORMS": 1,
|
||||
"rfc-INITIAL_FORMS": 0,
|
||||
"rfc-0-document": DocAlias.objects.filter(name__startswith="rfc").first().pk,
|
||||
"draft-TOTAL_FORMS": 1,
|
||||
"draft-INITIAL_FORMS": 0,
|
||||
"draft-0-document": "%s" % draft.docalias_set.first().pk,
|
||||
"draft-0-revisions": '00',
|
||||
"iprdocrel_set-TOTAL_FORMS": 2,
|
||||
"iprdocrel_set-INITIAL_FORMS": 0,
|
||||
"iprdocrel_set-0-document": "%s" % draft.docalias_set.first().pk,
|
||||
"iprdocrel_set-0-revisions": '00',
|
||||
"iprdocrel_set-1-document": DocAlias.objects.filter(name__startswith="rfc").first().pk,
|
||||
"patent_info": "none",
|
||||
"has_patent_pending": False,
|
||||
"licensing": "royalty-free",
|
||||
|
@ -319,13 +317,11 @@ class IprTests(TestCase):
|
|||
"ietfer_name": "Test Participant",
|
||||
"ietfer_contact_email": "test@ietfer.com",
|
||||
"ietfer_contact_info": "555-555-0101",
|
||||
"rfc-TOTAL_FORMS": 1,
|
||||
"rfc-INITIAL_FORMS": 0,
|
||||
"rfc-0-document": DocAlias.objects.filter(name__startswith="rfc").first().pk,
|
||||
"draft-TOTAL_FORMS": 1,
|
||||
"draft-INITIAL_FORMS": 0,
|
||||
"draft-0-document": "%s" % draft.docalias_set.first().pk,
|
||||
"draft-0-revisions": '00',
|
||||
"iprdocrel_set-TOTAL_FORMS": 2,
|
||||
"iprdocrel_set-INITIAL_FORMS": 0,
|
||||
"iprdocrel_set-0-document": "%s" % draft.docalias_set.first().pk,
|
||||
"iprdocrel_set-0-revisions": '00',
|
||||
"iprdocrel_set-1-document": DocAlias.objects.filter(name__startswith="rfc").first().pk,
|
||||
"patent_info": "none",
|
||||
"has_patent_pending": False,
|
||||
"licensing": "royalty-free",
|
||||
|
@ -333,7 +329,6 @@ class IprTests(TestCase):
|
|||
"submitter_email": "test@holder.com",
|
||||
})
|
||||
self.assertEqual(r.status_code, 200)
|
||||
# print r.content
|
||||
self.assertTrue("Your IPR disclosure has been submitted" in r.content)
|
||||
|
||||
iprs = IprDisclosureBase.objects.filter(title__icontains="belonging to Test Legal")
|
||||
|
@ -357,13 +352,11 @@ class IprTests(TestCase):
|
|||
"holder_contact_info": "555-555-0100",
|
||||
"ietfer_name": "Test Participant",
|
||||
"ietfer_contact_info": "555-555-0101",
|
||||
"rfc-TOTAL_FORMS": 1,
|
||||
"rfc-INITIAL_FORMS": 0,
|
||||
"rfc-0-document": DocAlias.objects.filter(name__startswith="rfc").first().pk,
|
||||
"draft-TOTAL_FORMS": 1,
|
||||
"draft-INITIAL_FORMS": 0,
|
||||
"draft-0-document": "%s" % draft.docalias_set.first().pk,
|
||||
"draft-0-revisions": '00',
|
||||
"iprdocrel_set-TOTAL_FORMS": 2,
|
||||
"iprdocrel_set-INITIAL_FORMS": 0,
|
||||
"iprdocrel_set-0-document": "%s" % draft.docalias_set.first().pk,
|
||||
"iprdocrel_set-0-revisions": '00',
|
||||
"iprdocrel_set-1-document": DocAlias.objects.filter(name__startswith="rfc").first().pk,
|
||||
"patent_info": "none",
|
||||
"has_patent_pending": False,
|
||||
"licensing": "royalty-free",
|
||||
|
@ -442,7 +435,7 @@ I would like to revoke this declaration.
|
|||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code,200)
|
||||
q = PyQuery(r.content)
|
||||
x = len(q('table#pending-iprs tr')) - 1 # minus header
|
||||
x = len(q('table.ipr-table tbody tr'))
|
||||
self.assertEqual(num,x)
|
||||
|
||||
def test_admin_removed(self):
|
||||
|
@ -459,7 +452,7 @@ I would like to revoke this declaration.
|
|||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code,200)
|
||||
q = PyQuery(r.content)
|
||||
x = len(q('table#removed-iprs tr')) - 1 # minus header
|
||||
x = len(q('table.ipr-table tbody tr'))
|
||||
self.assertEqual(num,x)
|
||||
|
||||
def test_admin_parked(self):
|
||||
|
@ -468,14 +461,11 @@ I would like to revoke this declaration.
|
|||
def test_post(self):
|
||||
make_test_data()
|
||||
ipr = IprDisclosureBase.objects.get(title='Statement regarding rights')
|
||||
url = urlreverse("ipr_post",kwargs={ "id": ipr.id })
|
||||
# fail if not logged in
|
||||
url = urlreverse("ipr_post", kwargs={ "id": ipr.id })
|
||||
login_testing_unauthorized(self, "secretary", url)
|
||||
|
||||
r = self.client.get(url,follow=True)
|
||||
self.assertTrue("Sign In" in r.content)
|
||||
# successful post
|
||||
self.client.login(username="secretary", password="secretary+password")
|
||||
r = self.client.get(url,follow=True)
|
||||
self.assertEqual(r.status_code,200)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
ipr = IprDisclosureBase.objects.get(title='Statement regarding rights')
|
||||
self.assertEqual(ipr.state.slug,'posted')
|
||||
|
||||
|
@ -518,4 +508,4 @@ Subject: test
|
|||
""".format(data['reply_to'],datetime.datetime.now().ctime())
|
||||
result = process_response_email(message_string)
|
||||
self.assertIsInstance(result,Message)
|
||||
self.assertFalse(event.response_past_due())
|
||||
self.assertFalse(event.response_past_due())
|
||||
|
|
|
@ -10,8 +10,6 @@ urlpatterns = patterns('ietf.ipr.views',
|
|||
url(r'^admin/$', RedirectView.as_view(url=reverse_lazy('ipr_admin',kwargs={'state':'pending'})),name="ipr_admin_main"),
|
||||
url(r'^admin/(?P<state>pending|removed|parked)/$', 'admin', name='ipr_admin'),
|
||||
url(r'^ajax/search/$', 'ajax_search', name='ipr_ajax_search'),
|
||||
url(r'^ajax/draft-search/$', 'ajax_draft_search', name='ipr_ajax_draft_search'),
|
||||
url(r'^ajax/rfc-search/$', 'ajax_rfc_search', name='ipr_ajax_rfc_search'),
|
||||
(r'^by-draft/$', 'iprs_for_drafts_txt'),
|
||||
url(r'^(?P<id>\d+)/$', 'show', name='ipr_show'),
|
||||
url(r'^(?P<id>\d+)/addcomment/$', 'add_comment', name='ipr_add_comment'),
|
||||
|
|
|
@ -10,8 +10,7 @@ from django.db.models import Q
|
|||
from django.forms.models import inlineformset_factory
|
||||
from django.forms.formsets import formset_factory
|
||||
from django.http import HttpResponse, Http404, HttpResponseRedirect
|
||||
from django.shortcuts import render_to_response as render, get_object_or_404, redirect
|
||||
from django.template import RequestContext
|
||||
from django.shortcuts import render, get_object_or_404, redirect
|
||||
from django.template.loader import render_to_string
|
||||
|
||||
from ietf.doc.models import DocAlias
|
||||
|
@ -19,9 +18,9 @@ from ietf.group.models import Role, Group
|
|||
from ietf.ietfauth.utils import role_required, has_role
|
||||
from ietf.ipr.mail import (message_from_message, get_reply_to, get_update_submitter_emails,
|
||||
get_update_cc_addrs)
|
||||
from ietf.ipr.fields import tokeninput_id_name_json
|
||||
from ietf.ipr.fields import select2_id_ipr_title_json
|
||||
from ietf.ipr.forms import (HolderIprDisclosureForm, GenericDisclosureForm,
|
||||
ThirdPartyIprDisclosureForm, DraftForm, RfcForm, SearchForm, MessageModelForm,
|
||||
ThirdPartyIprDisclosureForm, DraftForm, SearchForm, MessageModelForm,
|
||||
AddCommentForm, AddEmailForm, NotifyForm, StateForm, NonDocSpecificIprDisclosureForm,
|
||||
GenericIprDisclosureForm)
|
||||
from ietf.ipr.models import (IprDisclosureStateName, IprDisclosureBase,
|
||||
|
@ -174,45 +173,13 @@ def ajax_search(request):
|
|||
|
||||
objs = objs.distinct()[:10]
|
||||
|
||||
return HttpResponse(tokeninput_id_name_json(objs), content_type='application/json')
|
||||
|
||||
def ajax_draft_search(request):
|
||||
q = [w.strip() for w in request.GET.get('q', '').split() if w.strip()]
|
||||
return HttpResponse(select2_id_ipr_title_json(objs), content_type='application/json')
|
||||
|
||||
if not q:
|
||||
objs = DocAlias.objects.none()
|
||||
else:
|
||||
query = Q()
|
||||
for t in q:
|
||||
query &= Q(name__icontains=t)
|
||||
|
||||
objs = DocAlias.objects.filter(name__startswith='draft').filter(query)
|
||||
|
||||
objs = objs.distinct()[:10]
|
||||
|
||||
return HttpResponse(tokeninput_id_name_json(objs), content_type='application/json')
|
||||
|
||||
def ajax_rfc_search(request):
|
||||
# expects one numeric term
|
||||
q = [w.strip() for w in request.GET.get('q', '').split() if w.strip()]
|
||||
|
||||
if not q:
|
||||
objs = DocAlias.objects.none()
|
||||
else:
|
||||
query = Q()
|
||||
query &= Q(name__startswith='rfc%s' % q[0])
|
||||
|
||||
objs = DocAlias.objects.filter(query)
|
||||
|
||||
objs = objs.distinct()[:10]
|
||||
|
||||
return HttpResponse(tokeninput_id_name_json(objs), content_type='application/json')
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
# Views
|
||||
# ----------------------------------------------------------------
|
||||
def about(request):
|
||||
return render("ipr/disclosure.html", {}, context_instance=RequestContext(request))
|
||||
return render(request, "ipr/disclosure.html", {})
|
||||
|
||||
@role_required('Secretariat',)
|
||||
def add_comment(request, id):
|
||||
|
@ -239,8 +206,7 @@ def add_comment(request, id):
|
|||
else:
|
||||
form = AddCommentForm()
|
||||
|
||||
return render('ipr/add_comment.html',dict(ipr=ipr,form=form),
|
||||
context_instance=RequestContext(request))
|
||||
return render(request, 'ipr/add_comment.html',dict(ipr=ipr,form=form))
|
||||
|
||||
@role_required('Secretariat',)
|
||||
def add_email(request, id):
|
||||
|
@ -276,29 +242,31 @@ def add_email(request, id):
|
|||
else:
|
||||
form = AddEmailForm(ipr=ipr)
|
||||
|
||||
return render('ipr/add_email.html',dict(ipr=ipr,form=form),
|
||||
context_instance=RequestContext(request))
|
||||
return render(request, 'ipr/add_email.html',dict(ipr=ipr,form=form))
|
||||
|
||||
@role_required('Secretariat',)
|
||||
def admin(request,state):
|
||||
def admin(request, state):
|
||||
"""Administrative disclosure listing. For non-posted disclosures"""
|
||||
if state == 'removed':
|
||||
states = ('removed','rejected')
|
||||
else:
|
||||
states = [state]
|
||||
states = IprDisclosureStateName.objects.filter(slug__in=[state, "rejected"] if state == "removed" else [state])
|
||||
if not states:
|
||||
raise Http404
|
||||
|
||||
iprs = IprDisclosureBase.objects.filter(state__in=states).order_by('-time')
|
||||
|
||||
tabs = [('Pending','pending',urlreverse('ipr_admin',kwargs={'state':'pending'}),True),
|
||||
('Removed','removed',urlreverse('ipr_admin',kwargs={'state':'removed'}),True),
|
||||
('Parked','parked',urlreverse('ipr_admin',kwargs={'state':'parked'}),True)]
|
||||
|
||||
template = 'ipr/admin_' + state + '.html'
|
||||
return render(template, {
|
||||
|
||||
tabs = [
|
||||
t + (t[0].lower() == state.lower(),)
|
||||
for t in [
|
||||
('Pending', urlreverse('ipr_admin', kwargs={'state':'pending'})),
|
||||
('Removed', urlreverse('ipr_admin', kwargs={'state':'removed'})),
|
||||
('Parked', urlreverse('ipr_admin', kwargs={'state':'parked'})),
|
||||
]]
|
||||
|
||||
return render(request, 'ipr/admin_list.html', {
|
||||
'iprs': iprs,
|
||||
'tabs': tabs,
|
||||
'selected': state},
|
||||
context_instance=RequestContext(request)
|
||||
)
|
||||
'states': states,
|
||||
'administrative_list': state,
|
||||
})
|
||||
|
||||
@role_required('Secretariat',)
|
||||
def edit(request, id, updates=None):
|
||||
|
@ -306,35 +274,23 @@ def edit(request, id, updates=None):
|
|||
ipr = get_object_or_404(IprDisclosureBase, id=id).get_child()
|
||||
type = class_to_type[ipr.__class__.__name__]
|
||||
|
||||
# only include extra when initial formset is empty
|
||||
if ipr.iprdocrel_set.filter(document__name__startswith='draft'):
|
||||
draft_extra = 0
|
||||
else:
|
||||
draft_extra = 1
|
||||
if ipr.iprdocrel_set.filter(document__name__startswith='rfc'):
|
||||
rfc_extra = 0
|
||||
else:
|
||||
rfc_extra = 1
|
||||
DraftFormset = inlineformset_factory(IprDisclosureBase, IprDocRel, form=DraftForm, can_delete=True, extra=draft_extra)
|
||||
RfcFormset = inlineformset_factory(IprDisclosureBase, IprDocRel, form=RfcForm, can_delete=True, extra=rfc_extra)
|
||||
DraftFormset = inlineformset_factory(IprDisclosureBase, IprDocRel, form=DraftForm, can_delete=True, extra=1)
|
||||
|
||||
if request.method == 'POST':
|
||||
form = ipr_form_mapping[ipr.__class__.__name__](request.POST,instance=ipr)
|
||||
if not type == 'generic':
|
||||
draft_formset = DraftFormset(request.POST, instance=ipr, prefix='draft')
|
||||
rfc_formset = RfcFormset(request.POST, instance=ipr, prefix='rfc')
|
||||
if type != 'generic':
|
||||
draft_formset = DraftFormset(request.POST, instance=ipr)
|
||||
else:
|
||||
draft_formset = None
|
||||
rfc_formset = None
|
||||
|
||||
|
||||
if request.user.is_anonymous():
|
||||
person = Person.objects.get(name="(System)")
|
||||
else:
|
||||
person = request.user.person
|
||||
|
||||
# check formset validity
|
||||
if not type == 'generic':
|
||||
valid_formsets = draft_formset.is_valid() and rfc_formset.is_valid()
|
||||
if type != 'generic':
|
||||
valid_formsets = draft_formset.is_valid()
|
||||
else:
|
||||
valid_formsets = True
|
||||
|
||||
|
@ -343,13 +299,9 @@ def edit(request, id, updates=None):
|
|||
disclosure = form.save(commit=False)
|
||||
disclosure.save()
|
||||
|
||||
if not type == 'generic':
|
||||
# clear and recreate IprDocRels
|
||||
# IprDocRel.objects.filter(disclosure=ipr).delete()
|
||||
draft_formset = DraftFormset(request.POST, instance=disclosure, prefix='draft')
|
||||
if type != 'generic':
|
||||
draft_formset = DraftFormset(request.POST, instance=disclosure)
|
||||
draft_formset.save()
|
||||
rfc_formset = RfcFormset(request.POST, instance=disclosure, prefix='rfc')
|
||||
rfc_formset.save()
|
||||
|
||||
set_disclosure_title(disclosure)
|
||||
disclosure.save()
|
||||
|
@ -369,28 +321,20 @@ def edit(request, id, updates=None):
|
|||
|
||||
messages.success(request,'Disclosure modified')
|
||||
return redirect("ipr_show", id=ipr.id)
|
||||
|
||||
else:
|
||||
# assert False, form.errors
|
||||
pass
|
||||
|
||||
else:
|
||||
if ipr.updates:
|
||||
form = ipr_form_mapping[ipr.__class__.__name__](instance=ipr,initial={'updates':[ x.target for x in ipr.updates ]})
|
||||
else:
|
||||
form = ipr_form_mapping[ipr.__class__.__name__](instance=ipr)
|
||||
#disclosure = IprDisclosureBase() # dummy disclosure for inlineformset
|
||||
dqs=IprDocRel.objects.filter(document__name__startswith='draft')
|
||||
rqs=IprDocRel.objects.filter(document__name__startswith='rfc')
|
||||
draft_formset = DraftFormset(instance=ipr, prefix='draft',queryset=dqs)
|
||||
rfc_formset = RfcFormset(instance=ipr, prefix='rfc',queryset=rqs)
|
||||
|
||||
return render("ipr/details_edit.html", {
|
||||
draft_formset = DraftFormset(instance=ipr, queryset=IprDocRel.objects.all())
|
||||
|
||||
return render(request, "ipr/details_edit.html", {
|
||||
'form': form,
|
||||
'draft_formset':draft_formset,
|
||||
'rfc_formset':rfc_formset,
|
||||
'type':type},
|
||||
context_instance=RequestContext(request)
|
||||
)
|
||||
'type':type
|
||||
})
|
||||
|
||||
@role_required('Secretariat',)
|
||||
def email(request, id):
|
||||
|
@ -441,11 +385,10 @@ def email(request, id):
|
|||
}
|
||||
form = MessageModelForm(initial=initial)
|
||||
|
||||
return render("ipr/email.html", {
|
||||
return render(request, "ipr/email.html", {
|
||||
'ipr': ipr,
|
||||
'form':form},
|
||||
context_instance=RequestContext(request)
|
||||
)
|
||||
'form':form
|
||||
})
|
||||
|
||||
def history(request, id):
|
||||
"""Show the history for a specific IPR disclosure"""
|
||||
|
@ -454,16 +397,12 @@ def history(request, id):
|
|||
if not has_role(request.user, "Secretariat"):
|
||||
events = events.exclude(type='private_comment')
|
||||
|
||||
tabs = [('Disclosure','disclosure',urlreverse('ipr_show',kwargs={'id':id}),True),
|
||||
('History','history',urlreverse('ipr_history',kwargs={'id':id}),True)]
|
||||
|
||||
return render("ipr/details_history.html", {
|
||||
return render(request, "ipr/details_history.html", {
|
||||
'events':events,
|
||||
'ipr': ipr,
|
||||
'tabs':tabs,
|
||||
'selected':'history'},
|
||||
context_instance=RequestContext(request)
|
||||
)
|
||||
'tabs': get_details_tabs(ipr, 'History'),
|
||||
'selected_tab_entry':'history'
|
||||
})
|
||||
|
||||
def iprs_for_drafts_txt(request):
|
||||
docipr = {}
|
||||
|
@ -487,27 +426,25 @@ def iprs_for_drafts_txt(request):
|
|||
def new(request, type, updates=None):
|
||||
"""Submit a new IPR Disclosure. If the updates field != None, this disclosure
|
||||
updates one or more other disclosures."""
|
||||
|
||||
DraftFormset = inlineformset_factory(IprDisclosureBase, IprDocRel, form=DraftForm, can_delete=False, extra=1)
|
||||
RfcFormset = inlineformset_factory(IprDisclosureBase, IprDocRel, form=RfcForm, can_delete=False, extra=1)
|
||||
|
||||
# 1 to show initially + the template
|
||||
DraftFormset = inlineformset_factory(IprDisclosureBase, IprDocRel, form=DraftForm, can_delete=False, extra=1 + 1)
|
||||
|
||||
if request.method == 'POST':
|
||||
form = ipr_form_mapping[type](request.POST)
|
||||
if not type == 'generic':
|
||||
draft_formset = DraftFormset(request.POST, instance=IprDisclosureBase(), prefix='draft')
|
||||
rfc_formset = RfcFormset(request.POST, instance=IprDisclosureBase(), prefix='rfc')
|
||||
if type != 'generic':
|
||||
draft_formset = DraftFormset(request.POST, instance=IprDisclosureBase())
|
||||
else:
|
||||
draft_formset = None
|
||||
rfc_formset = None
|
||||
|
||||
|
||||
if request.user.is_anonymous():
|
||||
person = Person.objects.get(name="(System)")
|
||||
else:
|
||||
person = request.user.person
|
||||
|
||||
# check formset validity
|
||||
if not type == 'generic':
|
||||
valid_formsets = draft_formset.is_valid() and rfc_formset.is_valid()
|
||||
if type != 'generic':
|
||||
valid_formsets = draft_formset.is_valid()
|
||||
else:
|
||||
valid_formsets = True
|
||||
|
||||
|
@ -518,11 +455,9 @@ def new(request, type, updates=None):
|
|||
disclosure.state = IprDisclosureStateName.objects.get(slug='pending')
|
||||
disclosure.save()
|
||||
|
||||
if not type == 'generic':
|
||||
draft_formset = DraftFormset(request.POST, instance=disclosure, prefix='draft')
|
||||
if type != 'generic':
|
||||
draft_formset = DraftFormset(request.POST, instance=disclosure)
|
||||
draft_formset.save()
|
||||
rfc_formset = RfcFormset(request.POST, instance=disclosure, prefix='rfc')
|
||||
rfc_formset.save()
|
||||
|
||||
set_disclosure_title(disclosure)
|
||||
disclosure.save()
|
||||
|
@ -544,7 +479,7 @@ def new(request, type, updates=None):
|
|||
"ipr/new_update_email.txt",
|
||||
{"ipr": disclosure,})
|
||||
|
||||
return render("ipr/submitted.html", context_instance=RequestContext(request))
|
||||
return render(request, "ipr/submitted.html")
|
||||
|
||||
else:
|
||||
if updates:
|
||||
|
@ -552,16 +487,13 @@ def new(request, type, updates=None):
|
|||
else:
|
||||
form = ipr_form_mapping[type]()
|
||||
disclosure = IprDisclosureBase() # dummy disclosure for inlineformset
|
||||
draft_formset = DraftFormset(instance=disclosure, prefix='draft')
|
||||
rfc_formset = RfcFormset(instance=disclosure, prefix='rfc')
|
||||
|
||||
return render("ipr/details_edit.html", {
|
||||
draft_formset = DraftFormset(instance=disclosure)
|
||||
|
||||
return render(request, "ipr/details_edit.html", {
|
||||
'form': form,
|
||||
'draft_formset':draft_formset,
|
||||
'rfc_formset':rfc_formset,
|
||||
'type':type},
|
||||
context_instance=RequestContext(request)
|
||||
)
|
||||
'type':type,
|
||||
})
|
||||
|
||||
@role_required('Secretariat',)
|
||||
def notify(request, id, type):
|
||||
|
@ -597,11 +529,10 @@ def notify(request, id, type):
|
|||
initial = [ {'type':'msgout','text':m} for m in get_posted_emails(ipr) ]
|
||||
formset = NotifyFormset(initial=initial)
|
||||
|
||||
return render("ipr/notify.html", {
|
||||
return render(request, "ipr/notify.html", {
|
||||
'formset': formset,
|
||||
'ipr': ipr},
|
||||
context_instance=RequestContext(request)
|
||||
)
|
||||
'ipr': ipr,
|
||||
})
|
||||
|
||||
@role_required('Secretariat',)
|
||||
def post(request, id):
|
||||
|
@ -731,44 +662,47 @@ def search(request):
|
|||
iprs = sorted(iprs, key=lambda x: x.state.order)
|
||||
else:
|
||||
iprs = sorted(iprs, key=lambda x: (x.time, x.id), reverse=True)
|
||||
|
||||
return render(template, {
|
||||
|
||||
return render(request, template, {
|
||||
"q": q,
|
||||
"iprs": iprs,
|
||||
"docs": docs,
|
||||
"doc": doc,
|
||||
"form":form,
|
||||
"states":states},
|
||||
context_instance=RequestContext(request)
|
||||
)
|
||||
"states":states
|
||||
})
|
||||
|
||||
return HttpResponseRedirect(request.path)
|
||||
|
||||
else:
|
||||
form = SearchForm(initial={'state':['all']})
|
||||
return render("ipr/search.html", {"form":form }, context_instance=RequestContext(request))
|
||||
return render(request, "ipr/search.html", {"form":form })
|
||||
|
||||
def get_details_tabs(ipr, selected):
|
||||
return [
|
||||
t + (t[0].lower() == selected.lower(),)
|
||||
for t in [
|
||||
('Disclosure', urlreverse('ipr_show', kwargs={ 'id': ipr.pk })),
|
||||
('History', urlreverse('ipr_history', kwargs={ 'id': ipr.pk }))
|
||||
]]
|
||||
|
||||
def show(request, id):
|
||||
"""View of individual declaration"""
|
||||
ipr = get_object_or_404(IprDisclosureBase, id=id).get_child()
|
||||
if not has_role(request.user, 'Secretariat'):
|
||||
if ipr.state.slug == 'removed':
|
||||
return render("ipr/removed.html", {
|
||||
'ipr': ipr},
|
||||
context_instance=RequestContext(request)
|
||||
)
|
||||
return render(request, "ipr/removed.html", {
|
||||
'ipr': ipr
|
||||
})
|
||||
elif ipr.state.slug != 'posted':
|
||||
raise Http404
|
||||
|
||||
tabs = [('Disclosure','disclosure',urlreverse('ipr_show',kwargs={'id':id}),True),
|
||||
('History','history',urlreverse('ipr_history',kwargs={'id':id}),True)]
|
||||
|
||||
return render("ipr/details_view.html", {
|
||||
return render(request, "ipr/details_view.html", {
|
||||
'ipr': ipr,
|
||||
'tabs':tabs,
|
||||
'selected':'disclosure'},
|
||||
context_instance=RequestContext(request)
|
||||
)
|
||||
'tabs': get_details_tabs(ipr, 'Disclosure'),
|
||||
'updates_iprs': ipr.relatedipr_source_set.all(),
|
||||
'updated_by_iprs': ipr.relatedipr_target_set.filter(source__state="posted")
|
||||
})
|
||||
|
||||
def showlist(request):
|
||||
"""List all disclosures by type, posted only"""
|
||||
|
@ -781,12 +715,11 @@ def showlist(request):
|
|||
generic = itertools.chain(generic,nondocspecific)
|
||||
generic = sorted(generic, key=lambda x: x.time,reverse=True)
|
||||
|
||||
return render("ipr/list.html", {
|
||||
return render(request, "ipr/list.html", {
|
||||
'generic_disclosures' : generic,
|
||||
'specific_disclosures': specific,
|
||||
'thirdpty_disclosures': thirdpty},
|
||||
context_instance=RequestContext(request)
|
||||
)
|
||||
'thirdpty_disclosures': thirdpty,
|
||||
})
|
||||
|
||||
@role_required('Secretariat',)
|
||||
def state(request, id):
|
||||
|
@ -822,8 +755,7 @@ def state(request, id):
|
|||
else:
|
||||
form = StateForm(initial={'state':ipr.state.pk,'private':True})
|
||||
|
||||
return render('ipr/state.html',dict(ipr=ipr,form=form),
|
||||
context_instance=RequestContext(request))
|
||||
return render(request, 'ipr/state.html', dict(ipr=ipr, form=form))
|
||||
|
||||
# use for link to update specific IPR
|
||||
def update(request, id):
|
||||
|
@ -832,4 +764,4 @@ def update(request, id):
|
|||
ipr = get_object_or_404(IprDisclosureBase,id=id)
|
||||
child = ipr.get_child()
|
||||
type = class_to_type[child.__class__.__name__]
|
||||
return new(request, type, updates=id)
|
||||
return new(request, type, updates=id)
|
||||
|
|
50
ietf/liaisons/fields.py
Normal file
50
ietf/liaisons/fields.py
Normal file
|
@ -0,0 +1,50 @@
|
|||
import json
|
||||
|
||||
from django.utils.html import escape
|
||||
from django import forms
|
||||
from django.core.urlresolvers import reverse as urlreverse
|
||||
|
||||
from ietf.liaisons.models import LiaisonStatement
|
||||
|
||||
def select2_id_liaison_json(objs):
|
||||
return json.dumps([{ "id": o.pk, "text": escape(o.title) } for o in objs])
|
||||
|
||||
class SearchableLiaisonStatementField(forms.IntegerField):
|
||||
"""Server-based multi-select field for choosing liaison statements using
|
||||
select2.js."""
|
||||
|
||||
def __init__(self, hint_text="Type in title to search for document", *args, **kwargs):
|
||||
super(SearchableLiaisonStatementField, self).__init__(*args, **kwargs)
|
||||
|
||||
self.widget.attrs["class"] = "select2-field"
|
||||
self.widget.attrs["data-placeholder"] = hint_text
|
||||
self.widget.attrs["data-max-entries"] = 1
|
||||
|
||||
def prepare_value(self, value):
|
||||
if not value:
|
||||
value = None
|
||||
elif isinstance(value, LiaisonStatement):
|
||||
value = value
|
||||
else:
|
||||
value = LiaisonStatement.objects.exclude(approved=None).filter(pk=value).first()
|
||||
|
||||
self.widget.attrs["data-pre"] = select2_id_liaison_json([value] if value else [])
|
||||
|
||||
# doing this in the constructor is difficult because the URL
|
||||
# patterns may not have been fully constructed there yet
|
||||
self.widget.attrs["data-ajax-url"] = urlreverse("ajax_select2_search_liaison_statements")
|
||||
|
||||
return value
|
||||
|
||||
def clean(self, value):
|
||||
value = super(SearchableLiaisonStatementField, self).clean(value)
|
||||
|
||||
if value == None:
|
||||
return None
|
||||
|
||||
obj = LiaisonStatement.objects.filter(pk=value).first()
|
||||
if not obj and self.required:
|
||||
raise forms.ValidationError(u"You must select a value.")
|
||||
|
||||
return obj
|
||||
|
|
@ -11,11 +11,13 @@ from ietf.liaisons.accounts import (can_add_outgoing_liaison, can_add_incoming_l
|
|||
get_person_for_user, is_secretariat, is_sdo_liaison_manager)
|
||||
from ietf.liaisons.utils import IETFHM
|
||||
from ietf.liaisons.widgets import (FromWidget, ReadOnlyWidget, ButtonWidget,
|
||||
ShowAttachmentsWidget, RelatedLiaisonWidget)
|
||||
ShowAttachmentsWidget)
|
||||
from ietf.liaisons.models import LiaisonStatement, LiaisonStatementPurposeName
|
||||
from ietf.liaisons.fields import SearchableLiaisonStatementField
|
||||
from ietf.group.models import Group, Role
|
||||
from ietf.person.models import Person, Email
|
||||
from ietf.doc.models import Document
|
||||
from ietf.utils.fields import DatepickerDateField
|
||||
|
||||
|
||||
class LiaisonForm(forms.Form):
|
||||
|
@ -26,10 +28,11 @@ class LiaisonForm(forms.Form):
|
|||
to_poc = forms.CharField(widget=ReadOnlyWidget, label="POC", required=False)
|
||||
response_contact = forms.CharField(required=False, max_length=255)
|
||||
technical_contact = forms.CharField(required=False, max_length=255)
|
||||
cc1 = forms.CharField(widget=forms.Textarea, label="CC", required=False, help_text='Please insert one email address per line')
|
||||
cc1 = forms.CharField(widget=forms.Textarea, label="CC", required=False, help_text='Please insert one email address per line.')
|
||||
purpose = forms.ChoiceField()
|
||||
deadline_date = forms.DateField(label='Deadline')
|
||||
submitted_date = forms.DateField(label='Submission date', initial=datetime.date.today())
|
||||
related_to = SearchableLiaisonStatementField(label=u'Related Liaison Statement', required=False)
|
||||
deadline_date = DatepickerDateField(date_format="yyyy-mm-dd", picker_settings={"autoclose": "1" }, label='Deadline', required=True)
|
||||
submitted_date = DatepickerDateField(date_format="yyyy-mm-dd", picker_settings={"autoclose": "1" }, label='Submission date', required=True, initial=datetime.date.today())
|
||||
title = forms.CharField(label=u'Title')
|
||||
body = forms.CharField(widget=forms.Textarea, required=False)
|
||||
attachments = forms.CharField(label='Attachments', widget=ShowAttachmentsWidget, required=False)
|
||||
|
@ -40,13 +43,12 @@ class LiaisonForm(forms.Form):
|
|||
require=['id_attach_title', 'id_attach_file'],
|
||||
required_label='title and file'),
|
||||
required=False)
|
||||
related_to = forms.ModelChoiceField(LiaisonStatement.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', 'deadline_date')),
|
||||
('References', ('related_to', )),
|
||||
('Reference', ('related_to', )),
|
||||
('Liaison Statement', ('title', 'submitted_date', 'body', 'attachments')),
|
||||
('Add attachment', ('attach_title', 'attach_file', 'attach_button')),
|
||||
]
|
||||
|
@ -80,7 +82,7 @@ class LiaisonForm(forms.Form):
|
|||
self.initial["title"] = self.instance.title
|
||||
self.initial["body"] = self.instance.body
|
||||
self.initial["attachments"] = self.instance.attachments.all()
|
||||
self.initial["related_to"] = self.instance.related_to_id
|
||||
self.initial["related_to"] = self.instance.related_to
|
||||
if "approved" in self.fields:
|
||||
self.initial["approved"] = bool(self.instance.approved)
|
||||
|
||||
|
|
264
ietf/liaisons/migrations/0001_initial.py
Normal file
264
ietf/liaisons/migrations/0001_initial.py
Normal file
|
@ -0,0 +1,264 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from south.v2 import SchemaMigration
|
||||
|
||||
|
||||
class Migration(SchemaMigration):
|
||||
|
||||
def forwards(self, orm):
|
||||
# Adding model 'LiaisonStatement'
|
||||
# db.create_table(u'liaisons_liaisonstatement', (
|
||||
# (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
|
||||
# ('title', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)),
|
||||
# ('purpose', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['name.LiaisonStatementPurposeName'])),
|
||||
# ('body', self.gf('django.db.models.fields.TextField')(blank=True)),
|
||||
# ('deadline', self.gf('django.db.models.fields.DateField')(null=True, blank=True)),
|
||||
# ('related_to', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['liaisons.LiaisonStatement'], null=True, blank=True)),
|
||||
# ('from_group', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='liaisonstatement_from_set', null=True, to=orm['group.Group'])),
|
||||
# ('from_name', self.gf('django.db.models.fields.CharField')(max_length=255)),
|
||||
# ('from_contact', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['person.Email'], null=True, blank=True)),
|
||||
# ('to_group', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='liaisonstatement_to_set', null=True, to=orm['group.Group'])),
|
||||
# ('to_name', self.gf('django.db.models.fields.CharField')(max_length=255)),
|
||||
# ('to_contact', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)),
|
||||
# ('reply_to', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)),
|
||||
# ('response_contact', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)),
|
||||
# ('technical_contact', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)),
|
||||
# ('cc', self.gf('django.db.models.fields.TextField')(blank=True)),
|
||||
# ('submitted', self.gf('django.db.models.fields.DateTimeField')(null=True, blank=True)),
|
||||
# ('modified', self.gf('django.db.models.fields.DateTimeField')(null=True, blank=True)),
|
||||
# ('approved', self.gf('django.db.models.fields.DateTimeField')(null=True, blank=True)),
|
||||
# ('action_taken', self.gf('django.db.models.fields.BooleanField')(default=False)),
|
||||
# ))
|
||||
# db.send_create_signal(u'liaisons', ['LiaisonStatement'])
|
||||
|
||||
# # Adding M2M table for field attachments on 'LiaisonStatement'
|
||||
# m2m_table_name = db.shorten_name(u'liaisons_liaisonstatement_attachments')
|
||||
# db.create_table(m2m_table_name, (
|
||||
# ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
|
||||
# ('liaisonstatement', models.ForeignKey(orm[u'liaisons.liaisonstatement'], null=False)),
|
||||
# ('document', models.ForeignKey(orm[u'doc.document'], null=False))
|
||||
# ))
|
||||
# db.create_unique(m2m_table_name, ['liaisonstatement_id', 'document_id'])
|
||||
pass
|
||||
|
||||
|
||||
def backwards(self, orm):
|
||||
# # Deleting model 'LiaisonStatement'
|
||||
# db.delete_table(u'liaisons_liaisonstatement')
|
||||
|
||||
# # Removing M2M table for field attachments on 'LiaisonStatement'
|
||||
# db.delete_table(db.shorten_name(u'liaisons_liaisonstatement_attachments'))
|
||||
pass
|
||||
|
||||
models = {
|
||||
u'auth.group': {
|
||||
'Meta': {'object_name': 'Group'},
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
|
||||
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
|
||||
},
|
||||
u'auth.permission': {
|
||||
'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
|
||||
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
|
||||
},
|
||||
u'auth.user': {
|
||||
'Meta': {'object_name': 'User'},
|
||||
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
|
||||
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
|
||||
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}),
|
||||
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'})
|
||||
},
|
||||
u'contenttypes.contenttype': {
|
||||
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
|
||||
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
u'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'})
|
||||
},
|
||||
u'doc.document': {
|
||||
'Meta': {'object_name': 'Document'},
|
||||
'abstract': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'ad': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'ad_document_set'", 'null': 'True', 'to': u"orm['person.Person']"}),
|
||||
'authors': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['person.Email']", 'symmetrical': 'False', 'through': u"orm['doc.DocumentAuthor']", 'blank': 'True'}),
|
||||
'expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'external_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}),
|
||||
'group': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['group.Group']", 'null': 'True', 'blank': 'True'}),
|
||||
'intended_std_level': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.IntendedStdLevelName']", 'null': 'True', 'blank': 'True'}),
|
||||
'internal_comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'primary_key': 'True'}),
|
||||
'note': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'notify': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
|
||||
'order': ('django.db.models.fields.IntegerField', [], {'default': '1', 'blank': 'True'}),
|
||||
'pages': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'rev': ('django.db.models.fields.CharField', [], {'max_length': '16', 'blank': 'True'}),
|
||||
'shepherd': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'shepherd_document_set'", 'null': 'True', 'to': u"orm['person.Email']"}),
|
||||
'states': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['doc.State']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'std_level': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.StdLevelName']", 'null': 'True', 'blank': 'True'}),
|
||||
'stream': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.StreamName']", 'null': 'True', 'blank': 'True'}),
|
||||
'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': u"orm['name.DocTagName']", 'null': 'True', 'blank': 'True'}),
|
||||
'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.DocTypeName']", 'null': 'True', 'blank': 'True'})
|
||||
},
|
||||
u'doc.documentauthor': {
|
||||
'Meta': {'ordering': "['document', 'order']", 'object_name': 'DocumentAuthor'},
|
||||
'author': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['person.Email']"}),
|
||||
'document': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['doc.Document']"}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'order': ('django.db.models.fields.IntegerField', [], {'default': '1'})
|
||||
},
|
||||
u'doc.state': {
|
||||
'Meta': {'ordering': "['type', 'order']", 'object_name': 'State'},
|
||||
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'next_states': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'previous_states'", 'blank': 'True', 'to': u"orm['doc.State']"}),
|
||||
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50'}),
|
||||
'type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['doc.StateType']"}),
|
||||
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
|
||||
},
|
||||
u'doc.statetype': {
|
||||
'Meta': {'object_name': 'StateType'},
|
||||
'label': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'slug': ('django.db.models.fields.CharField', [], {'max_length': '30', 'primary_key': 'True'})
|
||||
},
|
||||
u'group.group': {
|
||||
'Meta': {'object_name': 'Group'},
|
||||
'acronym': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '40'}),
|
||||
'ad': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['person.Person']", 'null': 'True', 'blank': 'True'}),
|
||||
'charter': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'chartered_group'", 'unique': 'True', 'null': 'True', 'to': u"orm['doc.Document']"}),
|
||||
'comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'list_archive': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
|
||||
'list_email': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
|
||||
'list_subscribe': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '80'}),
|
||||
'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['group.Group']", 'null': 'True', 'blank': 'True'}),
|
||||
'state': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.GroupStateName']", 'null': 'True'}),
|
||||
'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.GroupTypeName']", 'null': 'True'}),
|
||||
'unused_states': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['doc.State']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'unused_tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['name.DocTagName']", 'symmetrical': 'False', 'blank': 'True'})
|
||||
},
|
||||
u'liaisons.liaisonstatement': {
|
||||
'Meta': {'object_name': 'LiaisonStatement'},
|
||||
'action_taken': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'approved': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'attachments': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['doc.Document']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'body': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'cc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'deadline': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'from_contact': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['person.Email']", 'null': 'True', 'blank': 'True'}),
|
||||
'from_group': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'liaisonstatement_from_set'", 'null': 'True', 'to': u"orm['group.Group']"}),
|
||||
'from_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'modified': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'purpose': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.LiaisonStatementPurposeName']"}),
|
||||
'related_to': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['liaisons.LiaisonStatement']", 'null': 'True', 'blank': 'True'}),
|
||||
'reply_to': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
|
||||
'response_contact': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
|
||||
'submitted': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'technical_contact': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
|
||||
'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
|
||||
'to_contact': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
|
||||
'to_group': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'liaisonstatement_to_set'", 'null': 'True', 'to': u"orm['group.Group']"}),
|
||||
'to_name': ('django.db.models.fields.CharField', [], {'max_length': '255'})
|
||||
},
|
||||
u'name.doctagname': {
|
||||
'Meta': {'ordering': "['order']", 'object_name': 'DocTagName'},
|
||||
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
|
||||
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
|
||||
},
|
||||
u'name.doctypename': {
|
||||
'Meta': {'ordering': "['order']", 'object_name': 'DocTypeName'},
|
||||
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
|
||||
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
|
||||
},
|
||||
u'name.groupstatename': {
|
||||
'Meta': {'ordering': "['order']", 'object_name': 'GroupStateName'},
|
||||
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
|
||||
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
|
||||
},
|
||||
u'name.grouptypename': {
|
||||
'Meta': {'ordering': "['order']", 'object_name': 'GroupTypeName'},
|
||||
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
|
||||
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
|
||||
},
|
||||
u'name.intendedstdlevelname': {
|
||||
'Meta': {'ordering': "['order']", 'object_name': 'IntendedStdLevelName'},
|
||||
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
|
||||
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
|
||||
},
|
||||
u'name.liaisonstatementpurposename': {
|
||||
'Meta': {'ordering': "['order']", 'object_name': 'LiaisonStatementPurposeName'},
|
||||
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
|
||||
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
|
||||
},
|
||||
u'name.stdlevelname': {
|
||||
'Meta': {'ordering': "['order']", 'object_name': 'StdLevelName'},
|
||||
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
|
||||
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
|
||||
},
|
||||
u'name.streamname': {
|
||||
'Meta': {'ordering': "['order']", 'object_name': 'StreamName'},
|
||||
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
|
||||
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
|
||||
},
|
||||
u'person.email': {
|
||||
'Meta': {'object_name': 'Email'},
|
||||
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'address': ('django.db.models.fields.CharField', [], {'max_length': '64', 'primary_key': 'True'}),
|
||||
'person': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['person.Person']", 'null': 'True'}),
|
||||
'time': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'})
|
||||
},
|
||||
u'person.person': {
|
||||
'Meta': {'object_name': 'Person'},
|
||||
'address': ('django.db.models.fields.TextField', [], {'max_length': '255', 'blank': 'True'}),
|
||||
'affiliation': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
|
||||
'ascii': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'ascii_short': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
|
||||
'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'user': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['auth.User']", 'unique': 'True', 'null': 'True', 'blank': 'True'})
|
||||
}
|
||||
}
|
||||
|
||||
complete_apps = ['liaisons']
|
229
ietf/liaisons/migrations/0002_add_missing_titles.py
Normal file
229
ietf/liaisons/migrations/0002_add_missing_titles.py
Normal file
|
@ -0,0 +1,229 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from south.v2 import DataMigration
|
||||
|
||||
class Migration(DataMigration):
|
||||
|
||||
def forwards(self, orm):
|
||||
for l in orm.LiaisonStatement.objects.filter(title=""):
|
||||
a = l.attachments.all().first()
|
||||
if a:
|
||||
l.title = a.title
|
||||
l.save()
|
||||
|
||||
def backwards(self, orm):
|
||||
pass
|
||||
|
||||
models = {
|
||||
u'auth.group': {
|
||||
'Meta': {'object_name': 'Group'},
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
|
||||
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
|
||||
},
|
||||
u'auth.permission': {
|
||||
'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
|
||||
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
|
||||
},
|
||||
u'auth.user': {
|
||||
'Meta': {'object_name': 'User'},
|
||||
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
|
||||
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
|
||||
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}),
|
||||
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'})
|
||||
},
|
||||
u'contenttypes.contenttype': {
|
||||
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
|
||||
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
u'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'})
|
||||
},
|
||||
u'doc.document': {
|
||||
'Meta': {'object_name': 'Document'},
|
||||
'abstract': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'ad': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'ad_document_set'", 'null': 'True', 'to': u"orm['person.Person']"}),
|
||||
'authors': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['person.Email']", 'symmetrical': 'False', 'through': u"orm['doc.DocumentAuthor']", 'blank': 'True'}),
|
||||
'expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'external_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}),
|
||||
'group': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['group.Group']", 'null': 'True', 'blank': 'True'}),
|
||||
'intended_std_level': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.IntendedStdLevelName']", 'null': 'True', 'blank': 'True'}),
|
||||
'internal_comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'primary_key': 'True'}),
|
||||
'note': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'notify': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
|
||||
'order': ('django.db.models.fields.IntegerField', [], {'default': '1', 'blank': 'True'}),
|
||||
'pages': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'rev': ('django.db.models.fields.CharField', [], {'max_length': '16', 'blank': 'True'}),
|
||||
'shepherd': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'shepherd_document_set'", 'null': 'True', 'to': u"orm['person.Email']"}),
|
||||
'states': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['doc.State']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'std_level': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.StdLevelName']", 'null': 'True', 'blank': 'True'}),
|
||||
'stream': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.StreamName']", 'null': 'True', 'blank': 'True'}),
|
||||
'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': u"orm['name.DocTagName']", 'null': 'True', 'blank': 'True'}),
|
||||
'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.DocTypeName']", 'null': 'True', 'blank': 'True'})
|
||||
},
|
||||
u'doc.documentauthor': {
|
||||
'Meta': {'ordering': "['document', 'order']", 'object_name': 'DocumentAuthor'},
|
||||
'author': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['person.Email']"}),
|
||||
'document': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['doc.Document']"}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'order': ('django.db.models.fields.IntegerField', [], {'default': '1'})
|
||||
},
|
||||
u'doc.state': {
|
||||
'Meta': {'ordering': "['type', 'order']", 'object_name': 'State'},
|
||||
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'next_states': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'previous_states'", 'blank': 'True', 'to': u"orm['doc.State']"}),
|
||||
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50'}),
|
||||
'type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['doc.StateType']"}),
|
||||
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
|
||||
},
|
||||
u'doc.statetype': {
|
||||
'Meta': {'object_name': 'StateType'},
|
||||
'label': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'slug': ('django.db.models.fields.CharField', [], {'max_length': '30', 'primary_key': 'True'})
|
||||
},
|
||||
u'group.group': {
|
||||
'Meta': {'object_name': 'Group'},
|
||||
'acronym': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '40'}),
|
||||
'ad': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['person.Person']", 'null': 'True', 'blank': 'True'}),
|
||||
'charter': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'chartered_group'", 'unique': 'True', 'null': 'True', 'to': u"orm['doc.Document']"}),
|
||||
'comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'list_archive': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
|
||||
'list_email': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
|
||||
'list_subscribe': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '80'}),
|
||||
'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['group.Group']", 'null': 'True', 'blank': 'True'}),
|
||||
'state': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.GroupStateName']", 'null': 'True'}),
|
||||
'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.GroupTypeName']", 'null': 'True'}),
|
||||
'unused_states': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['doc.State']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'unused_tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['name.DocTagName']", 'symmetrical': 'False', 'blank': 'True'})
|
||||
},
|
||||
u'liaisons.liaisonstatement': {
|
||||
'Meta': {'object_name': 'LiaisonStatement'},
|
||||
'action_taken': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'approved': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'attachments': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['doc.Document']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'body': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'cc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'deadline': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'from_contact': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['person.Email']", 'null': 'True', 'blank': 'True'}),
|
||||
'from_group': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'liaisonstatement_from_set'", 'null': 'True', 'to': u"orm['group.Group']"}),
|
||||
'from_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'modified': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'purpose': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['name.LiaisonStatementPurposeName']"}),
|
||||
'related_to': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['liaisons.LiaisonStatement']", 'null': 'True', 'blank': 'True'}),
|
||||
'reply_to': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
|
||||
'response_contact': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
|
||||
'submitted': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'technical_contact': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
|
||||
'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
|
||||
'to_contact': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
|
||||
'to_group': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'liaisonstatement_to_set'", 'null': 'True', 'to': u"orm['group.Group']"}),
|
||||
'to_name': ('django.db.models.fields.CharField', [], {'max_length': '255'})
|
||||
},
|
||||
u'name.doctagname': {
|
||||
'Meta': {'ordering': "['order']", 'object_name': 'DocTagName'},
|
||||
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
|
||||
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
|
||||
},
|
||||
u'name.doctypename': {
|
||||
'Meta': {'ordering': "['order']", 'object_name': 'DocTypeName'},
|
||||
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
|
||||
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
|
||||
},
|
||||
u'name.groupstatename': {
|
||||
'Meta': {'ordering': "['order']", 'object_name': 'GroupStateName'},
|
||||
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
|
||||
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
|
||||
},
|
||||
u'name.grouptypename': {
|
||||
'Meta': {'ordering': "['order']", 'object_name': 'GroupTypeName'},
|
||||
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
|
||||
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
|
||||
},
|
||||
u'name.intendedstdlevelname': {
|
||||
'Meta': {'ordering': "['order']", 'object_name': 'IntendedStdLevelName'},
|
||||
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
|
||||
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
|
||||
},
|
||||
u'name.liaisonstatementpurposename': {
|
||||
'Meta': {'ordering': "['order']", 'object_name': 'LiaisonStatementPurposeName'},
|
||||
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
|
||||
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
|
||||
},
|
||||
u'name.stdlevelname': {
|
||||
'Meta': {'ordering': "['order']", 'object_name': 'StdLevelName'},
|
||||
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
|
||||
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
|
||||
},
|
||||
u'name.streamname': {
|
||||
'Meta': {'ordering': "['order']", 'object_name': 'StreamName'},
|
||||
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
|
||||
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
|
||||
},
|
||||
u'person.email': {
|
||||
'Meta': {'object_name': 'Email'},
|
||||
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'address': ('django.db.models.fields.CharField', [], {'max_length': '64', 'primary_key': 'True'}),
|
||||
'person': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['person.Person']", 'null': 'True'}),
|
||||
'time': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'})
|
||||
},
|
||||
u'person.person': {
|
||||
'Meta': {'object_name': 'Person'},
|
||||
'address': ('django.db.models.fields.TextField', [], {'max_length': '255', 'blank': 'True'}),
|
||||
'affiliation': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
|
||||
'ascii': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'ascii_short': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
|
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
|
||||
'time': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'user': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['auth.User']", 'unique': 'True', 'null': 'True', 'blank': 'True'})
|
||||
}
|
||||
}
|
||||
|
||||
complete_apps = ['liaisons']
|
||||
symmetrical = True
|
0
ietf/liaisons/migrations/__init__.py
Normal file
0
ietf/liaisons/migrations/__init__.py
Normal file
|
@ -16,12 +16,12 @@ class LiaisonStatement(models.Model):
|
|||
|
||||
related_to = models.ForeignKey('LiaisonStatement', blank=True, null=True)
|
||||
|
||||
from_group = models.ForeignKey(Group, related_name="liaisonstatement_from_set", null=True, blank=True, help_text="Sender group, if it exists")
|
||||
from_name = models.CharField(max_length=255, help_text="Name of the sender body")
|
||||
from_group = models.ForeignKey(Group, related_name="liaisonstatement_from_set", null=True, blank=True, help_text="Sender group, if it exists.")
|
||||
from_name = models.CharField(max_length=255, help_text="Name of the sender body.")
|
||||
from_contact = models.ForeignKey(Email, blank=True, null=True)
|
||||
to_group = models.ForeignKey(Group, related_name="liaisonstatement_to_set", null=True, blank=True, help_text="Recipient group, if it exists")
|
||||
to_name = models.CharField(max_length=255, help_text="Name of the recipient body")
|
||||
to_contact = models.CharField(blank=True, max_length=255, help_text="Contacts at recipient body")
|
||||
to_group = models.ForeignKey(Group, related_name="liaisonstatement_to_set", null=True, blank=True, help_text="Recipient group, if it exists.")
|
||||
to_name = models.CharField(max_length=255, help_text="Name of the recipient body.")
|
||||
to_contact = models.CharField(blank=True, max_length=255, help_text="Contacts at recipient body.")
|
||||
|
||||
reply_to = models.CharField(blank=True, max_length=255)
|
||||
|
||||
|
@ -49,4 +49,4 @@ class LiaisonStatement(models.Model):
|
|||
return slugify("liaison" + " " + self.submitted.strftime("%Y-%m-%d") + " " + frm[:50] + " " + to[:50] + " " + self.title[:115])
|
||||
|
||||
def __unicode__(self):
|
||||
return self.title or u"<no title>"
|
||||
return self.title
|
||||
|
|
|
@ -5,7 +5,7 @@ from django.views.generic import RedirectView, TemplateView
|
|||
|
||||
urlpatterns = patterns('',
|
||||
(r'^help/$', TemplateView.as_view(template_name='liaisons/help.html')),
|
||||
(r'^help/fields/$', TemplateView.as_view(template_name='liaisons/field_help.html')),
|
||||
url(r'^help/fields/$', TemplateView.as_view(template_name='liaisons/field_help.html'), name="liaisons_field_help"),
|
||||
(r'^help/from_ietf/$', TemplateView.as_view(template_name='liaisons/guide_from_ietf.html')),
|
||||
(r'^help/to_ietf/$', TemplateView.as_view(template_name='liaisons/guide_to_ietf.html')),
|
||||
(r'^managers/$', RedirectView.as_view(url='http://www.ietf.org/liaison/managers.html')),
|
||||
|
@ -18,6 +18,6 @@ urlpatterns += patterns('ietf.liaisons.views',
|
|||
url(r'^for_approval/$', 'liaison_approval_list', name='liaison_approval_list'),
|
||||
url(r'^for_approval/(?P<object_id>\d+)/$', 'liaison_approval_detail', name='liaison_approval_detail'),
|
||||
url(r'^add/$', 'add_liaison', name='add_liaison'),
|
||||
url(r'^ajax/get_info/$', 'get_info'),
|
||||
url(r'^ajax/liaison_list/$', 'ajax_liaison_list', name='ajax_liaison_list'),
|
||||
url(r'^ajax/get_info/$', 'ajax_get_liaison_info'),
|
||||
url(r'^ajax/select2search/$', 'ajax_select2_search_liaison_statements', name='ajax_select2_search_liaison_statements'),
|
||||
)
|
||||
|
|
|
@ -16,6 +16,7 @@ from ietf.liaisons.accounts import (get_person_for_user, can_add_outgoing_liaiso
|
|||
from ietf.liaisons.forms import liaison_form_factory
|
||||
from ietf.liaisons.utils import IETFHM, can_submit_liaison_required, approvable_liaison_statements
|
||||
from ietf.liaisons.mails import notify_pending_by_email, send_liaison_by_email
|
||||
from ietf.liaisons.fields import select2_id_liaison_json
|
||||
|
||||
|
||||
|
||||
|
@ -44,7 +45,7 @@ def add_liaison(request, liaison=None):
|
|||
|
||||
|
||||
@can_submit_liaison_required
|
||||
def get_info(request):
|
||||
def ajax_get_liaison_info(request):
|
||||
person = get_person_for_user(request.user)
|
||||
|
||||
to_entity_id = request.GET.get('to_entity_id', None)
|
||||
|
@ -110,14 +111,20 @@ def liaison_list(request):
|
|||
"sort": sort,
|
||||
}, context_instance=RequestContext(request))
|
||||
|
||||
def ajax_liaison_list(request):
|
||||
sort, order_by = normalize_sort(request)
|
||||
liaisons = LiaisonStatement.objects.exclude(approved=None).order_by(order_by)
|
||||
def ajax_select2_search_liaison_statements(request):
|
||||
q = [w.strip() for w in request.GET.get('q', '').split() if w.strip()]
|
||||
|
||||
return render_to_response('liaisons/liaison_table.html', {
|
||||
"liaisons": liaisons,
|
||||
"sort": sort,
|
||||
}, context_instance=RequestContext(request))
|
||||
if not q:
|
||||
objs = LiaisonStatement.objects.none()
|
||||
else:
|
||||
qs = LiaisonStatement.objects.exclude(approved=None).all()
|
||||
|
||||
for t in q:
|
||||
qs = qs.filter(title__icontains=t)
|
||||
|
||||
objs = qs.distinct().order_by("-id")[:20]
|
||||
|
||||
return HttpResponse(select2_id_liaison_json(objs), content_type='application/json')
|
||||
|
||||
@can_submit_liaison_required
|
||||
def liaison_approval_list(request):
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
from django.conf import settings
|
||||
from django.core.urlresolvers import reverse as urlreverse
|
||||
from django.db.models.query import QuerySet
|
||||
from django.forms.widgets import Select, Widget, TextInput
|
||||
from django.forms.widgets import Select, Widget
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.html import conditional_escape
|
||||
|
||||
from ietf.liaisons.models import LiaisonStatement
|
||||
|
||||
|
||||
class FromWidget(Select):
|
||||
|
||||
|
@ -28,7 +25,7 @@ class FromWidget(Select):
|
|||
value = option[0]
|
||||
text = option[1]
|
||||
base = u'<input type="hidden" value="%s" id="id_%s" name="%s" />%s' % (conditional_escape(value), conditional_escape(name), conditional_escape(name), conditional_escape(text))
|
||||
base += u' (<a class="from_mailto" href="">' + conditional_escape(self.submitter) + u'</a>)'
|
||||
base += u' <a class="from_mailto form-control" href="">' + conditional_escape(self.submitter) + u'</a>'
|
||||
if self.full_power_on:
|
||||
base += '<div style="display: none;" class="reducedToOptions">'
|
||||
for from_code in self.full_power_on:
|
||||
|
@ -40,9 +37,8 @@ class FromWidget(Select):
|
|||
|
||||
|
||||
class ReadOnlyWidget(Widget):
|
||||
|
||||
def render(self, name, value, attrs=None):
|
||||
html = u'<div id="id_%s">%s</div>' % (conditional_escape(name), conditional_escape(value or ''))
|
||||
html = u'<div id="id_%s" class="form-control" style="height: auto; min-height: 34px;">%s</div>' % (conditional_escape(name), conditional_escape(value or ''))
|
||||
return mark_safe(html)
|
||||
|
||||
|
||||
|
@ -63,7 +59,7 @@ class ButtonWidget(Widget):
|
|||
html += u'<span style="display: none" class="attachRequiredField">%s</span>' % conditional_escape(i)
|
||||
required_str = u'Please fill in %s to attach a new file' % conditional_escape(self.required_label)
|
||||
html += u'<span style="display: none" class="attachDisabledLabel">%s</span>' % conditional_escape(required_str)
|
||||
html += u'<input type="button" class="addAttachmentWidget" value="%s" />' % conditional_escape(self.label)
|
||||
html += u'<input type="button" class="addAttachmentWidget btn btn-primary btn-sm" value="%s" />' % conditional_escape(self.label)
|
||||
return mark_safe(html)
|
||||
|
||||
|
||||
|
@ -71,8 +67,8 @@ class ShowAttachmentsWidget(Widget):
|
|||
|
||||
def render(self, name, value, attrs=None):
|
||||
html = u'<div id="id_%s">' % name
|
||||
html += u'<span style="display: none" class="showAttachmentsEmpty">No files attached</span>'
|
||||
html += u'<div class="attachedFiles">'
|
||||
html += u'<span style="display: none" class="showAttachmentsEmpty form-control" style="height: auto; min-height: 34px;">No files attached</span>'
|
||||
html += u'<div class="attachedFiles form-control" style="height: auto; min-height: 34px;">'
|
||||
if value and isinstance(value, QuerySet):
|
||||
for attachment in value:
|
||||
html += u'<a class="initialAttach" href="%s%s">%s</a><br />' % (settings.LIAISON_ATTACH_URL, conditional_escape(attachment.external_url), conditional_escape(attachment.title))
|
||||
|
@ -80,32 +76,3 @@ class ShowAttachmentsWidget(Widget):
|
|||
html += u'No files attached'
|
||||
html += u'</div></div>'
|
||||
return mark_safe(html)
|
||||
|
||||
|
||||
class RelatedLiaisonWidget(TextInput):
|
||||
|
||||
def render(self, name, value, attrs=None):
|
||||
if not value:
|
||||
value = ''
|
||||
title = ''
|
||||
noliaison = 'inline'
|
||||
deselect = 'none'
|
||||
else:
|
||||
liaison = LiaisonStatement.objects.get(pk=value)
|
||||
title = liaison.title
|
||||
if not title:
|
||||
attachments = liaison.attachments.all()
|
||||
if attachments:
|
||||
title = attachments[0].title
|
||||
else:
|
||||
title = 'Liaison #%s' % liaison.pk
|
||||
noliaison = 'none'
|
||||
deselect = 'inline'
|
||||
html = u'<span class="noRelated" style="display: %s;">No liaison selected</span>' % conditional_escape(noliaison)
|
||||
html += u'<span class="relatedLiaisonWidgetTitle">%s</span>' % conditional_escape(title)
|
||||
html += u'<input type="hidden" name="%s" class="relatedLiaisonWidgetValue" value="%s" /> ' % (conditional_escape(name), conditional_escape(value))
|
||||
html += u'<span style="display: none;" class="listURL">%s</span> ' % urlreverse('ajax_liaison_list')
|
||||
html += u'<div style="display: none;" class="relatedLiaisonWidgetDialog" id="related-dialog" title="Select a liaison"></div> '
|
||||
html += '<input type="button" id="id_%s" value="Select liaison" /> ' % conditional_escape(name)
|
||||
html += '<input type="button" style="display: %s;" id="id_no_%s" value="Deselect liaison" />' % (conditional_escape(deselect), conditional_escape(name))
|
||||
return mark_safe(html)
|
||||
|
|
|
@ -18,13 +18,13 @@ class MailingListTests(TestCase):
|
|||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
self.assertEqual(len(q(".group-archives a:contains(\"%s\")" % group.acronym)), 0)
|
||||
self.assertEqual(len(q(".content-wrapper a:contains(\"%s\")" % group.acronym)), 0)
|
||||
|
||||
# successful get
|
||||
group.list_archive = "https://example.com/foo"
|
||||
group.save()
|
||||
r = self.client.get(url)
|
||||
q = PyQuery(r.content)
|
||||
self.assertEqual(len(q(".group-archives a:contains(\"%s\")" % group.acronym)), 1)
|
||||
self.assertEqual(len(q(".content-wrapper a:contains(\"%s\")" % group.acronym)), 1)
|
||||
|
||||
|
||||
|
|
|
@ -59,7 +59,7 @@ class Meeting(models.Model):
|
|||
venue_addr = models.TextField(blank=True)
|
||||
break_area = models.CharField(blank=True, max_length=255)
|
||||
reg_area = models.CharField(blank=True, max_length=255)
|
||||
agenda_note = models.TextField(blank=True, help_text="Text in this field will be placed at the top of the html agenda page for the meeting. HTML can be used, but will not validated.")
|
||||
agenda_note = models.TextField(blank=True, help_text="Text in this field will be placed at the top of the html agenda page for the meeting. HTML can be used, but will not be validated.")
|
||||
agenda = models.ForeignKey('Schedule',null=True,blank=True, related_name='+')
|
||||
session_request_lock_message = models.CharField(blank=True,max_length=255) # locked if not empty
|
||||
|
||||
|
@ -298,8 +298,8 @@ class TimeSlot(models.Model):
|
|||
time = models.DateTimeField()
|
||||
duration = TimedeltaField()
|
||||
location = models.ForeignKey(Room, blank=True, null=True)
|
||||
show_location = models.BooleanField(default=True, help_text="Show location in agenda")
|
||||
sessions = models.ManyToManyField('Session', related_name='slots', through='ScheduledSession', null=True, blank=True, help_text=u"Scheduled session, if any")
|
||||
show_location = models.BooleanField(default=True, help_text="Show location in agenda.")
|
||||
sessions = models.ManyToManyField('Session', related_name='slots', through='ScheduledSession', null=True, blank=True, help_text=u"Scheduled session, if any.")
|
||||
modified = models.DateTimeField(default=datetime.datetime.now)
|
||||
#
|
||||
|
||||
|
@ -461,8 +461,8 @@ class Schedule(models.Model):
|
|||
meeting = models.ForeignKey(Meeting, null=True)
|
||||
name = models.CharField(max_length=16, blank=False)
|
||||
owner = models.ForeignKey(Person)
|
||||
visible = models.BooleanField(default=True, help_text=u"Make this agenda available to those who know about it")
|
||||
public = models.BooleanField(default=True, help_text=u"Make this agenda publically available")
|
||||
visible = models.BooleanField(default=True, help_text=u"Make this agenda available to those who know about it.")
|
||||
public = models.BooleanField(default=True, help_text=u"Make this agenda publically available.")
|
||||
badness = models.IntegerField(null=True, blank=True)
|
||||
# considering copiedFrom = models.ForeignKey('Schedule', blank=True, null=True)
|
||||
|
||||
|
@ -618,14 +618,14 @@ class ScheduledSession(models.Model):
|
|||
Each relationship is attached to the named agenda, which is owned by
|
||||
a specific person/user.
|
||||
"""
|
||||
timeslot = models.ForeignKey('TimeSlot', null=False, blank=False, help_text=u"")
|
||||
session = models.ForeignKey('Session', null=True, default=None, help_text=u"Scheduled session")
|
||||
timeslot = models.ForeignKey('TimeSlot', null=False, blank=False)
|
||||
session = models.ForeignKey('Session', null=True, default=None, help_text=u"Scheduled session.")
|
||||
schedule = models.ForeignKey('Schedule', null=False, blank=False, related_name='assignments')
|
||||
extendedfrom = models.ForeignKey('ScheduledSession', null=True, default=None, help_text=u"Timeslot this session is an extension of")
|
||||
extendedfrom = models.ForeignKey('ScheduledSession', null=True, default=None, help_text=u"Timeslot this session is an extension of.")
|
||||
modified = models.DateTimeField(default=datetime.datetime.now)
|
||||
notes = models.TextField(blank=True)
|
||||
badness = models.IntegerField(default=0, blank=True, null=True)
|
||||
pinned = models.BooleanField(default=False, help_text="Do not move session during automatic placement")
|
||||
pinned = models.BooleanField(default=False, help_text="Do not move session during automatic placement.")
|
||||
|
||||
class Meta:
|
||||
ordering = ["timeslot__time", "session__group__parent__name", "session__group__acronym", "session__name", ]
|
||||
|
@ -810,8 +810,8 @@ class Session(models.Model):
|
|||
Training sessions and similar are modeled by filling in a
|
||||
responsible group (e.g. Edu team) and filling in the name."""
|
||||
meeting = models.ForeignKey(Meeting)
|
||||
name = models.CharField(blank=True, max_length=255, help_text="Name of session, in case the session has a purpose rather than just being a group meeting")
|
||||
short = models.CharField(blank=True, max_length=32, help_text="Short version of 'name' above, for use in filenames")
|
||||
name = models.CharField(blank=True, max_length=255, help_text="Name of session, in case the session has a purpose rather than just being a group meeting.")
|
||||
short = models.CharField(blank=True, max_length=32, help_text="Short version of 'name' above, for use in filenames.")
|
||||
group = models.ForeignKey(Group) # The group type determines the session type. BOFs also need to be added as a group.
|
||||
attendees = models.IntegerField(null=True, blank=True)
|
||||
agenda_note = models.CharField(blank=True, max_length=255)
|
||||
|
|
|
@ -731,9 +731,9 @@ class CurrentScheduleState:
|
|||
#
|
||||
if False:
|
||||
class AutomaticScheduleStep(models.Model):
|
||||
schedule = models.ForeignKey('Schedule', null=False, blank=False, help_text=u"Who made this agenda")
|
||||
session = models.ForeignKey('Session', null=True, default=None, help_text=u"Scheduled session involved")
|
||||
moved_from = models.ForeignKey('ScheduledSession', related_name="+", null=True, default=None, help_text=u"Where session was")
|
||||
moved_to = models.ForeignKey('ScheduledSession', related_name="+", null=True, default=None, help_text=u"Where session went")
|
||||
schedule = models.ForeignKey('Schedule', null=False, blank=False, help_text=u"Who made this agenda.")
|
||||
session = models.ForeignKey('Session', null=True, default=None, help_text=u"Scheduled session involved.")
|
||||
moved_from = models.ForeignKey('ScheduledSession', related_name="+", null=True, default=None, help_text=u"Where session was.")
|
||||
moved_to = models.ForeignKey('ScheduledSession', related_name="+", null=True, default=None, help_text=u"Where session went.")
|
||||
stepnum = models.IntegerField(default=0, blank=True, null=True)
|
||||
|
||||
|
|
|
@ -48,7 +48,7 @@ class ScheduleEditTests(LiveServerTestCase):
|
|||
self.driver.get(url)
|
||||
self.driver.find_element_by_name('username').send_keys('plain')
|
||||
self.driver.find_element_by_name('password').send_keys('plain+password')
|
||||
self.driver.find_element_by_xpath('//input[@value="Sign in"]').click()
|
||||
self.driver.find_element_by_xpath('//button[@type="submit"]').click()
|
||||
|
||||
def testUnschedule(self):
|
||||
|
||||
|
|
|
@ -39,29 +39,21 @@ class MeetingTests(TestCase):
|
|||
session = Session.objects.filter(meeting=meeting, group__acronym="mars").first()
|
||||
slot = TimeSlot.objects.get(scheduledsession__session=session)
|
||||
|
||||
time_interval = "%s-%s" % (slot.time.strftime("%H%M"), (slot.time + slot.duration).strftime("%H%M"))
|
||||
time_interval = "%s-%s" % (slot.time.strftime("%H:%M").lstrip("0"), (slot.time + slot.duration).strftime("%H:%M").lstrip("0"))
|
||||
|
||||
# plain
|
||||
r = self.client.get(urlreverse("ietf.meeting.views.agenda", kwargs=dict(num=meeting.number)))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
agenda_content = q("#agenda").html()
|
||||
agenda_content = q(".content-wrapper").html()
|
||||
self.assertTrue(session.group.acronym in agenda_content)
|
||||
self.assertTrue(session.group.name in agenda_content)
|
||||
self.assertTrue(session.group.parent.acronym.upper() in agenda_content)
|
||||
self.assertTrue(slot.location.name in agenda_content)
|
||||
self.assertTrue(time_interval in agenda_content)
|
||||
|
||||
# mobile
|
||||
r = self.client.get(urlreverse("ietf.meeting.views.agenda", kwargs=dict(num=meeting.number)),
|
||||
{ '_testiphone': "1" })
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
agenda_content = q("#agenda").html()
|
||||
self.assertTrue(session.group.acronym in agenda_content)
|
||||
self.assertTrue(session.group.name[:10] in agenda_content)
|
||||
self.assertTrue(slot.location.name in agenda_content)
|
||||
self.assertTrue(time_interval in agenda_content)
|
||||
# the rest of the results don't have as nicely formatted times
|
||||
time_interval = time_interval.replace(":", "")
|
||||
|
||||
# text
|
||||
r = self.client.get(urlreverse("ietf.meeting.views.agenda", kwargs=dict(num=meeting.number, ext=".txt")))
|
||||
|
@ -125,12 +117,11 @@ class MeetingTests(TestCase):
|
|||
|
||||
r = self.client.get(urlreverse("ietf.meeting.views.materials", kwargs=dict(meeting_num=meeting.number)))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
#debug.show('r.content')
|
||||
q = PyQuery(r.content)
|
||||
row = q('.ietf-materials b:contains("%s")' % str(session.group.acronym.upper())).closest("tr")
|
||||
self.assertTrue(row.find("a:contains(\"Agenda\")"))
|
||||
self.assertTrue(row.find("a:contains(\"Minutes\")"))
|
||||
self.assertTrue(row.find("a:contains(\"Slideshow\")"))
|
||||
row = q('.content-wrapper td:contains("%s")' % str(session.group.acronym)).closest("tr")
|
||||
self.assertTrue(row.find('a:contains("Agenda")'))
|
||||
self.assertTrue(row.find('a:contains("Minutes")'))
|
||||
self.assertTrue(row.find('a:contains("Slideshow")'))
|
||||
|
||||
# FIXME: missing tests of .pdf/.tar generation (some code can
|
||||
# probably be lifted from similar tests in iesg/tests.py)
|
||||
|
|
|
@ -9,8 +9,7 @@ from ietf.meeting import ajax
|
|||
urlpatterns = patterns('',
|
||||
(r'^(?P<meeting_num>\d+)/materials.html$', views.materials),
|
||||
(r'^agenda/$', views.agenda),
|
||||
(r'^(?P<base>agenda-utc)(?P<ext>.html)?$', views.agenda),
|
||||
(r'^agenda(?P<ext>.html)?$', views.agenda),
|
||||
(r'^agenda(-utc)?(?P<ext>.html)?$', views.agenda),
|
||||
(r'^agenda(?P<ext>.txt)$', views.agenda),
|
||||
(r'^agenda(?P<ext>.csv)$', views.agenda),
|
||||
(r'^agenda/edit$', views.edit_agenda),
|
||||
|
@ -28,8 +27,7 @@ urlpatterns = patterns('',
|
|||
(r'^(?P<num>\d+)/agenda/(?P<owner>[A-Za-z0-9-.+_]+@[A-Za-z0-9._]+)/(?P<name>[A-Za-z0-9-:_]+)/sessions.json$', ajax.scheduledsessions_json),
|
||||
(r'^(?P<num>\d+)/agenda/(?P<owner>[A-Za-z0-9-.+_]+@[A-Za-z0-9._]+)/(?P<name>[A-Za-z0-9-:_]+).json$', ajax.agenda_infourl),
|
||||
(r'^(?P<num>\d+)/agenda/edit$', views.edit_agenda),
|
||||
(r'^(?P<num>\d+)/agenda(?P<ext>.html)?/?$', views.agenda),
|
||||
(r'^(?P<num>\d+)/(?P<base>agenda-utc)(?P<ext>.html)?/?$', views.agenda),
|
||||
(r'^(?P<num>\d+)/agenda(-utc)?(?P<ext>.html)?/?$', views.agenda),
|
||||
(r'^(?P<num>\d+)/requests.html$', RedirectView.as_view(url='/meeting/%(num)s/requests', permanent=True)),
|
||||
(r'^(?P<num>\d+)/requests$', views.meeting_requests),
|
||||
(r'^(?P<num>\d+)/agenda(?P<ext>.txt)$', views.agenda),
|
||||
|
|
|
@ -187,7 +187,7 @@ def edit_timeslots(request, num=None):
|
|||
roomsurl = reverse(timeslot_roomsurl, args=[meeting.number])
|
||||
adddayurl = reverse(timeslot_slotsurl, args=[meeting.number])
|
||||
|
||||
return HttpResponse(render_to_string("meeting/timeslot_edit.html",
|
||||
return render(request, "meeting/timeslot_edit.html",
|
||||
{"timeslots": timeslots,
|
||||
"meeting_base_url": meeting_base_url,
|
||||
"site_base_url": site_base_url,
|
||||
|
@ -199,8 +199,9 @@ def edit_timeslots(request, num=None):
|
|||
"time_slices":time_slices,
|
||||
"slot_slices": slots,
|
||||
"date_slices":date_slices,
|
||||
"meeting":meeting},
|
||||
RequestContext(request)), content_type="text/html")
|
||||
"meeting":meeting,
|
||||
"hide_menu": True,
|
||||
})
|
||||
|
||||
class RoomForm(ModelForm):
|
||||
class Meta:
|
||||
|
@ -227,12 +228,13 @@ def edit_roomurl(request, num, roomid):
|
|||
roomform = RoomForm(instance=room)
|
||||
meeting_base_url = request.build_absolute_uri(meeting.base_url())
|
||||
site_base_url = request.build_absolute_uri('/')[:-1] # skip the trailing slash
|
||||
return HttpResponse(render_to_string("meeting/room_edit.html",
|
||||
return render(request, "meeting/room_edit.html",
|
||||
{"meeting_base_url": meeting_base_url,
|
||||
"site_base_url": site_base_url,
|
||||
"editroom": roomform,
|
||||
"meeting":meeting},
|
||||
RequestContext(request)), content_type="text/html")
|
||||
"meeting":meeting,
|
||||
"hide_menu": True,
|
||||
})
|
||||
|
||||
##############################################################################
|
||||
#@role_required('Area Director','Secretariat')
|
||||
|
@ -268,7 +270,8 @@ def edit_agenda(request, num=None, owner=None, name=None):
|
|||
return HttpResponse(render_to_string("meeting/private_agenda.html",
|
||||
{"schedule":schedule,
|
||||
"meeting": meeting,
|
||||
"meeting_base_url":meeting_base_url},
|
||||
"meeting_base_url":meeting_base_url,
|
||||
"hide_menu": True},
|
||||
RequestContext(request)), status=403, content_type="text/html")
|
||||
|
||||
scheduledsessions = get_all_scheduledsessions_from_schedule(schedule)
|
||||
|
@ -287,7 +290,7 @@ def edit_agenda(request, num=None, owner=None, name=None):
|
|||
|
||||
time_slices,date_slices = build_all_agenda_slices(meeting)
|
||||
|
||||
return HttpResponse(render_to_string("meeting/landscape_edit.html",
|
||||
return render(request, "meeting/landscape_edit.html",
|
||||
{"schedule":schedule,
|
||||
"saveas": saveas,
|
||||
"saveasurl": saveasurl,
|
||||
|
@ -302,8 +305,9 @@ def edit_agenda(request, num=None, owner=None, name=None):
|
|||
"area_directors" : ads,
|
||||
"wg_list": wg_list ,
|
||||
"scheduledsessions": scheduledsessions,
|
||||
"show_inline": set(["txt","htm","html"]) },
|
||||
RequestContext(request)), content_type="text/html")
|
||||
"show_inline": set(["txt","htm","html"]),
|
||||
"hide_menu": True,
|
||||
})
|
||||
|
||||
##############################################################################
|
||||
# show the properties associated with an agenda (visible, public)
|
||||
|
@ -326,11 +330,12 @@ def edit_agenda_properties(request, num=None, owner=None, name=None):
|
|||
if not (canedit or has_role(request.user,'Secretariat')):
|
||||
return HttpResponseForbidden("You may not edit this agenda")
|
||||
else:
|
||||
return HttpResponse(render_to_string("meeting/properties_edit.html",
|
||||
return render(request, "meeting/properties_edit.html",
|
||||
{"schedule":schedule,
|
||||
"form":form,
|
||||
"meeting":meeting},
|
||||
RequestContext(request)), content_type="text/html")
|
||||
"meeting":meeting,
|
||||
"hide_menu": True,
|
||||
})
|
||||
|
||||
##############################################################################
|
||||
# show list of agendas.
|
||||
|
@ -352,29 +357,25 @@ def edit_agendas(request, num=None, order=None):
|
|||
|
||||
schedules = schedules.order_by('owner', 'name')
|
||||
|
||||
return HttpResponse(render_to_string("meeting/agenda_list.html",
|
||||
return render(request, "meeting/agenda_list.html",
|
||||
{"meeting": meeting,
|
||||
"schedules": schedules.all()
|
||||
},
|
||||
RequestContext(request)),
|
||||
content_type="text/html")
|
||||
"schedules": schedules.all(),
|
||||
"hide_menu": True,
|
||||
})
|
||||
|
||||
@ensure_csrf_cookie
|
||||
def agenda(request, num=None, name=None, base=None, ext=None):
|
||||
base = base if base else 'agenda'
|
||||
ext = ext if ext else '.html'
|
||||
if 'iPhone' in get_user_agent(request) and ext == ".html":
|
||||
base = 'm_agenda'
|
||||
mimetype = {".html":"text/html", ".txt": "text/plain", ".ics":"text/calendar", ".csv":"text/csv"}
|
||||
meeting = get_meeting(num)
|
||||
schedule = get_schedule(meeting, name)
|
||||
if schedule == None:
|
||||
return HttpResponse(render_to_string("meeting/no-"+base+ext,
|
||||
{'meeting':meeting }, RequestContext(request)), content_type=mimetype[ext])
|
||||
base = base.replace("-utc", "")
|
||||
return render(request, "meeting/no-"+base+ext, {'meeting':meeting }, content_type=mimetype[ext])
|
||||
|
||||
updated = meeting_updated(meeting)
|
||||
return HttpResponse(render_to_string("meeting/"+base+ext,
|
||||
{"schedule":schedule, "updated": updated}, RequestContext(request)), content_type=mimetype[ext])
|
||||
return render(request, "meeting/"+base+ext, {"schedule":schedule, "updated": updated}, content_type=mimetype[ext])
|
||||
|
||||
def read_agenda_file(num, doc):
|
||||
# XXXX FIXME: the path fragment in the code below should be moved to
|
||||
|
|
220
ietf/name/migrations/0031_fix_ipr_none_selected_choice.py
Normal file
220
ietf/name/migrations/0031_fix_ipr_none_selected_choice.py
Normal file
|
@ -0,0 +1,220 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from south.v2 import DataMigration
|
||||
|
||||
class Migration(DataMigration):
|
||||
|
||||
def forwards(self, orm):
|
||||
orm.IprLicenseTypeName.objects.filter(slug="none-selected").update(desc="[None selected]")
|
||||
|
||||
def backwards(self, orm):
|
||||
orm.IprLicenseTypeName.objects.filter(slug="none-selected").update(desc="")
|
||||
|
||||
models = {
|
||||
u'name.ballotpositionname': {
|
||||
'Meta': {'ordering': "['order']", 'object_name': 'BallotPositionName'},
|
||||
'blocking': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
|
||||
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
|
||||
},
|
||||
u'name.constraintname': {
|
||||
'Meta': {'ordering': "['order']", 'object_name': 'ConstraintName'},
|
||||
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'penalty': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
|
||||
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
|
||||
},
|
||||
u'name.dbtemplatetypename': {
|
||||
'Meta': {'ordering': "['order']", 'object_name': 'DBTemplateTypeName'},
|
||||
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
|
||||
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
|
||||
},
|
||||
u'name.docrelationshipname': {
|
||||
'Meta': {'ordering': "['order']", 'object_name': 'DocRelationshipName'},
|
||||
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'revname': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
|
||||
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
|
||||
},
|
||||
u'name.docremindertypename': {
|
||||
'Meta': {'ordering': "['order']", 'object_name': 'DocReminderTypeName'},
|
||||
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
|
||||
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
|
||||
},
|
||||
u'name.doctagname': {
|
||||
'Meta': {'ordering': "['order']", 'object_name': 'DocTagName'},
|
||||
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
|
||||
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
|
||||
},
|
||||
u'name.doctypename': {
|
||||
'Meta': {'ordering': "['order']", 'object_name': 'DocTypeName'},
|
||||
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
|
||||
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
|
||||
},
|
||||
u'name.draftsubmissionstatename': {
|
||||
'Meta': {'ordering': "['order']", 'object_name': 'DraftSubmissionStateName'},
|
||||
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'next_states': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'previous_states'", 'blank': 'True', 'to': u"orm['name.DraftSubmissionStateName']"}),
|
||||
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
|
||||
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
|
||||
},
|
||||
u'name.feedbacktypename': {
|
||||
'Meta': {'ordering': "['order']", 'object_name': 'FeedbackTypeName'},
|
||||
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
|
||||
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
|
||||
},
|
||||
u'name.groupmilestonestatename': {
|
||||
'Meta': {'ordering': "['order']", 'object_name': 'GroupMilestoneStateName'},
|
||||
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
|
||||
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
|
||||
},
|
||||
u'name.groupstatename': {
|
||||
'Meta': {'ordering': "['order']", 'object_name': 'GroupStateName'},
|
||||
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
|
||||
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
|
||||
},
|
||||
u'name.grouptypename': {
|
||||
'Meta': {'ordering': "['order']", 'object_name': 'GroupTypeName'},
|
||||
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
|
||||
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
|
||||
},
|
||||
u'name.intendedstdlevelname': {
|
||||
'Meta': {'ordering': "['order']", 'object_name': 'IntendedStdLevelName'},
|
||||
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
|
||||
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
|
||||
},
|
||||
u'name.iprdisclosurestatename': {
|
||||
'Meta': {'ordering': "['order']", 'object_name': 'IprDisclosureStateName'},
|
||||
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
|
||||
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
|
||||
},
|
||||
u'name.ipreventtypename': {
|
||||
'Meta': {'ordering': "['order']", 'object_name': 'IprEventTypeName'},
|
||||
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
|
||||
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
|
||||
},
|
||||
u'name.iprlicensetypename': {
|
||||
'Meta': {'ordering': "['order']", 'object_name': 'IprLicenseTypeName'},
|
||||
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
|
||||
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
|
||||
},
|
||||
u'name.liaisonstatementpurposename': {
|
||||
'Meta': {'ordering': "['order']", 'object_name': 'LiaisonStatementPurposeName'},
|
||||
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
|
||||
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
|
||||
},
|
||||
u'name.meetingtypename': {
|
||||
'Meta': {'ordering': "['order']", 'object_name': 'MeetingTypeName'},
|
||||
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
|
||||
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
|
||||
},
|
||||
u'name.nomineepositionstatename': {
|
||||
'Meta': {'ordering': "['order']", 'object_name': 'NomineePositionStateName'},
|
||||
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
|
||||
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
|
||||
},
|
||||
u'name.rolename': {
|
||||
'Meta': {'ordering': "['order']", 'object_name': 'RoleName'},
|
||||
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
|
||||
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
|
||||
},
|
||||
u'name.roomresourcename': {
|
||||
'Meta': {'ordering': "['order']", 'object_name': 'RoomResourceName'},
|
||||
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
|
||||
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
|
||||
},
|
||||
u'name.sessionstatusname': {
|
||||
'Meta': {'ordering': "['order']", 'object_name': 'SessionStatusName'},
|
||||
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
|
||||
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
|
||||
},
|
||||
u'name.stdlevelname': {
|
||||
'Meta': {'ordering': "['order']", 'object_name': 'StdLevelName'},
|
||||
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
|
||||
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
|
||||
},
|
||||
u'name.streamname': {
|
||||
'Meta': {'ordering': "['order']", 'object_name': 'StreamName'},
|
||||
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
|
||||
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
|
||||
},
|
||||
u'name.timeslottypename': {
|
||||
'Meta': {'ordering': "['order']", 'object_name': 'TimeSlotTypeName'},
|
||||
'desc': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'slug': ('django.db.models.fields.CharField', [], {'max_length': '32', 'primary_key': 'True'}),
|
||||
'used': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
|
||||
}
|
||||
}
|
||||
|
||||
complete_apps = ['name']
|
||||
symmetrical = True
|
|
@ -11,13 +11,13 @@ from ietf.dbtemplate.forms import DBTemplateForm
|
|||
from ietf.group.models import Group, Role
|
||||
from ietf.ietfauth.utils import role_required
|
||||
from ietf.name.models import RoleName, FeedbackTypeName, NomineePositionStateName
|
||||
from ietf.nomcom.models import ( NomCom, Nomination, Nominee, NomineePosition,
|
||||
from ietf.nomcom.models import ( NomCom, Nomination, Nominee, NomineePosition,
|
||||
Position, Feedback, ReminderDates )
|
||||
from ietf.nomcom.utils import (NOMINATION_RECEIPT_TEMPLATE, FEEDBACK_RECEIPT_TEMPLATE,
|
||||
get_user_email, validate_private_key, validate_public_key,
|
||||
get_or_create_nominee, create_feedback_email)
|
||||
from ietf.person.models import Email
|
||||
from ietf.person.fields import AutocompletedEmailField
|
||||
from ietf.person.fields import SearchableEmailField
|
||||
from ietf.utils.fields import MultiEmailField
|
||||
from ietf.utils.mail import send_mail
|
||||
|
||||
|
@ -207,62 +207,6 @@ class EditMembersFormPreview(FormPreview):
|
|||
return redirect('nomcom_edit_members', year=self.year)
|
||||
|
||||
|
||||
class EditChairForm(BaseNomcomForm, forms.Form):
|
||||
|
||||
chair = forms.EmailField(label="Chair email", required=False,
|
||||
widget=forms.TextInput(attrs={'size': '40'}))
|
||||
|
||||
fieldsets = [('Chair info', ('chair',))]
|
||||
|
||||
|
||||
class EditChairFormPreview(FormPreview):
|
||||
form_template = 'nomcom/edit_chair.html'
|
||||
preview_template = 'nomcom/edit_chair_preview.html'
|
||||
|
||||
@method_decorator(role_required("Secretariat"))
|
||||
def __call__(self, request, *args, **kwargs):
|
||||
year = kwargs['year']
|
||||
group = get_nomcom_group_or_404(year)
|
||||
self.state['group'] = group
|
||||
self.state['rolodex_url'] = ROLODEX_URL
|
||||
self.group = group
|
||||
self.year = year
|
||||
|
||||
return super(EditChairFormPreview, self).__call__(request, *args, **kwargs)
|
||||
|
||||
def get_initial(self, request):
|
||||
chair = self.group.get_chair()
|
||||
if chair:
|
||||
return { "chair": chair.email.address }
|
||||
return {}
|
||||
|
||||
def process_preview(self, request, form, context):
|
||||
chair_email = form.cleaned_data['chair']
|
||||
try:
|
||||
chair_email_obj = Email.objects.get(address=chair_email)
|
||||
chair_person = chair_email_obj.person
|
||||
except Email.DoesNotExist:
|
||||
chair_person = None
|
||||
chair_email_obj = None
|
||||
chair_info = {'email': chair_email,
|
||||
'email_obj': chair_email_obj,
|
||||
'person': chair_person}
|
||||
|
||||
self.state.update({'chair_info': chair_info})
|
||||
|
||||
def done(self, request, cleaned_data):
|
||||
chair_info = self.state['chair_info']
|
||||
chair_exclude = self.group.role_set.filter(name__slug='chair').exclude(email__address=chair_info['email'])
|
||||
chair_exclude.delete()
|
||||
if chair_info['email_obj'] and chair_info['person']:
|
||||
Role.objects.get_or_create(name=RoleName.objects.get(slug="chair"),
|
||||
group=self.group,
|
||||
person=chair_info['person'],
|
||||
email=chair_info['email_obj'])
|
||||
|
||||
return redirect('nomcom_edit_chair', year=self.year)
|
||||
|
||||
|
||||
class EditNomcomForm(BaseNomcomForm, forms.ModelForm):
|
||||
|
||||
fieldsets = [('Edit nomcom settings', ('public_key', 'initial_text',
|
||||
|
@ -296,7 +240,7 @@ class EditNomcomForm(BaseNomcomForm, forms.ModelForm):
|
|||
class MergeForm(BaseNomcomForm, forms.Form):
|
||||
|
||||
secondary_emails = MultiEmailField(label="Secondary email addresses",
|
||||
help_text="Provide a comma separated list of email addresses. Nominations already received with any of these email address will be moved to show under the primary address", widget=forms.Textarea)
|
||||
help_text="Provide a comma separated list of email addresses. Nominations already received with any of these email address will be moved to show under the primary address.", widget=forms.Textarea)
|
||||
primary_email = forms.EmailField(label="Primary email address",
|
||||
widget=forms.TextInput(attrs={'size': '40'}))
|
||||
|
||||
|
@ -378,11 +322,10 @@ class MergeForm(BaseNomcomForm, forms.Form):
|
|||
|
||||
|
||||
class NominateForm(BaseNomcomForm, forms.ModelForm):
|
||||
comments = forms.CharField(label="Candidate's Qualifications for the Position:",
|
||||
comments = forms.CharField(label="Candidate's qualifications for the position",
|
||||
widget=forms.Textarea())
|
||||
confirmation = forms.BooleanField(label='Email comments back to me as confirmation',
|
||||
help_text="If you want to get a confirmation mail containing your feedback in cleartext, \
|
||||
please check the 'email comments back to me as confirmation'",
|
||||
help_text="If you want to get a confirmation mail containing your feedback in cleartext, please check the 'email comments back to me as confirmation'.",
|
||||
required=False)
|
||||
|
||||
fieldsets = [('Candidate Nomination', ('position', 'candidate_name',
|
||||
|
@ -400,6 +343,7 @@ class NominateForm(BaseNomcomForm, forms.ModelForm):
|
|||
'candidate_email', 'candidate_phone',
|
||||
'comments']
|
||||
|
||||
self.fields['nominator_email'].label = 'Nominator email'
|
||||
if self.nomcom:
|
||||
self.fields['position'].queryset = Position.objects.get_by_nomcom(self.nomcom).opened()
|
||||
self.fields['comments'].help_text = self.nomcom.initial_text
|
||||
|
@ -461,7 +405,7 @@ class NominateForm(BaseNomcomForm, forms.ModelForm):
|
|||
# send receipt email to nominator
|
||||
if confirmation:
|
||||
if author:
|
||||
subject = 'Nomination Receipt'
|
||||
subject = 'Nomination receipt'
|
||||
from_email = settings.NOMCOM_FROM_EMAIL
|
||||
to_email = author.address
|
||||
context = {'nominee': nominee.email.person.name,
|
||||
|
@ -479,19 +423,18 @@ class NominateForm(BaseNomcomForm, forms.ModelForm):
|
|||
|
||||
|
||||
class FeedbackForm(BaseNomcomForm, forms.ModelForm):
|
||||
position_name = forms.CharField(label='position',
|
||||
position_name = forms.CharField(label='Position',
|
||||
widget=forms.TextInput(attrs={'size': '40'}))
|
||||
nominee_name = forms.CharField(label='nominee name',
|
||||
nominee_name = forms.CharField(label='Nominee name',
|
||||
widget=forms.TextInput(attrs={'size': '40'}))
|
||||
nominee_email = forms.CharField(label='nominee email',
|
||||
nominee_email = forms.CharField(label='Nominee email',
|
||||
widget=forms.TextInput(attrs={'size': '40'}))
|
||||
nominator_email = forms.CharField(label='commenter email')
|
||||
nominator_email = forms.CharField(label='Commenter email')
|
||||
|
||||
comments = forms.CharField(label='Comments on this candidate',
|
||||
comments = forms.CharField(label='Comments on this nominee',
|
||||
widget=forms.Textarea())
|
||||
confirmation = forms.BooleanField(label='Email comments back to me as confirmation',
|
||||
help_text="If you want to get a confirmation mail containing your feedback in cleartext, \
|
||||
please check the 'email comments back to me as confirmation'",
|
||||
help_text="If you want to get a confirmation mail containing your feedback in cleartext, please check the 'email comments back to me as confirmation'.",
|
||||
required=False)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
@ -656,7 +599,7 @@ class PositionForm(BaseNomcomForm, forms.ModelForm):
|
|||
fieldsets = [('Position', ('name', 'description',
|
||||
'is_open', 'incumbent'))]
|
||||
|
||||
incumbent = AutocompletedEmailField(required=False)
|
||||
incumbent = SearchableEmailField(required=False)
|
||||
|
||||
class Meta:
|
||||
model = Position
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# -*- coding: utf-8-No-BOM -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
|
||||
from django.db import models
|
||||
|
@ -38,11 +38,11 @@ class NomCom(models.Model):
|
|||
upload_to=upload_path_handler, blank=True, null=True)
|
||||
|
||||
group = models.ForeignKey(Group)
|
||||
send_questionnaire = models.BooleanField(verbose_name='Send questionnaires automatically"', default=False,
|
||||
help_text='If you check this box, questionnaires are sent automatically after nominations')
|
||||
send_questionnaire = models.BooleanField(verbose_name='Send questionnaires automatically', default=False,
|
||||
help_text='If you check this box, questionnaires are sent automatically after nominations.')
|
||||
reminder_interval = models.PositiveIntegerField(help_text='If the nomcom user sets the interval field then a cron command will \
|
||||
send reminders to the nominees who have not responded using \
|
||||
the following formula: (today - nomination_date) % interval == 0',
|
||||
the following formula: (today - nomination_date) % interval == 0.',
|
||||
blank=True, null=True)
|
||||
initial_text = models.TextField(verbose_name='Help text for nomination form',
|
||||
blank=True)
|
||||
|
|
|
@ -41,12 +41,8 @@ def add_num_nominations(user, position, nominee):
|
|||
nominees__in=[nominee],
|
||||
author=author,
|
||||
type='comment').count()
|
||||
if count:
|
||||
mark = """<span style="white-space: pre; color: red;">*</span>"""
|
||||
else:
|
||||
mark = """<span style="white-space: pre;"> </span> """
|
||||
|
||||
return '<span title="%d earlier comments from you on %s as %s">%s</span> ' % (count, nominee.email.address, position, mark)
|
||||
return '<span class="badge" title="%d earlier comments from you on %s as %s">%s</span> ' % (count, nominee.email.address, position, count)
|
||||
|
||||
|
||||
@register.filter
|
||||
|
@ -76,7 +72,7 @@ def decrypt(string, request, year, plain=False):
|
|||
code, out, error = pipe(command % (settings.OPENSSL_COMMAND,
|
||||
encrypted_file.name), key)
|
||||
if code != 0:
|
||||
log("openssl error: %s:\n Error %s: %s" %(command, code, error))
|
||||
log("openssl error: %s:\n Error %s: %s" %(command, code, error))
|
||||
|
||||
os.unlink(encrypted_file.name)
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# -*- coding: UTF-8-No-BOM -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
import tempfile
|
||||
import datetime
|
||||
|
||||
|
@ -22,7 +22,7 @@ from ietf.nomcom.test_data import nomcom_test_data, generate_cert, check_comment
|
|||
from ietf.nomcom.models import NomineePosition, Position, Nominee, \
|
||||
NomineePositionStateName, Feedback, FeedbackTypeName, \
|
||||
Nomination
|
||||
from ietf.nomcom.forms import EditChairForm, EditChairFormPreview, EditMembersForm
|
||||
from ietf.nomcom.forms import EditMembersForm, EditMembersFormPreview
|
||||
from ietf.nomcom.utils import get_nomcom_by_year, get_or_create_nominee
|
||||
from ietf.nomcom.management.commands.send_reminders import Command, is_time_to_send
|
||||
|
||||
|
@ -54,7 +54,6 @@ class NomcomViewsTest(TestCase):
|
|||
self.private_index_url = reverse('nomcom_private_index', kwargs={'year': self.year})
|
||||
self.private_merge_url = reverse('nomcom_private_merge', kwargs={'year': self.year})
|
||||
self.edit_members_url = reverse('nomcom_edit_members', kwargs={'year': self.year})
|
||||
self.edit_chair_url = reverse('nomcom_edit_chair', kwargs={'year': self.year})
|
||||
self.edit_nomcom_url = reverse('nomcom_edit_nomcom', kwargs={'year': self.year})
|
||||
self.private_nominate_url = reverse('nomcom_private_nominate', kwargs={'year': self.year})
|
||||
self.add_questionnaire_url = reverse('nomcom_private_questionnaire', kwargs={'year': self.year})
|
||||
|
@ -214,31 +213,36 @@ class NomcomViewsTest(TestCase):
|
|||
"primary_email": nominees[0]}
|
||||
response = self.client.post(self.private_merge_url, test_data)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, "info-message-error")
|
||||
q = PyQuery(response.content)
|
||||
self.assertTrue(q("form .has-error"))
|
||||
|
||||
test_data = {"primary_email": nominees[0],
|
||||
"secondary_emails": ""}
|
||||
response = self.client.post(self.private_merge_url, test_data)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, "info-message-error")
|
||||
q = PyQuery(response.content)
|
||||
self.assertTrue(q("form .has-error"))
|
||||
|
||||
test_data = {"primary_email": "",
|
||||
"secondary_emails": nominees[0]}
|
||||
response = self.client.post(self.private_merge_url, test_data)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, "info-message-error")
|
||||
q = PyQuery(response.content)
|
||||
self.assertTrue(q("form .has-error"))
|
||||
|
||||
test_data = {"primary_email": "unknown@example.com",
|
||||
"secondary_emails": nominees[0]}
|
||||
response = self.client.post(self.private_merge_url, test_data)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, "info-message-error")
|
||||
q = PyQuery(response.content)
|
||||
self.assertTrue(q("form .has-error"))
|
||||
|
||||
test_data = {"primary_email": nominees[0],
|
||||
"secondary_emails": "unknown@example.com"}
|
||||
response = self.client.post(self.private_merge_url, test_data)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, "info-message-error")
|
||||
q = PyQuery(response.content)
|
||||
self.assertTrue(q("form .has-error"))
|
||||
|
||||
test_data = {"secondary_emails": """%s,
|
||||
%s,
|
||||
|
@ -247,7 +251,7 @@ class NomcomViewsTest(TestCase):
|
|||
|
||||
response = self.client.post(self.private_merge_url, test_data)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, "info-message-success")
|
||||
self.assertContains(response, "alert-success")
|
||||
|
||||
self.assertEqual(Nominee.objects.filter(email__address=nominees[1],
|
||||
duplicated__isnull=False).count(), 1)
|
||||
|
@ -293,7 +297,7 @@ class NomcomViewsTest(TestCase):
|
|||
# preview
|
||||
self.client.post(self.edit_members_url, test_data)
|
||||
|
||||
hash = EditChairFormPreview(EditChairForm).security_hash(None, EditMembersForm(test_data))
|
||||
hash = EditMembersFormPreview(EditMembersForm).security_hash(None, EditMembersForm(test_data))
|
||||
test_data.update({'hash': hash, 'stage': 2})
|
||||
|
||||
# submit
|
||||
|
@ -318,33 +322,6 @@ class NomcomViewsTest(TestCase):
|
|||
self.check_url_status(self.private_index_url, 403)
|
||||
self.client.logout()
|
||||
|
||||
def change_chair(self, user):
|
||||
test_data = {'chair': '%s%s' % (user, EMAIL_DOMAIN),
|
||||
'stage': 1}
|
||||
# preview
|
||||
self.client.post(self.edit_chair_url, test_data)
|
||||
|
||||
hash = EditChairFormPreview(EditChairForm).security_hash(None, EditChairForm(test_data))
|
||||
test_data.update({'hash': hash, 'stage': 2})
|
||||
|
||||
# submit
|
||||
self.client.post(self.edit_chair_url, test_data)
|
||||
|
||||
def test_edit_chair_view(self):
|
||||
self.access_secretariat_url(self.edit_chair_url)
|
||||
self.change_chair(COMMUNITY_USER)
|
||||
|
||||
# check chair actions
|
||||
self.client.login(username=COMMUNITY_USER,password=COMMUNITY_USER+"+password")
|
||||
self.check_url_status(self.edit_members_url, 200)
|
||||
self.check_url_status(self.edit_nomcom_url, 200)
|
||||
self.client.logout()
|
||||
|
||||
# revert edit nomcom chair
|
||||
login_testing_unauthorized(self, SECRETARIAT_USER, self.edit_chair_url)
|
||||
self.change_chair(CHAIR_USER)
|
||||
self.client.logout()
|
||||
|
||||
def test_edit_nomcom_view(self):
|
||||
r = self.access_chair_url(self.edit_nomcom_url)
|
||||
q = PyQuery(r.content)
|
||||
|
@ -433,7 +410,8 @@ class NomcomViewsTest(TestCase):
|
|||
|
||||
nomcom = get_nomcom_by_year(self.year)
|
||||
if not nomcom.public_key:
|
||||
self.assertNotContains(response, "nominateform")
|
||||
q = PyQuery(response.content)
|
||||
self.assertEqual(len(q("#nominate-form")), 0)
|
||||
|
||||
# save the cert file in tmp
|
||||
nomcom.public_key.storage.location = tempfile.gettempdir()
|
||||
|
@ -441,7 +419,8 @@ class NomcomViewsTest(TestCase):
|
|||
|
||||
response = self.client.get(nominate_url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, "nominateform")
|
||||
q = PyQuery(response.content)
|
||||
self.assertEqual(len(q("#nominate-form")), 1)
|
||||
|
||||
position = Position.objects.get(name=position_name)
|
||||
candidate_email = nominee_email
|
||||
|
@ -459,7 +438,8 @@ class NomcomViewsTest(TestCase):
|
|||
|
||||
response = self.client.post(nominate_url, test_data)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, "info-message-success")
|
||||
q = PyQuery(response.content)
|
||||
self.assertContains(response, "alert-success")
|
||||
|
||||
# check objects
|
||||
email = Email.objects.get(address=candidate_email)
|
||||
|
@ -526,7 +506,7 @@ class NomcomViewsTest(TestCase):
|
|||
response = self.client.post(self.add_questionnaire_url, test_data)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, "info-message-success")
|
||||
self.assertContains(response, "alert-success")
|
||||
|
||||
## check objects
|
||||
feedback = Feedback.objects.filter(positions__in=[position],
|
||||
|
@ -597,17 +577,18 @@ class NomcomViewsTest(TestCase):
|
|||
nominee_position = NomineePosition.objects.get(nominee=nominee,
|
||||
position=position)
|
||||
state = nominee_position.state
|
||||
if not state.slug == 'accepted':
|
||||
if state.slug != 'accepted':
|
||||
response = self.client.post(feedback_url, test_data)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, "info-message-error")
|
||||
q = PyQuery(response.content)
|
||||
self.assertTrue(q("form .has-error"))
|
||||
# accept nomination
|
||||
nominee_position.state = NomineePositionStateName.objects.get(slug='accepted')
|
||||
nominee_position.save()
|
||||
|
||||
response = self.client.post(feedback_url, test_data)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, "info-message-success")
|
||||
self.assertContains(response, "alert-success")
|
||||
|
||||
## check objects
|
||||
feedback = Feedback.objects.filter(positions__in=[position],
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
from django.conf.urls import patterns, url
|
||||
from django.views.generic import TemplateView
|
||||
|
||||
from ietf.nomcom.forms import ( EditChairForm, EditChairFormPreview,
|
||||
EditMembersForm, EditMembersFormPreview )
|
||||
from ietf.nomcom.forms import EditMembersForm, EditMembersFormPreview
|
||||
|
||||
urlpatterns = patterns('ietf.nomcom.views',
|
||||
url(r'^$', 'index'),
|
||||
|
@ -22,7 +21,6 @@ urlpatterns = patterns('ietf.nomcom.views',
|
|||
# url(r'^(?P<year>\d{4})/private/send-reminder-mail/$', RedirectView.as_view(url=reverse_lazy('nomcom_send_reminder_mail',kwargs={'year':year,'type':'accept'}))),
|
||||
url(r'^(?P<year>\d{4})/private/send-reminder-mail/(?P<type>\w+)/$', 'send_reminder_mail', name='nomcom_send_reminder_mail'),
|
||||
url(r'^(?P<year>\d{4})/private/edit-members/$', EditMembersFormPreview(EditMembersForm), name='nomcom_edit_members'),
|
||||
url(r'^(?P<year>\d{4})/private/edit-chair/$', EditChairFormPreview(EditChairForm), name='nomcom_edit_chair'),
|
||||
url(r'^(?P<year>\d{4})/private/edit-nomcom/$', 'edit_nomcom', name='nomcom_edit_nomcom'),
|
||||
url(r'^(?P<year>\d{4})/private/delete-nomcom/$', 'delete_nomcom', name='nomcom_delete_nomcom'),
|
||||
url(r'^deleted/$', TemplateView.as_view(template_name='nomcom/deleted.html'), name='nomcom_deleted'),
|
||||
|
|
|
@ -56,7 +56,7 @@ def index(request):
|
|||
nomcom.ann_url = None
|
||||
return render_to_response('nomcom/index.html',
|
||||
{'nomcom_list': nomcom_list,}, RequestContext(request))
|
||||
|
||||
|
||||
|
||||
def year_index(request, year):
|
||||
nomcom = get_nomcom_by_year(year)
|
||||
|
@ -70,21 +70,21 @@ def year_index(request, year):
|
|||
|
||||
def announcements(request):
|
||||
address_re = re.compile("<.*>")
|
||||
|
||||
|
||||
nomcoms = Group.objects.filter(type="nomcom")
|
||||
|
||||
regimes = []
|
||||
|
||||
|
||||
for n in nomcoms:
|
||||
e = GroupEvent.objects.filter(group=n, type="changed_state", changestategroupevent__state="active").order_by('time')[:1]
|
||||
n.start_year = e[0].time.year if e else 0
|
||||
e = GroupEvent.objects.filter(group=n, type="changed_state", changestategroupevent__state="conclude").order_by('time')[:1]
|
||||
n.end_year = e[0].time.year if e else n.start_year + 1
|
||||
|
||||
r = n.role_set.select_related().filter(name="chair")
|
||||
chair = None
|
||||
if r:
|
||||
chair = r[0]
|
||||
r = n.role_set.select_related().filter(name="chair")
|
||||
chair = None
|
||||
if r:
|
||||
chair = r[0]
|
||||
|
||||
announcements = Message.objects.filter(related_groups=n).order_by('-time')
|
||||
for a in announcements:
|
||||
|
@ -311,8 +311,8 @@ def nominate(request, year, public):
|
|||
template = 'nomcom/private_nominate.html'
|
||||
|
||||
if not has_publickey:
|
||||
message = ('warning', "This Nomcom is not yet accepting nominations")
|
||||
return render_to_response(template,
|
||||
message = ('warning', "This Nomcom is not yet accepting nominations")
|
||||
return render_to_response(template,
|
||||
{'message': message,
|
||||
'nomcom': nomcom,
|
||||
'year': year,
|
||||
|
@ -361,17 +361,19 @@ def feedback(request, year, public):
|
|||
positions = Position.objects.get_by_nomcom(nomcom=nomcom).opened()
|
||||
|
||||
if public:
|
||||
template = 'nomcom/public_feedback.html'
|
||||
base_template = "nomcom/nomcom_public_base.html"
|
||||
else:
|
||||
template = 'nomcom/private_feedback.html'
|
||||
base_template = "nomcom/nomcom_private_base.html"
|
||||
|
||||
if not has_publickey:
|
||||
message = ('warning', "This Nomcom is not yet accepting comments")
|
||||
return render_to_response(template,
|
||||
{'message': message,
|
||||
'nomcom': nomcom,
|
||||
'year': year,
|
||||
'selected': 'feedback'}, RequestContext(request))
|
||||
return render(request, 'nomcom/feedback.html', {
|
||||
'message': message,
|
||||
'nomcom': nomcom,
|
||||
'year': year,
|
||||
'selected': 'feedback',
|
||||
'base_template': base_template
|
||||
})
|
||||
|
||||
message = None
|
||||
if request.method == 'POST':
|
||||
|
@ -385,14 +387,16 @@ def feedback(request, year, public):
|
|||
form = FeedbackForm(nomcom=nomcom, user=request.user, public=public,
|
||||
position=position, nominee=nominee)
|
||||
|
||||
return render_to_response(template,
|
||||
{'form': form,
|
||||
'message': message,
|
||||
'nomcom': nomcom,
|
||||
'year': year,
|
||||
'positions': positions,
|
||||
'submit_disabled': submit_disabled,
|
||||
'selected': 'feedback'}, RequestContext(request))
|
||||
return render(request, 'nomcom/feedback.html', {
|
||||
'form': form,
|
||||
'message': message,
|
||||
'nomcom': nomcom,
|
||||
'year': year,
|
||||
'positions': positions,
|
||||
'submit_disabled': submit_disabled,
|
||||
'selected': 'feedback',
|
||||
'base_template': base_template
|
||||
})
|
||||
|
||||
|
||||
@role_required("Nomcom Chair", "Nomcom Advisor")
|
||||
|
|
|
@ -8,7 +8,7 @@ import debug # pyflakes:ignore
|
|||
|
||||
from ietf.person.models import Email, Person
|
||||
|
||||
def tokeninput_id_name_json(objs):
|
||||
def select2_id_name_json(objs):
|
||||
def format_email(e):
|
||||
return escape(u"%s <%s>" % (e.person.name, e.address))
|
||||
def format_person(p):
|
||||
|
@ -16,63 +16,63 @@ def tokeninput_id_name_json(objs):
|
|||
|
||||
formatter = format_email if objs and isinstance(objs[0], Email) else format_person
|
||||
|
||||
return json.dumps([{ "id": o.pk, "name": formatter(o) } for o in objs if o])
|
||||
return json.dumps([{ "id": o.pk, "text": formatter(o) } for o in objs if o])
|
||||
|
||||
class AutocompletedPersonsField(forms.CharField):
|
||||
"""Tokenizing autocompleted multi-select field for choosing
|
||||
persons/emails or just persons using jquery.tokeninput.js.
|
||||
class SearchablePersonsField(forms.CharField):
|
||||
"""Server-based multi-select field for choosing
|
||||
persons/emails or just persons using select2.js.
|
||||
|
||||
The field operates on either Email or Person models. In the case
|
||||
of Email models, the person name is shown next to the email address.
|
||||
of Email models, the person name is shown next to the email
|
||||
address.
|
||||
|
||||
The field uses a comma-separated list of primary keys in a
|
||||
CharField element as its API, the tokeninput Javascript adds some
|
||||
selection magic on top of this so we have to pass it a JSON
|
||||
representation of ids and user-understandable labels."""
|
||||
CharField element as its API with some extra attributes used by
|
||||
the Javascript part."""
|
||||
|
||||
def __init__(self,
|
||||
max_entries=None, # max number of selected objs
|
||||
only_users=False, # only select persons who also have a user
|
||||
model=Person, # or Email
|
||||
hint_text="Type in name to search for person",
|
||||
hint_text="Type in name to search for person.",
|
||||
*args, **kwargs):
|
||||
kwargs["max_length"] = 1000
|
||||
self.max_entries = max_entries
|
||||
self.only_users = only_users
|
||||
self.model = model
|
||||
|
||||
super(AutocompletedPersonsField, self).__init__(*args, **kwargs)
|
||||
super(SearchablePersonsField, self).__init__(*args, **kwargs)
|
||||
|
||||
self.widget.attrs["class"] = "tokenized-field"
|
||||
self.widget.attrs["data-hint-text"] = hint_text
|
||||
self.widget.attrs["class"] = "select2-field"
|
||||
self.widget.attrs["data-placeholder"] = hint_text
|
||||
if self.max_entries != None:
|
||||
self.widget.attrs["data-max-entries"] = self.max_entries
|
||||
|
||||
def parse_tokenized_value(self, value):
|
||||
def parse_select2_value(self, value):
|
||||
return [x.strip() for x in value.split(",") if x.strip()]
|
||||
|
||||
def prepare_value(self, value):
|
||||
if not value:
|
||||
value = ""
|
||||
if isinstance(value, basestring):
|
||||
pks = self.parse_tokenized_value(value)
|
||||
pks = self.parse_select2_value(value)
|
||||
value = self.model.objects.filter(pk__in=pks).select_related("person")
|
||||
if isinstance(value, self.model):
|
||||
value = [value]
|
||||
|
||||
self.widget.attrs["data-pre"] = tokeninput_id_name_json(value)
|
||||
self.widget.attrs["data-pre"] = select2_id_name_json(value)
|
||||
|
||||
# doing this in the constructor is difficult because the URL
|
||||
# patterns may not have been fully constructed there yet
|
||||
self.widget.attrs["data-ajax-url"] = urlreverse("ajax_tokeninput_search", kwargs={ "model_name": self.model.__name__.lower() })
|
||||
self.widget.attrs["data-ajax-url"] = urlreverse("ajax_select2_search_person_email", kwargs={ "model_name": self.model.__name__.lower() })
|
||||
if self.only_users:
|
||||
self.widget.attrs["data-ajax-url"] += "?user=1" # require a Datatracker account
|
||||
|
||||
return ",".join(e.address for e in value)
|
||||
return u",".join(e.address for e in value)
|
||||
|
||||
def clean(self, value):
|
||||
value = super(AutocompletedPersonsField, self).clean(value)
|
||||
pks = self.parse_tokenized_value(value)
|
||||
value = super(SearchablePersonsField, self).clean(value)
|
||||
pks = self.parse_select2_value(value)
|
||||
|
||||
objs = self.model.objects.filter(pk__in=pks)
|
||||
if self.model == Email:
|
||||
|
@ -92,32 +92,32 @@ class AutocompletedPersonsField(forms.CharField):
|
|||
|
||||
return objs
|
||||
|
||||
class AutocompletedPersonField(AutocompletedPersonsField):
|
||||
"""Version of AutocompletedPersonsField specialized to a single object."""
|
||||
class SearchablePersonField(SearchablePersonsField):
|
||||
"""Version of SearchablePersonsField specialized to a single object."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs["max_entries"] = 1
|
||||
super(AutocompletedPersonField, self).__init__(*args, **kwargs)
|
||||
super(SearchablePersonField, self).__init__(*args, **kwargs)
|
||||
|
||||
def clean(self, value):
|
||||
return super(AutocompletedPersonField, self).clean(value).first()
|
||||
return super(SearchablePersonField, self).clean(value).first()
|
||||
|
||||
|
||||
class AutocompletedEmailsField(AutocompletedPersonsField):
|
||||
"""Version of AutocompletedPersonsField with the defaults right for Emails."""
|
||||
class SearchableEmailsField(SearchablePersonsField):
|
||||
"""Version of SearchablePersonsField with the defaults right for Emails."""
|
||||
|
||||
def __init__(self, model=Email, hint_text="Type in name or email to search for person and email address",
|
||||
def __init__(self, model=Email, hint_text="Type in name or email to search for person and email address.",
|
||||
*args, **kwargs):
|
||||
super(AutocompletedEmailsField, self).__init__(model=model, hint_text=hint_text, *args, **kwargs)
|
||||
super(SearchableEmailsField, self).__init__(model=model, hint_text=hint_text, *args, **kwargs)
|
||||
|
||||
class AutocompletedEmailField(AutocompletedEmailsField):
|
||||
"""Version of AutocompletedEmailsField specialized to a single object."""
|
||||
class SearchableEmailField(SearchableEmailsField):
|
||||
"""Version of SearchableEmailsField specialized to a single object."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs["max_entries"] = 1
|
||||
super(AutocompletedEmailField, self).__init__(*args, **kwargs)
|
||||
super(SearchableEmailField, self).__init__(*args, **kwargs)
|
||||
|
||||
def clean(self, value):
|
||||
return super(AutocompletedEmailField, self).clean(value).first()
|
||||
return super(SearchableEmailField, self).clean(value).first()
|
||||
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ class PersonTests(TestCase):
|
|||
draft = make_test_data()
|
||||
person = draft.ad
|
||||
|
||||
r = self.client.get(urlreverse("ietf.person.views.ajax_tokeninput_search", kwargs={ "model_name": "email"}), dict(q=person.name))
|
||||
r = self.client.get(urlreverse("ietf.person.views.ajax_select2_search", kwargs={ "model_name": "email"}), dict(q=person.name))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
data = json.loads(r.content)
|
||||
self.assertEqual(data[0]["id"], person.email_address())
|
||||
|
|
|
@ -2,6 +2,6 @@ from django.conf.urls import patterns
|
|||
from ietf.person import ajax
|
||||
|
||||
urlpatterns = patterns('',
|
||||
(r'^search/(?P<model_name>(person|email))/$', "ietf.person.views.ajax_tokeninput_search", None, 'ajax_tokeninput_search'),
|
||||
(r'^search/(?P<model_name>(person|email))/$', "ietf.person.views.ajax_select2_search", None, 'ajax_select2_search_person_email'),
|
||||
(r'^(?P<personid>[a-z0-9]+).json$', ajax.person_json),
|
||||
)
|
||||
|
|
|
@ -2,9 +2,9 @@ from django.http import HttpResponse
|
|||
from django.db.models import Q
|
||||
|
||||
from ietf.person.models import Email, Person
|
||||
from ietf.person.fields import tokeninput_id_name_json
|
||||
from ietf.person.fields import select2_id_name_json
|
||||
|
||||
def ajax_tokeninput_search(request, model_name):
|
||||
def ajax_select2_search(request, model_name):
|
||||
if model_name == "email":
|
||||
model = Email
|
||||
else:
|
||||
|
@ -39,6 +39,11 @@ def ajax_tokeninput_search(request, model_name):
|
|||
if only_users:
|
||||
objs = objs.exclude(user=None)
|
||||
|
||||
objs = objs.distinct()[:10]
|
||||
try:
|
||||
page = int(request.GET.get("p", 1)) - 1
|
||||
except ValueError:
|
||||
page = 0
|
||||
|
||||
return HttpResponse(tokeninput_id_name_json(objs), content_type='application/json')
|
||||
objs = objs.distinct()[page:page + 10]
|
||||
|
||||
return HttpResponse(select2_id_name_json(objs), content_type='application/json')
|
||||
|
|
|
@ -8,7 +8,7 @@ from ietf.doc.models import Document, DocAlias, State
|
|||
from ietf.name.models import IntendedStdLevelName, DocRelationshipName
|
||||
from ietf.group.models import Group
|
||||
from ietf.person.models import Person, Email
|
||||
from ietf.person.fields import AutocompletedEmailField
|
||||
from ietf.person.fields import SearchableEmailField
|
||||
from ietf.secr.groups.forms import get_person
|
||||
|
||||
|
||||
|
@ -82,7 +82,7 @@ class AliasModelChoiceField(forms.ModelChoiceField):
|
|||
# ---------------------------------------------
|
||||
class AddModelForm(forms.ModelForm):
|
||||
start_date = forms.DateField()
|
||||
group = GroupModelChoiceField(required=True,help_text='Use group "none" for Individual Submissions')
|
||||
group = GroupModelChoiceField(required=True,help_text='Use group "none" for Individual Submissions.')
|
||||
|
||||
class Meta:
|
||||
model = Document
|
||||
|
@ -103,7 +103,7 @@ class AuthorForm(forms.Form):
|
|||
see an id_email field
|
||||
'''
|
||||
person = forms.CharField(max_length=50,widget=forms.TextInput(attrs={'class':'name-autocomplete'}),help_text="To see a list of people type the first name, or last name, or both.")
|
||||
email = forms.CharField(widget=forms.Select(),help_text="Select an email")
|
||||
email = forms.CharField(widget=forms.Select(),help_text="Select an email.")
|
||||
|
||||
# check for id within parenthesis to ensure name was selected from the list
|
||||
def clean_person(self):
|
||||
|
@ -132,7 +132,7 @@ class EditModelForm(forms.ModelForm):
|
|||
iesg_state = forms.ModelChoiceField(queryset=State.objects.filter(type='draft-iesg'),required=False)
|
||||
group = GroupModelChoiceField(required=True)
|
||||
review_by_rfc_editor = forms.BooleanField(required=False)
|
||||
shepherd = AutocompletedEmailField(required=False, only_users=True)
|
||||
shepherd = SearchableEmailField(required=False, only_users=True)
|
||||
|
||||
class Meta:
|
||||
model = Document
|
||||
|
@ -372,5 +372,5 @@ class UploadForm(forms.Form):
|
|||
return self.cleaned_data
|
||||
|
||||
class WithdrawForm(forms.Form):
|
||||
type = forms.CharField(widget=forms.Select(choices=WITHDRAW_CHOICES),help_text='Select which type of withdraw to perform')
|
||||
type = forms.CharField(widget=forms.Select(choices=WITHDRAW_CHOICES),help_text='Select which type of withdraw to perform.')
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ from django import forms
|
|||
|
||||
from ietf.group.models import Group
|
||||
from ietf.meeting.models import ResourceAssociation
|
||||
from ietf.person.fields import AutocompletedPersonsField
|
||||
from ietf.person.fields import SearchablePersonsField
|
||||
|
||||
|
||||
# -------------------------------------------------
|
||||
|
@ -67,7 +67,7 @@ class SessionForm(forms.Form):
|
|||
wg_selector3 = forms.ChoiceField(choices=WG_CHOICES,required=False)
|
||||
third_session = forms.BooleanField(required=False)
|
||||
resources = forms.MultipleChoiceField(choices=[(x.pk,x.desc) for x in ResourceAssociation.objects.all()], widget=forms.CheckboxSelectMultiple,required=False)
|
||||
bethere = AutocompletedPersonsField(label="Must be present", required=False)
|
||||
bethere = SearchablePersonsField(label="Must be present", required=False)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(SessionForm, self).__init__(*args, **kwargs)
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
{% block extrastyle %}{% endblock %}
|
||||
|
||||
{% block extrahead %}
|
||||
<script type="text/javascript" src="{{ SECR_STATIC_URL }}js/jquery-1.5.1.min.js"></script>
|
||||
<script type="text/javascript" src="/js/lib/jquery-1.11.1.min.js"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block blockbots %}<meta name="robots" content="NONE,NOARCHIVE" />{% endblock %}
|
||||
|
|
|
@ -7,9 +7,11 @@
|
|||
<link rel="stylesheet" type="text/css" href="{{ SECR_STATIC_URL }}css/jquery.ui.autocomplete.css" />
|
||||
<script type="text/javascript" src="{{ SECR_STATIC_URL }}js/jquery-ui-1.8.1.custom.min.js"></script>
|
||||
<script type="text/javascript" src="{{ SECR_STATIC_URL }}js/utils.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/css/token-input.css"></link>
|
||||
<script type="text/javascript" src="/js/lib/jquery.tokeninput.js"></script>
|
||||
<script type="text/javascript" src="/js/tokenized-field.js"></script>
|
||||
|
||||
<link rel="stylesheet" href="/css/lib/select2.css">
|
||||
<link rel="stylesheet" href="/css/lib/select2-bootstrap.css">
|
||||
<script src="/js/lib/select2-3.5.2.min.js"></script>
|
||||
<script src="/js/select2-field.js"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block breadcrumbs %}{{ block.super }}
|
||||
|
|
|
@ -5,9 +5,11 @@
|
|||
{% block extrahead %}{{ block.super }}
|
||||
<script type="text/javascript" src="{{ SECR_STATIC_URL }}js/utils.js"></script>
|
||||
<script type="text/javascript" src="{{ SECR_STATIC_URL }}js/sessions.js"></script>
|
||||
<script type="text/javascript" src="/js/lib/jquery.tokeninput.js"></script>
|
||||
<script type="text/javascript" src="/js/tokenized-field.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/css/token-input.css"></link>
|
||||
|
||||
<link rel="stylesheet" href="/css/lib/select2.css">
|
||||
<link rel="stylesheet" href="/css/lib/select2-bootstrap.css">
|
||||
<script src="/js/lib/select2-3.5.2.min.js"></script>
|
||||
<script src="/js/select2-field.js"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block breadcrumbs %}{{ block.super }}
|
||||
|
|
|
@ -1,67 +0,0 @@
|
|||
{% extends "base_site.html" %}
|
||||
{% load ietf_filters %}
|
||||
|
||||
{% block title %}Sessions{% endblock %}
|
||||
|
||||
{% block extrahead %}{{ block.super }}
|
||||
<script type="text/javascript" src="/static/js/utils.js"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block breadcrumbs %}{{ block.super }}
|
||||
» Sessions
|
||||
{% endblock %}
|
||||
{% block instructions %}
|
||||
<a href="https://www.ietf.org/wg/request-tool-instructions.html" target="_blank">Instructions</a>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="module interim-container">
|
||||
<h2>
|
||||
Sessions Request Tool: IETF {{ meeting.meeting_num }}
|
||||
{% if user|has_role:"Secretariat" %}
|
||||
{% if is_locked %}
|
||||
<span class="locked"><a href="{% url "sessions_tool_status" %}">Tool Status: Locked</a></span>
|
||||
{% else %}
|
||||
<span class="unlocked"><a href="{% url "sessions_tool_status" %}">Tool Status: Unlocked</a></span>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</h2>
|
||||
<div class="inline-related">
|
||||
<h3><b>Request New Session</b></h3>
|
||||
<p>The list below includes those working groups that you currently chair which do not already have a session scheduled. You can click on an acronym to initiate a request for a new session at the upcoming IETF meeting. Click "Group will not meet" to send a notification that the group does not plan to meet.</p>
|
||||
<ul>
|
||||
{% for group in unscheduled_groups %}
|
||||
<li>
|
||||
<a href="{% url "sessions_new" acronym=group.acronym %}">{{ group.acronym }}</a>
|
||||
{% if meeting in group.meetings_not_scheduled.all %}
|
||||
<span class="required"> (Currently, this group does not plan to hold a session at IETF {{ meeting.meeting_num }})</span>
|
||||
{% else %}
|
||||
<!--
|
||||
<a href="{% url "sessions_no_session" acronym=group.acronym %}"> - [ Group will not meet ]</a>
|
||||
-->
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div> <!-- inline-related -->
|
||||
<br>
|
||||
<div class="inline-related">
|
||||
<h2></h2>
|
||||
<h3><b>Edit / Cancel Previously Requested Sessions</b></h3>
|
||||
<p>The list below includes those working groups for which you or your co-chair has requested sessions at the upcoming IETF meeting. You can click on an acronym to initiate changes to a session, or cancel a session.</p>
|
||||
<ul>
|
||||
{% for session in scheduled_sessions %}
|
||||
<li><a href="{% url "sessions_view" session_id=session.session_id %}">{{ session.group }} - {% if not session.ts_status_id %}{{ session.status }}{% else %}First Two Sessions:{{ session.status }}, Third Session:{{ session.ts_status }}{% endif %}</a></li>
|
||||
{% empty %}
|
||||
<i>NONE</i>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div> <!-- inline-related -->
|
||||
</div> <!-- module -->
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block footer-extras %}
|
||||
{% include "includes/sessions_footer.html" %}
|
||||
{% endblock %}
|
|
@ -5,9 +5,11 @@
|
|||
{% block extrahead %}{{ block.super }}
|
||||
<script type="text/javascript" src="{{ SECR_STATIC_URL }}js/utils.js"></script>
|
||||
<script type="text/javascript" src="{{ SECR_STATIC_URL }}js/sessions.js"></script>
|
||||
<script type="text/javascript" src="/js/lib/jquery.tokeninput.js"></script>
|
||||
<script type="text/javascript" src="/js/tokenized-field.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/css/token-input.css"></link>
|
||||
|
||||
<link rel="stylesheet" href="/css/lib/select2.css">
|
||||
<link rel="stylesheet" href="/css/lib/select2-bootstrap.css">
|
||||
<script src="/js/lib/select2-3.5.2.min.js"></script>
|
||||
<script src="/js/select2-field.js"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block breadcrumbs %}{{ block.super }}
|
||||
|
|
|
@ -14,7 +14,7 @@ except ImportError:
|
|||
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
# a place to put ajax logs if necessary.
|
||||
LOG_DIR = '/var/log/datatracker'
|
||||
LOG_DIR = '/var/log/datatracker'
|
||||
|
||||
import sys
|
||||
sys.path.append(os.path.abspath(BASE_DIR + "/.."))
|
||||
|
@ -63,9 +63,9 @@ DATABASES = {
|
|||
}
|
||||
|
||||
DATABASE_TEST_OPTIONS = {
|
||||
# Comment this out if your database doesn't support InnoDB
|
||||
'init_command': 'SET storage_engine=InnoDB',
|
||||
}
|
||||
# Comment this out if your database doesn't support InnoDB
|
||||
'init_command': 'SET storage_engine=InnoDB',
|
||||
}
|
||||
|
||||
# Local time zone for this installation. Choices can be found here:
|
||||
# http://www.postgresql.org/docs/8.1/static/datetime-keywords.html#DATETIME-TIMEZONE-SET-TABLE
|
||||
|
@ -87,10 +87,11 @@ USE_I18N = False
|
|||
|
||||
USE_TZ = False
|
||||
|
||||
MEDIA_URL = 'http://www.ietf.org/'
|
||||
MEDIA_URL = '//www.ietf.org/'
|
||||
|
||||
STATIC_URL = "/"
|
||||
STATIC_ROOT = os.path.abspath(BASE_DIR + "/../static/")
|
||||
STATIC_URL = STATIC_ROOT + '/'
|
||||
|
||||
WSGI_APPLICATION = "ietf.wsgi.application"
|
||||
|
||||
|
@ -147,8 +148,10 @@ SESSION_EXPIRE_AT_BROWSER_CLOSE = False
|
|||
SESSION_SERIALIZER = 'django.contrib.sessions.serializers.PickleSerializer'
|
||||
|
||||
TEMPLATE_LOADERS = (
|
||||
'django.template.loaders.filesystem.Loader',
|
||||
'django.template.loaders.app_directories.Loader',
|
||||
('django.template.loaders.cached.Loader', (
|
||||
'django.template.loaders.filesystem.Loader',
|
||||
'django.template.loaders.app_directories.Loader',
|
||||
)),
|
||||
'ietf.dbtemplate.template.Loader',
|
||||
)
|
||||
|
||||
|
@ -200,6 +203,9 @@ INSTALLED_APPS = (
|
|||
'django.contrib.messages',
|
||||
'south',
|
||||
'tastypie',
|
||||
'widget_tweaks',
|
||||
'typogrify',
|
||||
'bootstrap3',
|
||||
'ietf.person',
|
||||
'ietf.name',
|
||||
'ietf.group',
|
||||
|
@ -234,6 +240,28 @@ INSTALLED_APPS = (
|
|||
'ietf.dbtemplate',
|
||||
)
|
||||
|
||||
# Settings for django-bootstrap3
|
||||
# See http://django-bootstrap3.readthedocs.org/en/latest/settings.html
|
||||
BOOTSTRAP3 = {
|
||||
# Label class to use in horizontal forms
|
||||
'horizontal_label_class': 'col-md-2',
|
||||
|
||||
# Field class to use in horiozntal forms
|
||||
'horizontal_field_class': 'col-md-10',
|
||||
|
||||
# Set HTML required attribute on required fields
|
||||
'set_required': True,
|
||||
|
||||
# Set placeholder attributes to label if no placeholder is provided
|
||||
'set_placeholder': False,
|
||||
|
||||
# Class to indicate required
|
||||
'form_required_class': 'bootstrap3-required',
|
||||
|
||||
# Class to indicate error
|
||||
'form_error_class': 'bootstrap3-error',
|
||||
}
|
||||
|
||||
INTERNAL_IPS = (
|
||||
# AMS servers
|
||||
'64.170.98.32',
|
||||
|
@ -292,22 +320,22 @@ INTERNET_DRAFT_ARCHIVE_DIR = '/a/www/www6s/draft-archive'
|
|||
MEETING_RECORDINGS_DIR = '/a/www/audio'
|
||||
|
||||
# Mailing list info URL for lists hosted on the IETF servers
|
||||
MAILING_LIST_INFO_URL = "https://www.ietf.org/mailman/listinfo/%(list_addr)s"
|
||||
MAILING_LIST_INFO_URL = "//www.ietf.org/mailman/listinfo/%(list_addr)s"
|
||||
|
||||
# Ideally, more of these would be local -- but since we don't support
|
||||
# versions right now, we'll point to external websites
|
||||
DOC_HREFS = {
|
||||
"charter": "http://www.ietf.org/charter/{doc.name}-{doc.rev}.txt",
|
||||
"draft": "http://www.ietf.org/archive/id/{doc.name}-{doc.rev}.txt",
|
||||
"slides": "http://www.ietf.org/slides/{doc.name}-{doc.rev}",
|
||||
"conflrev": "http://www.ietf.org/cr/{doc.name}-{doc.rev}.txt",
|
||||
"statchg": "http://www.ietf.org/sc/{doc.name}-{doc.rev}.txt",
|
||||
"charter": "//www.ietf.org/charter/{doc.name}-{doc.rev}.txt",
|
||||
"draft": "//www.ietf.org/archive/id/{doc.name}-{doc.rev}.txt",
|
||||
"slides": "//www.ietf.org/slides/{doc.name}-{doc.rev}",
|
||||
"conflrev": "//www.ietf.org/cr/{doc.name}-{doc.rev}.txt",
|
||||
"statchg": "//www.ietf.org/sc/{doc.name}-{doc.rev}.txt",
|
||||
}
|
||||
|
||||
MEETING_DOC_HREFS = {
|
||||
"agenda": "/meeting/{meeting}/agenda/{doc.group.acronym}/",
|
||||
"minutes": "http://www.ietf.org/proceedings/{meeting}/minutes/{doc.external_url}",
|
||||
"slides": "http://www.ietf.org/proceedings/{meeting}/slides/{doc.external_url}",
|
||||
"minutes": "//www.ietf.org/proceedings/{meeting}/minutes/{doc.external_url}",
|
||||
"slides": "//www.ietf.org/proceedings/{meeting}/slides/{doc.external_url}",
|
||||
"recording": "{doc.external_url}",
|
||||
}
|
||||
|
||||
|
@ -332,15 +360,15 @@ IANA_APPROVE_EMAIL = "drafts-approval@icann.org"
|
|||
|
||||
# Put real password in settings_local.py
|
||||
IANA_SYNC_PASSWORD = "secret"
|
||||
IANA_SYNC_CHANGES_URL = "https://datatracker.iana.org:4443/data-tracker/changes"
|
||||
IANA_SYNC_PROTOCOLS_URL = "http://www.iana.org/protocols/"
|
||||
IANA_SYNC_CHANGES_URL = "//datatracker.iana.org:4443/data-tracker/changes"
|
||||
IANA_SYNC_PROTOCOLS_URL = "//www.iana.org/protocols/"
|
||||
|
||||
RFC_TEXT_RSYNC_SOURCE="ftp.rfc-editor.org::rfcs-text-only"
|
||||
|
||||
RFC_EDITOR_SYNC_PASSWORD="secret"
|
||||
RFC_EDITOR_SYNC_NOTIFICATION_URL = "http://www.rfc-editor.org/parser/parser.php"
|
||||
RFC_EDITOR_QUEUE_URL = "http://www.rfc-editor.org/queue2.xml"
|
||||
RFC_EDITOR_INDEX_URL = "http://www.rfc-editor.org/rfc/rfc-index.xml"
|
||||
RFC_EDITOR_SYNC_NOTIFICATION_URL = "//www.rfc-editor.org/parser/parser.php"
|
||||
RFC_EDITOR_QUEUE_URL = "//www.rfc-editor.org/queue2.xml"
|
||||
RFC_EDITOR_INDEX_URL = "//www.rfc-editor.org/rfc/rfc-index.xml"
|
||||
|
||||
# Liaison Statement Tool settings
|
||||
LIAISON_UNIVERSAL_FROM = 'Liaison Statement Management Tool <lsmt@' + IETF_DOMAIN + '>'
|
||||
|
@ -376,7 +404,7 @@ INTERNET_DRAFT_DAYS_TO_EXPIRE = 185
|
|||
|
||||
IDSUBMIT_REPOSITORY_PATH = INTERNET_DRAFT_PATH
|
||||
IDSUBMIT_STAGING_PATH = '/a/www/www6s/staging/'
|
||||
IDSUBMIT_STAGING_URL = 'http://www.ietf.org/staging/'
|
||||
IDSUBMIT_STAGING_URL = '//www.ietf.org/staging/'
|
||||
IDSUBMIT_IDNITS_BINARY = '/a/www/ietf-datatracker/scripts/idnits'
|
||||
|
||||
IDSUBMIT_MAX_PLAIN_DRAFT_SIZE = 6291456 # Max size of the txt draft in bytes
|
||||
|
@ -410,7 +438,7 @@ TZDATA_ICS_PATH = BASE_DIR + '/../vzic/zoneinfo/'
|
|||
CHANGELOG_PATH = '/www/ietf-datatracker/web/changelog'
|
||||
|
||||
SECR_BLUE_SHEET_PATH = '/a/www/ietf-datatracker/documents/blue_sheet.rtf'
|
||||
SECR_BLUE_SHEET_URL = 'https://datatracker.ietf.org/documents/blue_sheet.rtf'
|
||||
SECR_BLUE_SHEET_URL = '//datatracker.ietf.org/documents/blue_sheet.rtf'
|
||||
SECR_INTERIM_LISTING_DIR = '/a/www/www6/meeting/interim'
|
||||
SECR_MAX_UPLOAD_SIZE = 40960000
|
||||
SECR_PROCEEDINGS_DIR = '/a/www/www6s/proceedings/'
|
||||
|
@ -454,6 +482,9 @@ GROUP_ALIAS_DOMAIN = IETF_DOMAIN
|
|||
# Path to the email alias lists. Used by ietf.utils.aliases
|
||||
DRAFT_ALIASES_PATH = "/a/postfix/draft-aliases"
|
||||
DRAFT_VIRTUAL_PATH = "/a/postfix/draft-virtual"
|
||||
|
||||
# Set debug apps in DEV_APPS settings_local
|
||||
DEV_APPS = ()
|
||||
DRAFT_VIRTUAL_DOMAIN = "virtual.ietf.org"
|
||||
|
||||
GROUP_ALIASES_PATH = "/a/postfix/group-aliases"
|
||||
|
@ -466,11 +497,17 @@ POSTCONFIRM_PATH = "/a/postconfirm/test-wrapper"
|
|||
# sensitive or site-specific changes. DO NOT commit settings_local.py to svn.
|
||||
from settings_local import * # pyflakes:ignore
|
||||
|
||||
# Add DEV_APPS to INSTALLED_APPS
|
||||
INSTALLED_APPS += DEV_APPS
|
||||
|
||||
# We provide a secret key only for test and development modes. It's
|
||||
# absolutely vital that django fails to start in production mode unless a
|
||||
# secret key has been provided elsewhere, not in this file which is
|
||||
# publicly available, for instance from the source repository.
|
||||
if SERVER_MODE != 'production':
|
||||
# stomp out the cached template loader, it's annoying
|
||||
TEMPLATE_LOADERS = tuple(l for e in TEMPLATE_LOADERS for l in (e[1] if isinstance(e, tuple) and "cached.Loader" in e[0] else (e,)))
|
||||
|
||||
CACHES = {
|
||||
'default': {
|
||||
'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
|
||||
|
|
|
@ -47,20 +47,20 @@ class UploadForm(forms.Form):
|
|||
ietf_monday = Meeting.get_ietf_monday()
|
||||
|
||||
if now.date() >= (first_cut_off-timedelta(days=settings.CUTOFF_WARNING_DAYS)) and now.date() < first_cut_off:
|
||||
self.cutoff_warning = ( 'The pre-meeting cut-off date for new documents (i.e., version -00 Internet-Drafts) is %s at %02sh UTC.<br/>' % (first_cut_off, settings.CUTOFF_HOUR) +
|
||||
'The pre-meeting cut-off date for revisions to existing documents is %s at %02sh UTC.<br/>' % (second_cut_off, settings.CUTOFF_HOUR) )
|
||||
self.cutoff_warning = ( 'The pre-meeting cut-off date for new documents (i.e., version -00 Internet-Drafts) is %s at %02sh UTC.' % (first_cut_off, settings.CUTOFF_HOUR) +
|
||||
'The pre-meeting cut-off date for revisions to existing documents is %s at %02sh UTC.' % (second_cut_off, settings.CUTOFF_HOUR) )
|
||||
elif now.date() >= first_cut_off and now.date() < second_cut_off: # We are in the first_cut_off
|
||||
if now.date() == first_cut_off and now.hour < settings.CUTOFF_HOUR:
|
||||
self.cutoff_warning = 'The pre-meeting cut-off date for new documents (i.e., version -00 Internet-Drafts) is %s, at %02sh UTC. After that, you will not be able to submit a new document until %s, at %sh UTC' % (first_cut_off, settings.CUTOFF_HOUR, ietf_monday, settings.CUTOFF_HOUR, )
|
||||
else: # No 00 version allowed
|
||||
self.cutoff_warning = 'The pre-meeting cut-off date for new documents (i.e., version -00 Internet-Drafts) was %s at %sh UTC. You will not be able to submit a new document until %s, at %sh UTC.<br>You can still submit a version -01 or higher Internet-Draft until %sh UTC, %s' % (first_cut_off, settings.CUTOFF_HOUR, ietf_monday, settings.CUTOFF_HOUR, settings.CUTOFF_HOUR, second_cut_off, )
|
||||
self.cutoff_warning = 'The pre-meeting cut-off date for new documents (i.e., version -00 Internet-Drafts) was %s at %sh UTC. You will not be able to submit a new document until %s, at %sh UTC. You can still submit a version -01 or higher Internet-Draft until %sh UTC, %s' % (first_cut_off, settings.CUTOFF_HOUR, ietf_monday, settings.CUTOFF_HOUR, settings.CUTOFF_HOUR, second_cut_off, )
|
||||
self.in_first_cut_off = True
|
||||
elif now.date() >= second_cut_off and now.date() < ietf_monday:
|
||||
if now.date() == second_cut_off and now.hour < settings.CUTOFF_HOUR: # We are in the first_cut_off yet
|
||||
self.cutoff_warning = 'The pre-meeting cut-off date for new documents (i.e., version -00 Internet-Drafts) was %s at %02sh UTC. You will not be able to submit a new document until %s, at %02sh UTC.<br>The I-D submission tool will be shut down at %02sh UTC today, and reopened at %02sh UTC on %s' % (first_cut_off, settings.CUTOFF_HOUR, ietf_monday, settings.CUTOFF_HOUR, settings.CUTOFF_HOUR, settings.CUTOFF_HOUR, ietf_monday)
|
||||
self.cutoff_warning = 'The pre-meeting cut-off date for new documents (i.e., version -00 Internet-Drafts) was %s at %02sh UTC. You will not be able to submit a new document until %s, at %02sh UTC. The I-D submission tool will be shut down at %02sh UTC today, and reopened at %02sh UTC on %s' % (first_cut_off, settings.CUTOFF_HOUR, ietf_monday, settings.CUTOFF_HOUR, settings.CUTOFF_HOUR, settings.CUTOFF_HOUR, ietf_monday)
|
||||
self.in_first_cut_off = True
|
||||
else: # Completely shut down of the tool
|
||||
self.cutoff_warning = 'The cut-off time for the I-D submission was %02dh UTC, %s.<br>The I-D submission tool will be reopened at %02dh local time at the IETF meeting location, %s.' % (settings.CUTOFF_HOUR, second_cut_off, settings.CUTOFF_HOUR, ietf_monday)
|
||||
self.cutoff_warning = 'The cut-off time for the I-D submission was %02dh UTC, %s. The I-D submission tool will be reopened at %02dh local time at the IETF meeting location, %s.' % (settings.CUTOFF_HOUR, second_cut_off, settings.CUTOFF_HOUR, ietf_monday)
|
||||
self.shutdown = True
|
||||
|
||||
def clean_file(self, field_name, parser_class):
|
||||
|
@ -116,7 +116,7 @@ class UploadForm(forms.Form):
|
|||
# check existing
|
||||
existing = Submission.objects.filter(name=self.parsed_draft.filename, rev=self.parsed_draft.revision).exclude(state__in=("posted", "cancel"))
|
||||
if existing:
|
||||
raise forms.ValidationError(mark_safe('Submission with same name and revision is currently being processed. <a href="%s">Check the status here</a>' % urlreverse("submit_submission_status", kwargs={ 'submission_id': existing[0].pk })))
|
||||
raise forms.ValidationError(mark_safe('Submission with same name and revision is currently being processed. <a href="%s">Check the status here.</a>' % urlreverse("submit_submission_status", kwargs={ 'submission_id': existing[0].pk })))
|
||||
|
||||
# cut-off
|
||||
if self.parsed_draft.revision == '00' and self.in_first_cut_off:
|
||||
|
@ -239,7 +239,7 @@ class EditSubmissionForm(forms.ModelForm):
|
|||
pages = forms.IntegerField(required=True)
|
||||
abstract = forms.CharField(widget=forms.Textarea, required=True)
|
||||
|
||||
note = forms.CharField(label=mark_safe(u'Comment to<br/> the Secretariat'), widget=forms.Textarea, required=False)
|
||||
note = forms.CharField(label=mark_safe(u'Comment to the Secretariat'), widget=forms.Textarea, required=False)
|
||||
|
||||
class Meta:
|
||||
model = Submission
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue