Summary: Resolve person/email/document multiselect issue by importing
select2 and switching the widgets over to using that. Port the milestones editing page to Bootstrap. - Legacy-Id: 8713
This commit is contained in:
parent
c7342d2f30
commit
cebc979282
|
@ -8,17 +8,16 @@ import debug # pyflakes:ignore
|
|||
|
||||
from ietf.doc.models import Document, DocAlias
|
||||
|
||||
def tokeninput_id_doc_name_json(objs):
|
||||
return json.dumps([{ "id": o.pk, "name": escape(o.name) } for o in objs])
|
||||
def select2_id_doc_name_json(objs):
|
||||
return json.dumps([{ "id": o.pk, "text": escape(o.name) } for o in objs])
|
||||
|
||||
class AutocompletedDocumentsField(forms.CharField):
|
||||
"""Tokenizing autocompleted multi-select field for choosing
|
||||
documents using jquery.tokeninput.js.
|
||||
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, 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
|
||||
|
@ -31,39 +30,45 @@ class AutocompletedDocumentsField(forms.CharField):
|
|||
self.doc_type = doc_type
|
||||
self.model = model
|
||||
|
||||
super(AutocompletedDocumentsField, self).__init__(*args, **kwargs)
|
||||
super(SearchableDocumentsField, 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)
|
||||
value = self.model.objects.filter(pk__in=pks, type=self.doc_type)
|
||||
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"] = tokeninput_id_doc_name_json(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_tokeninput_search_docs", kwargs={
|
||||
self.widget.attrs["data-ajax-url"] = urlreverse("ajax_select2_search_docs", kwargs={
|
||||
"doc_type": self.doc_type,
|
||||
"model_name": self.model.__name__.lower()
|
||||
})
|
||||
|
||||
return ",".join(o.pk for o in value)
|
||||
return u",".join(unicode(o.pk) for o in value)
|
||||
|
||||
def clean(self, value):
|
||||
value = super(AutocompletedDocumentsField, self).clean(value)
|
||||
pks = self.parse_tokenized_value(value)
|
||||
value = super(SearchableDocumentsField, self).clean(value)
|
||||
pks = self.parse_select2_value(value)
|
||||
|
||||
objs = self.model.objects.filter(pk__in=pks)
|
||||
|
||||
|
@ -77,7 +82,7 @@ class AutocompletedDocumentsField(forms.CharField):
|
|||
|
||||
return objs
|
||||
|
||||
class AutocompletedDocAliasField(AutocompletedDocumentsField):
|
||||
class SearchableDocAliasesField(SearchableDocumentsField):
|
||||
def __init__(self, model=DocAlias, *args, **kwargs):
|
||||
super(AutocompletedDocAliasField, self).__init__(model=model, *args, **kwargs)
|
||||
super(SearchableDocAliasesField, self).__init__(model=model, *args, **kwargs)
|
||||
|
||||
|
|
|
@ -127,7 +127,7 @@ class SearchTestCase(TestCase):
|
|||
draft = make_test_data()
|
||||
|
||||
# Document
|
||||
url = urlreverse("ajax_tokeninput_search_docs", kwargs={
|
||||
url = urlreverse("ajax_select2_search_docs", kwargs={
|
||||
"model_name": "document",
|
||||
"doc_type": "draft",
|
||||
})
|
||||
|
@ -139,7 +139,7 @@ class SearchTestCase(TestCase):
|
|||
# DocAlias
|
||||
doc_alias = draft.docalias_set.get()
|
||||
|
||||
url = urlreverse("ajax_tokeninput_search_docs", kwargs={
|
||||
url = urlreverse("ajax_select2_search_docs", kwargs={
|
||||
"model_name": "docalias",
|
||||
"doc_type": "draft",
|
||||
})
|
||||
|
|
|
@ -1227,8 +1227,7 @@ class ChangeReplacesTests(TestCase):
|
|||
|
||||
# Post that says replacea replaces base a
|
||||
self.assertEqual(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))
|
||||
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.assertEqual(Document.objects.get(name='draft-test-base-a').get_state().slug,'repl')
|
||||
|
@ -1236,23 +1235,20 @@ class ChangeReplacesTests(TestCase):
|
|||
# Post that says replaceboth replaces both base a and base b
|
||||
url = urlreverse('doc_change_replaces', kwargs=dict(name=self.replaceboth.name))
|
||||
self.assertEqual(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))
|
||||
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))
|
||||
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))
|
||||
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')
|
||||
|
||||
|
|
|
@ -49,7 +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'^tokeninputsearch/(?P<model_name>(document|docalias))/(?P<doc_type>draft)/$', views_search.ajax_tokeninput_search_docs, name="ajax_tokeninput_search_docs"),
|
||||
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"),
|
||||
|
|
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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"""
|
||||
|
@ -968,7 +955,7 @@ def edit_shepherd(request, name):
|
|||
c.desc = "Document shepherd changed to "+ (doc.shepherd.person.name if doc.shepherd else "(None)")
|
||||
c.save()
|
||||
|
||||
if doc.shepherd.formatted_email() not in doc.notify:
|
||||
if doc.shepherd and doc.shepherd.formatted_email() not in doc.notify:
|
||||
login = request.user.person
|
||||
addrs = doc.notify
|
||||
if addrs:
|
||||
|
|
|
@ -45,7 +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 tokeninput_id_doc_name_json
|
||||
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
|
||||
|
@ -629,7 +629,7 @@ def index_active_drafts(request):
|
|||
|
||||
return render_to_response("doc/index_active_drafts.html", { 'groups': groups }, context_instance=RequestContext(request))
|
||||
|
||||
def ajax_tokeninput_search_docs(request, model_name, doc_type):
|
||||
def ajax_select2_search_docs(request, model_name, doc_type):
|
||||
if model_name == "docalias":
|
||||
model = DocAlias
|
||||
else:
|
||||
|
@ -652,4 +652,4 @@ def ajax_tokeninput_search_docs(request, model_name, doc_type):
|
|||
|
||||
objs = qs.distinct().order_by("name")[:20]
|
||||
|
||||
return HttpResponse(tokeninput_id_doc_name_json(objs), content_type='application/json')
|
||||
return HttpResponse(select2_id_doc_name_json(objs), content_type='application/json')
|
||||
|
|
|
@ -19,7 +19,7 @@ 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
|
||||
|
||||
|
@ -29,11 +29,11 @@ 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(label="Chairs", required=False, only_users=True)
|
||||
secretaries = AutocompletedEmailsField(label="Secretarias", required=False, only_users=True)
|
||||
techadv = AutocompletedEmailsField(label="Technical Advisors", required=False, only_users=True)
|
||||
delegates = AutocompletedEmailsField(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))
|
||||
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").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)
|
||||
|
|
|
@ -303,7 +303,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,99 +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 AutocompletedDocumentsField
|
||||
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 = AutocompletedDocumentsField(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()))
|
||||
self.needs_review = needs_review
|
||||
|
||||
def clean_resolved(self):
|
||||
r = self.cleaned_data["resolved"].strip()
|
||||
|
@ -134,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
|
||||
|
@ -153,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
|
||||
|
||||
|
@ -180,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")
|
||||
|
||||
|
@ -257,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':
|
||||
|
@ -269,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:
|
||||
|
@ -311,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)
|
||||
|
||||
|
@ -326,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
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import os
|
||||
import shutil
|
||||
import calendar
|
||||
import json
|
||||
import datetime
|
||||
|
||||
from pyquery import PyQuery
|
||||
|
@ -530,23 +529,21 @@ 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
|
||||
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",
|
||||
|
@ -580,8 +577,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",
|
||||
|
@ -612,11 +608,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)
|
||||
|
@ -639,8 +634,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",
|
||||
|
@ -670,15 +664,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)
|
||||
|
@ -688,8 +681,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),
|
||||
|
|
|
@ -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
|
||||
|
@ -33,7 +33,7 @@ def stream_documents(request, acronym):
|
|||
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)
|
||||
|
|
|
@ -17,7 +17,7 @@ from ietf.nomcom.utils import (NOMINATION_RECEIPT_TEMPLATE, FEEDBACK_RECEIPT_TEM
|
|||
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
|
||||
|
||||
|
@ -657,7 +657,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
|
||||
|
|
|
@ -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,19 +16,19 @@ 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])
|
||||
return json.dumps([{ "id": o.pk, "text": formatter(o) } for o in objs])
|
||||
|
||||
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
|
||||
|
@ -41,38 +41,38 @@ class AutocompletedPersonsField(forms.CharField):
|
|||
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.",
|
||||
*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
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -7,9 +7,58 @@
|
|||
<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="/facelift/css/lib/select2.css">
|
||||
<link rel="stylesheet" href="/facelift/css/lib/select2-bootstrap.css">
|
||||
<script src="/facelift/js/lib/select2-3.5.2.min.js"></script>
|
||||
<script>
|
||||
// this is copy-pasted from ietf.js, we should include that here too instead of this
|
||||
$(document).ready(function () {
|
||||
$(".select2-field").each(function () {
|
||||
var e = $(this);
|
||||
if (e.data("ajax-url")) {
|
||||
var maxEntries = e.data("max-entries");
|
||||
var multiple = maxEntries != 1;
|
||||
var prefetched = e.data("pre");
|
||||
e.select2({
|
||||
multiple: multiple,
|
||||
minimumInputLength: 2,
|
||||
width: "off",
|
||||
allowClear: true,
|
||||
maximumSelectionSize: maxEntries,
|
||||
ajax: {
|
||||
url: e.data("ajax-url"),
|
||||
dataType: "json",
|
||||
quietMillis: 250,
|
||||
data: function (term, page) {
|
||||
return {
|
||||
q: term,
|
||||
p: page
|
||||
};
|
||||
},
|
||||
results: function (results) {
|
||||
return {
|
||||
results: results,
|
||||
more: results.length == 10
|
||||
};
|
||||
}
|
||||
},
|
||||
escapeMarkup: function (m) {
|
||||
return m;
|
||||
},
|
||||
initSelection: function (element, cb) {
|
||||
if (!multiple && prefetched.length > 0)
|
||||
cb(prefetched[0]);
|
||||
else
|
||||
cb(prefetched);
|
||||
|
||||
},
|
||||
dropdownCssClass: "bigdrop"
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% block breadcrumbs %}{{ block.super }}
|
||||
|
|
|
@ -5,9 +5,58 @@
|
|||
{% 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="/facelift/css/lib/select2.css">
|
||||
<link rel="stylesheet" href="/facelift/css/lib/select2-bootstrap.css">
|
||||
<script src="/facelift/js/lib/select2-3.5.2.min.js"></script>
|
||||
<script>
|
||||
// this is copy-pasted from ietf.js, we should include that here too instead of this
|
||||
$(document).ready(function () {
|
||||
$(".select2-field").each(function () {
|
||||
var e = $(this);
|
||||
if (e.data("ajax-url")) {
|
||||
var maxEntries = e.data("max-entries");
|
||||
var multiple = maxEntries != 1;
|
||||
var prefetched = e.data("pre");
|
||||
e.select2({
|
||||
multiple: multiple,
|
||||
minimumInputLength: 2,
|
||||
width: "off",
|
||||
allowClear: true,
|
||||
maximumSelectionSize: maxEntries,
|
||||
ajax: {
|
||||
url: e.data("ajax-url"),
|
||||
dataType: "json",
|
||||
quietMillis: 250,
|
||||
data: function (term, page) {
|
||||
return {
|
||||
q: term,
|
||||
p: page
|
||||
};
|
||||
},
|
||||
results: function (results) {
|
||||
return {
|
||||
results: results,
|
||||
more: results.length == 10
|
||||
};
|
||||
}
|
||||
},
|
||||
escapeMarkup: function (m) {
|
||||
return m;
|
||||
},
|
||||
initSelection: function (element, cb) {
|
||||
if (!multiple && prefetched.length > 0)
|
||||
cb(prefetched[0]);
|
||||
else
|
||||
cb(prefetched);
|
||||
|
||||
},
|
||||
dropdownCssClass: "bigdrop"
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% block breadcrumbs %}{{ block.super }}
|
||||
|
|
|
@ -5,9 +5,58 @@
|
|||
{% 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="/facelift/css/lib/select2.css">
|
||||
<link rel="stylesheet" href="/facelift/css/lib/select2-bootstrap.css">
|
||||
<script src="/facelift/js/lib/select2-3.5.2.min.js"></script>
|
||||
<script>
|
||||
// this is copy-pasted from ietf.js, we should include that here too instead of this
|
||||
$(document).ready(function () {
|
||||
$(".select2-field").each(function () {
|
||||
var e = $(this);
|
||||
if (e.data("ajax-url")) {
|
||||
var maxEntries = e.data("max-entries");
|
||||
var multiple = maxEntries != 1;
|
||||
var prefetched = e.data("pre");
|
||||
e.select2({
|
||||
multiple: multiple,
|
||||
minimumInputLength: 2,
|
||||
width: "off",
|
||||
allowClear: true,
|
||||
maximumSelectionSize: maxEntries,
|
||||
ajax: {
|
||||
url: e.data("ajax-url"),
|
||||
dataType: "json",
|
||||
quietMillis: 250,
|
||||
data: function (term, page) {
|
||||
return {
|
||||
q: term,
|
||||
p: page
|
||||
};
|
||||
},
|
||||
results: function (results) {
|
||||
return {
|
||||
results: results,
|
||||
more: results.length == 10
|
||||
};
|
||||
}
|
||||
},
|
||||
escapeMarkup: function (m) {
|
||||
return m;
|
||||
},
|
||||
initSelection: function (element, cb) {
|
||||
if (!multiple && prefetched.length > 0)
|
||||
cb(prefetched[0]);
|
||||
else
|
||||
cb(prefetched);
|
||||
|
||||
},
|
||||
dropdownCssClass: "bigdrop"
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% block breadcrumbs %}{{ block.super }}
|
||||
|
|
|
@ -7,8 +7,8 @@ Change document shepherd for {{ doc.name }}-{{ doc.rev }}
|
|||
{% endblock %}
|
||||
|
||||
{% block pagehead %}
|
||||
<link rel="stylesheet" href="/facelift/css/tokenfield-typeahead.min.css">
|
||||
<link rel="stylesheet" href="/facelift/css/bootstrap-tokenfield.min.css">
|
||||
<link rel="stylesheet" href="/facelift/css/lib/select2.css">
|
||||
<link rel="stylesheet" href="/facelift/css/lib/select2-bootstrap.css">
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
@ -19,7 +19,7 @@ Change document shepherd for {{ doc.name }}-{{ doc.rev }}
|
|||
|
||||
{% bootstrap_messages %}
|
||||
|
||||
<form class="tokenized-form" role="form" enctype="multipart/form-data" method="post">
|
||||
<form role="form" enctype="multipart/form-data" method="post">
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form form %}
|
||||
|
||||
|
@ -31,6 +31,5 @@ Change document shepherd for {{ doc.name }}-{{ doc.rev }}
|
|||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
<script src="/facelift/js/lib/typeahead.bundle.min.js"></script>
|
||||
<script src="/facelift/js/lib/bootstrap-tokenfield.min.js"></script>
|
||||
<script src="/facelift/js/lib/select2-3.5.2.min.js"></script>
|
||||
{% endblock %}
|
||||
|
|
|
@ -7,23 +7,17 @@
|
|||
{% bootstrap_messages %}
|
||||
|
||||
{% block pagehead %}
|
||||
<link rel="stylesheet" href="/facelift/css/tokenfield-typeahead.min.css">
|
||||
<link rel="stylesheet" href="/facelift/css/bootstrap-tokenfield.min.css">
|
||||
<link rel="stylesheet" href="/facelift/css/lib/select2.css">
|
||||
<link rel="stylesheet" href="/facelift/css/lib/select2-bootstrap.css">
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Change documents replaced by<br><small>{{ doc }}</small></h1>
|
||||
|
||||
<form class="tokenized-form" role="form" method="post">
|
||||
<form role="form" method="post">
|
||||
{% csrf_token %}
|
||||
|
||||
<div class="form-group">
|
||||
<label>{{ form.replaces.label }}</label>
|
||||
<input type="text" class="form-control tokenized-field" data-ajax-url="{% url "doc_ajax_internet_draft" %}?term=" data-display="label" data-io="#id_replaces" data-format="json">
|
||||
<span class="help-block">Enter draft names, separated with commas.</span>
|
||||
</div>
|
||||
|
||||
{% bootstrap_form form %}
|
||||
{% bootstrap_form form %}
|
||||
|
||||
{% buttons %}
|
||||
<button type="submit" class="btn btn-primary">Save</button>
|
||||
|
@ -34,6 +28,5 @@
|
|||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
<script src="/facelift/js/lib/typeahead.bundle.min.js"></script>
|
||||
<script src="/facelift/js/lib/bootstrap-tokenfield.min.js"></script>
|
||||
<script src="/facelift/js/lib/select2-3.5.2.min.js"></script>
|
||||
{% endblock %}
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
|
||||
{% bootstrap_messages %}
|
||||
|
||||
<form class="tokenized-form" role="form" method="post">
|
||||
<form role="form" method="post">
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form form %}
|
||||
|
||||
|
|
|
@ -12,20 +12,18 @@
|
|||
{% endblock %}
|
||||
|
||||
{% block pagehead %}
|
||||
<link rel="stylesheet" href="/facelift/css/tokenfield-typeahead.min.css">
|
||||
<link rel="stylesheet" href="/facelift/css/bootstrap-tokenfield.min.css">
|
||||
<link rel="stylesheet" href="/facelift/css/lib/select2.css">
|
||||
<link rel="stylesheet" href="/facelift/css/lib/select2-bootstrap.css">
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>
|
||||
{% if action == "edit" %}
|
||||
Edit {{ group.type.name }} {{ group.acronym }}
|
||||
{% elif action == "charter" %}
|
||||
Start chartering new group
|
||||
{% else %}
|
||||
{% if action == "charter" %}
|
||||
Start chartering new group
|
||||
{% else %}
|
||||
Create new group or BOF
|
||||
{% endif %}
|
||||
Create new group or BOF
|
||||
{% endif %}
|
||||
</h1>
|
||||
|
||||
|
@ -35,7 +33,7 @@
|
|||
chairs and delegates, need a datatracker account to actually do
|
||||
so. New accounts can be <a href="{% url "create_account" %}">created here</a>.</p>
|
||||
|
||||
<form class="tokenized-form form-horizontal" role="form" method="post">
|
||||
<form class="form-horizontal" role="form" method="post">
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form form layout='horizontal' %}
|
||||
|
||||
|
@ -43,18 +41,15 @@ so. New accounts can be <a href="{% url "create_account" %}">created here</a>.</
|
|||
{% if action == "edit" %}
|
||||
<button class="btn btn-primary" type="submit">Submit</button>
|
||||
<a class="btn btn-default pull-right" href="{{ group.about_url }}">Back</a>
|
||||
{% else %}
|
||||
{% if action == "charter" %}
|
||||
<button class="btn btn-primary" type="submit">Start chartering</button>
|
||||
{% else %}
|
||||
<button class="btn btn-primary" type="submit">Create group or BOF</button>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endbuttons %}
|
||||
{% elif action == "charter" %}
|
||||
<button class="btn btn-primary" type="submit">Start chartering</button>
|
||||
{% else %}
|
||||
<button class="btn btn-primary" type="submit">Create group or BOF</button>
|
||||
{% endif %}
|
||||
{% endbuttons %}
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
<script src="/facelift/js/lib/typeahead.bundle.min.js"></script>
|
||||
<script src="/facelift/js/lib/bootstrap-tokenfield.min.js"></script>
|
||||
<script src="/facelift/js/lib/select2-3.5.2.min.js"></script>
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,111 +1,99 @@
|
|||
{% extends "base.html" %}
|
||||
{% extends "ietf.html" %}
|
||||
|
||||
{% load bootstrap3 %}
|
||||
|
||||
{% block pagehead %}
|
||||
<link rel="stylesheet" href="/facelift/css/lib/select2.css">
|
||||
<link rel="stylesheet" href="/facelift/css/lib/select2-bootstrap.css">
|
||||
<link rel="stylesheet" href="/facelift/css/datepicker3.css">
|
||||
{% endblock %}
|
||||
|
||||
{% block title %}{{ title }}{% endblock %}
|
||||
|
||||
{% block morecss %}
|
||||
tr.milestone td { padding: 0.2em 0; cursor: pointer; vertical-align: top; }
|
||||
tr.milestone:hover { background-color: #e8f0fa; }
|
||||
td.due { width: 5em; }
|
||||
.milestone.changed { font-weight: bold; }
|
||||
.milestone .note { font-style: italic; display: inline-block; margin-left: 0.5em; color: #2647a0; }
|
||||
.milestone .doc { display: block; padding-left: 1em; }
|
||||
.edit-milestone { display: none; }
|
||||
.milestone.delete, .edit-milestone.delete, .edit-milestone.delete input { color: #aaa !important; }
|
||||
.edit-milestone table { margin: 1em 0; }
|
||||
.edit-milestone table td { padding: 0.1em; }
|
||||
.edit-milestone .desc input { width: 50em; }
|
||||
.edit-milestone .due input { width: 6em; }
|
||||
.edit-milestone input[type=checkbox] { vertical-align: middle; margin: 0 0.2em 0 0.8em;}
|
||||
.edit-milestone .resolved label { vertical-align: middle; }
|
||||
.edit-milestone .delete label { vertical-align: middle; }
|
||||
.edit-milestone .accept ul { display: inline-block; margin: 0; padding: 0; }
|
||||
.edit-milestone .accept ul li { list-style: none; display: inline-block; margin: 0; padding: 0; padding-left: 0.4em; }
|
||||
.edit-milestone .accept ul li label { vertical-align: middle; }
|
||||
.edit-milestone .accept ul li input { margin: 0; padding: 0; vertical-align: middle; }
|
||||
.edit-milestone .docs td { vertical-align: top; }
|
||||
|
||||
ul.errorlist { border-width: 0px; padding: 0px; margin: 0px; display: inline-block; }
|
||||
ul.errorlist li { color: #a00; margin: 0px; padding: 0px; list-style: none; }
|
||||
p.help { font-style: italic; }
|
||||
p.error { color: #a00; font-size: larger; }
|
||||
tr.milestone.add { font-style: italic; }
|
||||
{% endblock %}
|
||||
|
||||
{% block pagehead %}
|
||||
<link rel="stylesheet" type="text/css" href="/css/token-input.css"></link>
|
||||
{% endblock %}
|
||||
{% bootstrap_messages %}
|
||||
|
||||
{% block content %}
|
||||
{% load ietf_filters %}
|
||||
<h1>{{ title }}</h1>
|
||||
|
||||
<noscript>This page depends on Javascript being enabled to work properly.</noscript>
|
||||
|
||||
<p>Links:
|
||||
<a href="{{ g.about_url }}">{{ group.acronym }} {{ group.type.name }}</a>
|
||||
<a href="{{ group.about_url }}">{{ group.acronym }} {{ group.type.name }}</a>
|
||||
{% if group.charter %}
|
||||
- <a href="{% url "doc_view" name=group.charter.canonical_name %}">{{ group.charter.canonical_name }}</a>
|
||||
{% endif %}
|
||||
</p>
|
||||
|
||||
|
||||
<p class="help">{% if forms %}Click a milestone to edit it.{% endif %}
|
||||
<p class="help-block">
|
||||
{% if forms %}Click a milestone to edit it.{% endif %}
|
||||
|
||||
{% if needs_review %}
|
||||
Note that as {{ group.type.name }} Chair you cannot edit descriptions of existing
|
||||
milestones and milestones you add are subject to review by the {{ reviewer }}.
|
||||
{% endif %}
|
||||
{% if needs_review %}
|
||||
Note that as {{ group.type.name }} Chair you cannot edit descriptions of existing
|
||||
milestones and milestones you add are subject to review by the {{ reviewer }}.
|
||||
{% endif %}
|
||||
</p>
|
||||
|
||||
{% if can_reset %}
|
||||
<p>
|
||||
You can <a href="{% url "group_reset_charter_milestones" group_type=group.type_id acronym=group.acronym %}">reset
|
||||
this list</a> to the milestones currently in use for the {{ group.acronym }} {{ group.type.name }}.
|
||||
You can <a href="{% url "group_reset_charter_milestones" group_type=group.type_id acronym=group.acronym %}">reset
|
||||
this list</a> to the milestones currently in use for the {{ group.acronym }} {{ group.type.name }}.
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
{% if form_errors %}
|
||||
<p class="error">There were errors, see below.</p>
|
||||
<p class="alert alert-danger">There were errors, see below.</p>
|
||||
{% endif %}
|
||||
|
||||
<form action="" method="post" id="milestones-form">{% csrf_token %}
|
||||
<table cellspacing="0" cellpadding="0">
|
||||
{% for form in forms %}
|
||||
<tr class="milestone{% if form.delete.data %} delete{% endif %}">
|
||||
<td class="due">{% if form.milestone.resolved %}{{ form.milestone.resolved }}{% else %}{{ form.milestone.due|date:"M Y" }}{% endif %}</td>
|
||||
<td>
|
||||
<div>{{ form.milestone.desc }}
|
||||
{% if form.needs_review %}<span class="note">awaiting accept</span>{% endif %}
|
||||
{% if form.changed %}<span class="note">changed</span>{% endif %}
|
||||
</div>
|
||||
<form method="post" id="milestones-form">{% csrf_token %}
|
||||
<table class="table">
|
||||
|
||||
{% for d in form.docs_names %}
|
||||
<div class="doc">{{ d }}</div>
|
||||
{% for form in forms %}
|
||||
<tr class="milestone{% if form.delete.data %} delete{% endif %}">
|
||||
<td class="due">
|
||||
{% if form.milestone.resolved %}
|
||||
<span class="label label-success">{{ form.milestone.resolved }}</span>
|
||||
{% else %}
|
||||
{{ form.milestone.due|date:"M Y" }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<div>{{ form.milestone.desc }}
|
||||
{% if form.needs_review %}<span title="This milestone is not active yet, awaiting {{ reviewer }} acceptance" class="label label-warning">Awaiting accept</span>{% endif %}
|
||||
{% if form.changed %}<span class="label label-info">Changed</span>{% endif %}
|
||||
</div>
|
||||
|
||||
{% for d in form.docs_names %}
|
||||
<div class="doc">{{ d }}</div>
|
||||
{% endfor %}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr class="edit-milestone{% if form.changed %} changed{% endif %}">
|
||||
<td colspan="2">{% include "group/milestone_form.html" %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr class="edit-milestone{% if form.changed %} changed{% endif %}"><td colspan="2">{% include "group/milestone_form.html" %}</td></tr>
|
||||
{% endfor %}
|
||||
<tr class="milestone add"><td></td><td>Add {% if milestone_set == "chartering" %}charter{% endif%} milestone {% if needs_review %}for {{ reviewer }} review{% endif %}</td></tr>
|
||||
<tr class="edit-milestone template"><td colspan="2">{% include "group/milestone_form.html" with form=empty_form %}</td></tr>
|
||||
</table>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td><button type="button" class="btn btn-default add-milestone">Add extra {% if milestone_set == "chartering" %}charter{% endif%} milestone {% if needs_review %}for {{ reviewer }} review{% endif %}</button></td>
|
||||
</tr>
|
||||
|
||||
<div class="actions">
|
||||
<a class="button" href="{% if milestone_set == "charter" %}{% url "doc_view" name=group.charter.canonical_name %}{% else %}{{ group.about_url }}{% endif %}">Cancel</a>
|
||||
<input class="button" type="submit" data-labelsave="Save" data-labelreview="Review changes" value="Save" style="display:none"/>
|
||||
<tr class="edit-milestone template"><td colspan="2">{% include "group/milestone_form.html" with form=empty_form %}</td></tr>
|
||||
</table>
|
||||
|
||||
{% buttons %}
|
||||
<a class="btn btn-default pull-right" href="{% if milestone_set == "charter" %}{% url "doc_view" name=group.charter.canonical_name %}{% else %}{{ group.about_url }}{% endif %}">Cancel</a>
|
||||
|
||||
<button style="display:none" class="btn btn-primary" type="submit" data-labelsave="Save" data-labelreview="Review changes">Save</button>
|
||||
<input type="hidden" name="action" value="save">
|
||||
</div>
|
||||
{% endbuttons %}
|
||||
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
{% block content_end %}
|
||||
<script type="text/javascript" src="/js/lib/jquery.tokeninput.js"></script>
|
||||
<script type="text/javascript" src="/js/lib/json2.js"></script>
|
||||
<script type="text/javascript" src="/js/tokenized-field.js"></script>
|
||||
<script>
|
||||
var finishedMilestoneText = "{{ finished_milestone_text|escapejs }}";
|
||||
</script>
|
||||
<script type="text/javascript" src="/js/edit-milestones.js"></script>
|
||||
{% block js %}
|
||||
<script src="/facelift/js/lib/select2-3.5.2.min.js"></script>
|
||||
<script src="/facelift/js/lib/bootstrap-datepicker.js"></script>
|
||||
<script type="text/javascript" src="/facelift/js/edit-milestones.js"></script>
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,42 +1,9 @@
|
|||
{# assumes group, form, needs_review, reviewer are in the context #}
|
||||
<input type="hidden" name="prefix" value="{{ form.prefix|default:"" }}"/>
|
||||
{{ form.id }}
|
||||
|
||||
<table cellspacing="0" cellpadding="0">
|
||||
<tr>
|
||||
<td>{{ form.desc.label_tag }}</td>
|
||||
<td>
|
||||
<span class="desc">
|
||||
{% if needs_review and form.milestone and form.milestone.state_id != "review" %}
|
||||
{{ form.milestone.desc }} {{ form.desc.as_hidden }}
|
||||
{% else %}
|
||||
{{ form.desc }}
|
||||
{% endif %}
|
||||
</span>
|
||||
<span class="delete">{{ form.delete }} {{ form.delete.label_tag }}</span>
|
||||
</td>
|
||||
</tr>
|
||||
{% if form.desc.errors %}<tr><td></td><td colspan="2">{{ form.desc.errors }}</td></tr>{% endif %}
|
||||
<tr>
|
||||
<td>Due date:</td>
|
||||
<td><span class="due">{{ form.due_month }} {{ form.due_year }}</span> {{ form.due_month.errors }} {{ form.due_year.errors }}
|
||||
<span class="resolved">{{ form.resolved_checkbox }} {{ form.resolved_checkbox.label_tag }} {{ form.resolved }}</span>
|
||||
{{ form.resolved.errors }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="docs">
|
||||
<td>Drafts:</td>
|
||||
<td>{{ form.docs }}
|
||||
{{ form.docs.errors }}
|
||||
</td>
|
||||
</tr>
|
||||
{% if form.needs_review %}
|
||||
<tr class="needs-review">
|
||||
<td>Review:</td>
|
||||
<td class="accept">
|
||||
This milestone is not active yet, awaiting
|
||||
{{ reviewer }} acceptance{% if needs_review %}.{% else %}: {{ form.accept }}{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</table>
|
||||
{% load bootstrap3 %}
|
||||
|
||||
<div role="form" class="form-horizontal">
|
||||
<input type="hidden" name="prefix" value="{{ form.prefix|default:"" }}"/>
|
||||
|
||||
{% bootstrap_form form layout='horizontal' %}
|
||||
</div>
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
{% block title %}Manage {{ group.name }} RFC stream{% endblock %}
|
||||
|
||||
{% block pagehead %}
|
||||
<link rel="stylesheet" href="/facelift/css/tokenfield-typeahead.min.css">
|
||||
<link rel="stylesheet" href="/facelift/css/bootstrap-tokenfield.min.css">
|
||||
<link rel="stylesheet" href="/facelift/css/lib/select2.css">
|
||||
<link rel="stylesheet" href="/facelift/css/lib/select2-bootstrap.css">
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
@ -29,12 +29,12 @@
|
|||
datatracker account. New accounts can be <a href="{% url "create_account" %}">created here</a>.
|
||||
</p>
|
||||
|
||||
<form class="tokenized-form" action="" role="form" method="post">
|
||||
<form action="" role="form" method="post">
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form form %}
|
||||
|
||||
{% buttons %}
|
||||
<input type="submit" class="btn btn-primary" value="Save">
|
||||
<button type="submit" class="btn btn-primary">Save</button>
|
||||
<a class="btn btn-default pull-right" href="{% url "ietf.group.views_stream.streams" %}{{ group.acronym }}">Back</a>
|
||||
{% endbuttons %}
|
||||
|
||||
|
@ -42,6 +42,5 @@
|
|||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
<script src="/facelift/js/lib/typeahead.bundle.min.js"></script>
|
||||
<script src="/facelift/js/lib/bootstrap-tokenfield.min.js"></script>
|
||||
<script src="/facelift/js/lib/select2-3.5.2.min.js"></script>
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
{% extends "nomcom/nomcom_private_base.html" %}
|
||||
|
||||
{% block pagehead %}
|
||||
<link rel="stylesheet" href="/facelift/css/tokenfield-typeahead.min.css">
|
||||
<link rel="stylesheet" href="/facelift/css/bootstrap-tokenfield.min.css">
|
||||
<link rel="stylesheet" href="/facelift/css/lib/select2.css">
|
||||
<link rel="stylesheet" href="/facelift/css/lib/select2-bootstrap.css">
|
||||
{% endblock %}
|
||||
|
||||
{% load bootstrap3 %}
|
||||
|
@ -27,6 +27,5 @@
|
|||
{% endblock %}
|
||||
|
||||
{% block content_end %}
|
||||
<script src="/facelift/js/lib/typeahead.bundle.min.js"></script>
|
||||
<script src="/facelift/js/lib/bootstrap-tokenfield.min.js"></script>
|
||||
<script src="/facelift/js/lib/select2-3.5.2.min.js"></script>
|
||||
{% endblock %}
|
||||
|
|
|
@ -206,3 +206,17 @@ pre { line-height: 1.214; }
|
|||
.navbar-dev .navbar-link:hover {
|
||||
color: #ecdbff;
|
||||
}
|
||||
|
||||
/* milestone editing */
|
||||
#milestones-form .milestone {
|
||||
cursor: pointer;
|
||||
}
|
||||
#milestones-form .milestone:hover {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
#milestones-form .edit-milestone {
|
||||
display: none;
|
||||
}
|
||||
#milestones-form .milestone.delete, #milestones-form .edit-milestone.delete, #milestones-form .edit-milestone.delete input {
|
||||
color: #aaa !important;
|
||||
}
|
||||
|
|
497
static/facelift/css/lib/select2-bootstrap.css
Normal file
497
static/facelift/css/lib/select2-bootstrap.css
Normal file
|
@ -0,0 +1,497 @@
|
|||
/**
|
||||
* Select2 Bootstrap 3 CSS v1.4.1
|
||||
* Tested with Bootstrap v3.2.0 and Select2 v3.3.2, v3.4.1-v3.4.5, v3.5.1, master
|
||||
* in latest Chrome, Safari, Firefox, Opera (Mac) and IE8-IE11
|
||||
* MIT License
|
||||
*/
|
||||
/**
|
||||
* Reset Bootstrap 3 .form-control styles which - if applied to the
|
||||
* original <select>-element the Select2-plugin may be run against -
|
||||
* are copied to the .select2-container.
|
||||
*
|
||||
* 1. Overwrite .select2-container's original display:inline-block
|
||||
* with Bootstrap 3's default for .form-control, display:block;
|
||||
* courtesy of @juristr (@see https://github.com/fk/select2-bootstrap-css/pull/1)
|
||||
*/
|
||||
.select2-container.form-control {
|
||||
background: transparent;
|
||||
border: none;
|
||||
display: block;
|
||||
/* 1 */
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust Select2 inputs to fit Bootstrap 3 default .form-control appearance.
|
||||
*/
|
||||
.select2-container .select2-choices .select2-search-field input,
|
||||
.select2-container .select2-choice,
|
||||
.select2-container .select2-choices {
|
||||
background: none;
|
||||
padding: 0;
|
||||
border-color: #cccccc;
|
||||
border-radius: 4px;
|
||||
color: #555555;
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
background-color: white;
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
|
||||
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
|
||||
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
|
||||
}
|
||||
|
||||
.select2-search input {
|
||||
border-color: #cccccc;
|
||||
border-radius: 4px;
|
||||
color: #555555;
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
background-color: white;
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
|
||||
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
|
||||
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
|
||||
}
|
||||
|
||||
.select2-container .select2-choices .select2-search-field input {
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust Select2 input heights to match the Bootstrap default.
|
||||
*/
|
||||
.select2-container .select2-choice {
|
||||
height: 34px;
|
||||
line-height: 1.42857;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address Multi Select2's height which - depending on how many elements have been selected -
|
||||
* may grown higher than their initial size.
|
||||
*/
|
||||
.select2-container.select2-container-multi.form-control {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address Bootstrap 3 control sizing classes
|
||||
* @see http://getbootstrap.com/css/#forms-control-sizes
|
||||
*/
|
||||
.select2-container.input-sm .select2-choice,
|
||||
.input-group-sm .select2-container .select2-choice {
|
||||
height: 30px;
|
||||
line-height: 1.5;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.select2-container.input-lg .select2-choice,
|
||||
.input-group-lg .select2-container .select2-choice {
|
||||
height: 46px;
|
||||
line-height: 1.33;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.select2-container-multi .select2-choices .select2-search-field input {
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.select2-container-multi.input-sm .select2-choices .select2-search-field input,
|
||||
.input-group-sm .select2-container-multi .select2-choices .select2-search-field input {
|
||||
height: 28px;
|
||||
}
|
||||
|
||||
.select2-container-multi.input-lg .select2-choices .select2-search-field input,
|
||||
.input-group-lg .select2-container-multi .select2-choices .select2-search-field input {
|
||||
height: 44px;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust height and line-height for .select2-search-field amd multi-select Select2 widgets.
|
||||
*
|
||||
* 1. Class repetition to address missing .select2-chosen in Select2 < 3.3.2.
|
||||
*/
|
||||
.select2-container-multi .select2-choices .select2-search-field input {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.select2-chosen,
|
||||
.select2-choice > span:first-child,
|
||||
.select2-container .select2-choices .select2-search-field input {
|
||||
padding: 6px 12px;
|
||||
}
|
||||
|
||||
.input-sm .select2-chosen,
|
||||
.input-group-sm .select2-chosen,
|
||||
.input-sm .select2-choice > span:first-child,
|
||||
.input-group-sm .select2-choice > span:first-child,
|
||||
.input-sm .select2-choices .select2-search-field input,
|
||||
.input-group-sm .select2-choices .select2-search-field input {
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
.input-lg .select2-chosen,
|
||||
.input-group-lg .select2-chosen,
|
||||
.input-lg .select2-choice > span:first-child,
|
||||
.input-group-lg .select2-choice > span:first-child,
|
||||
.input-lg .select2-choices .select2-search-field input,
|
||||
.input-group-lg .select2-choices .select2-search-field input {
|
||||
padding: 10px 16px;
|
||||
}
|
||||
|
||||
.select2-container-multi .select2-choices .select2-search-choice {
|
||||
margin-top: 5px;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
.select2-container-multi.input-sm .select2-choices .select2-search-choice,
|
||||
.input-group-sm .select2-container-multi .select2-choices .select2-search-choice {
|
||||
margin-top: 3px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.select2-container-multi.input-lg .select2-choices .select2-search-choice,
|
||||
.input-group-lg .select2-container-multi .select2-choices .select2-search-choice {
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust the single Select2's dropdown arrow button appearance.
|
||||
*
|
||||
* 1. For Select2 v.3.3.2.
|
||||
*/
|
||||
.select2-container .select2-choice .select2-arrow,
|
||||
.select2-container .select2-choice div {
|
||||
border-left: 1px solid #cccccc;
|
||||
background: none;
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
|
||||
}
|
||||
|
||||
.select2-dropdown-open .select2-choice .select2-arrow,
|
||||
.select2-dropdown-open .select2-choice div {
|
||||
border-left-color: transparent;
|
||||
background: none;
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust the dropdown arrow button icon position for the single-select Select2 elements
|
||||
* to make it line up vertically now that we increased the height of .select2-container.
|
||||
*
|
||||
* 1. Class repetition to address missing .select2-chosen in Select2 v.3.3.2.
|
||||
*/
|
||||
.select2-container .select2-choice .select2-arrow b,
|
||||
.select2-container .select2-choice div b {
|
||||
background-position: 0 3px;
|
||||
}
|
||||
|
||||
.select2-dropdown-open .select2-choice .select2-arrow b,
|
||||
.select2-dropdown-open .select2-choice div b {
|
||||
background-position: -18px 3px;
|
||||
}
|
||||
|
||||
.select2-container.input-sm .select2-choice .select2-arrow b,
|
||||
.input-group-sm .select2-container .select2-choice .select2-arrow b,
|
||||
.select2-container.input-sm .select2-choice div b,
|
||||
.input-group-sm .select2-container .select2-choice div b {
|
||||
background-position: 0 1px;
|
||||
}
|
||||
|
||||
.select2-dropdown-open.input-sm .select2-choice .select2-arrow b,
|
||||
.input-group-sm .select2-dropdown-open .select2-choice .select2-arrow b,
|
||||
.select2-dropdown-open.input-sm .select2-choice div b,
|
||||
.input-group-sm .select2-dropdown-open .select2-choice div b {
|
||||
background-position: -18px 1px;
|
||||
}
|
||||
|
||||
.select2-container.input-lg .select2-choice .select2-arrow b,
|
||||
.input-group-lg .select2-container .select2-choice .select2-arrow b,
|
||||
.select2-container.input-lg .select2-choice div b,
|
||||
.input-group-lg .select2-container .select2-choice div b {
|
||||
background-position: 0 9px;
|
||||
}
|
||||
|
||||
.select2-dropdown-open.input-lg .select2-choice .select2-arrow b,
|
||||
.input-group-lg .select2-dropdown-open .select2-choice .select2-arrow b,
|
||||
.select2-dropdown-open.input-lg .select2-choice div b,
|
||||
.input-group-lg .select2-dropdown-open .select2-choice div b {
|
||||
background-position: -18px 9px;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address Bootstrap's validation states and change Select2's border colors and focus states.
|
||||
* Apply .has-warning, .has-danger or .has-succes to #select2-drop to match Bootstraps' colors.
|
||||
*/
|
||||
.has-warning .select2-choice,
|
||||
.has-warning .select2-choices {
|
||||
border-color: #8a6d3b;
|
||||
}
|
||||
.has-warning .select2-container-active .select2-choice,
|
||||
.has-warning .select2-container-multi.select2-container-active .select2-choices {
|
||||
border-color: #66512c;
|
||||
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #c0a16b;
|
||||
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #c0a16b;
|
||||
}
|
||||
.has-warning.select2-drop-active {
|
||||
border-color: #66512c;
|
||||
}
|
||||
.has-warning.select2-drop-active.select2-drop.select2-drop-above {
|
||||
border-top-color: #66512c;
|
||||
}
|
||||
|
||||
.has-error .select2-choice,
|
||||
.has-error .select2-choices {
|
||||
border-color: #a94442;
|
||||
}
|
||||
.has-error .select2-container-active .select2-choice,
|
||||
.has-error .select2-container-multi.select2-container-active .select2-choices {
|
||||
border-color: #843534;
|
||||
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ce8483;
|
||||
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ce8483;
|
||||
}
|
||||
.has-error.select2-drop-active {
|
||||
border-color: #843534;
|
||||
}
|
||||
.has-error.select2-drop-active.select2-drop.select2-drop-above {
|
||||
border-top-color: #843534;
|
||||
}
|
||||
|
||||
.has-success .select2-choice,
|
||||
.has-success .select2-choices {
|
||||
border-color: #3c763d;
|
||||
}
|
||||
.has-success .select2-container-active .select2-choice,
|
||||
.has-success .select2-container-multi.select2-container-active .select2-choices {
|
||||
border-color: #2b542c;
|
||||
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #67b168;
|
||||
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #67b168;
|
||||
}
|
||||
.has-success.select2-drop-active {
|
||||
border-color: #2b542c;
|
||||
}
|
||||
.has-success.select2-drop-active.select2-drop.select2-drop-above {
|
||||
border-top-color: #2b542c;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make Select2's active-styles - applied to .select2-container when the widget receives focus -
|
||||
* fit Bootstrap 3's .form-element:focus appearance.
|
||||
*/
|
||||
.select2-container-active .select2-choice,
|
||||
.select2-container-multi.select2-container-active .select2-choices {
|
||||
border-color: #66afe9;
|
||||
outline: none;
|
||||
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
|
||||
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
|
||||
-webkit-transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
|
||||
-o-transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
|
||||
transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
|
||||
}
|
||||
|
||||
.select2-drop-active {
|
||||
border-color: #66afe9;
|
||||
}
|
||||
|
||||
.select2-drop-auto-width,
|
||||
.select2-drop.select2-drop-above.select2-drop-active {
|
||||
border-top-color: #66afe9;
|
||||
}
|
||||
|
||||
/**
|
||||
* Select2 widgets in Bootstrap Input Groups
|
||||
*
|
||||
* When Select2 widgets are combined with other elements using Bootstrap 3's
|
||||
* "Input Group" component, we don't want specific edges of the Select2 container
|
||||
* to have a border-radius.
|
||||
*
|
||||
* In Bootstrap 2, input groups required a markup where these style adjustments
|
||||
* could be bound to a CSS-class identifying if the additional elements are appended,
|
||||
* prepended or both.
|
||||
*
|
||||
* Bootstrap 3 doesn't rely on these classes anymore, so we have to use our own.
|
||||
* Use .select2-bootstrap-prepend and .select2-bootstrap-append on a Bootstrap 3 .input-group
|
||||
* to let the contained Select2 widget know which edges should not be rounded as they are
|
||||
* directly followed by another element.
|
||||
*
|
||||
* @see http://getbootstrap.com/components/#input-groups
|
||||
*/
|
||||
.input-group.select2-bootstrap-prepend [class^="select2-choice"] {
|
||||
border-bottom-left-radius: 0 !important;
|
||||
border-top-left-radius: 0 !important;
|
||||
}
|
||||
|
||||
.input-group.select2-bootstrap-append [class^="select2-choice"] {
|
||||
border-bottom-right-radius: 0 !important;
|
||||
border-top-right-radius: 0 !important;
|
||||
}
|
||||
|
||||
.select2-dropdown-open [class^="select2-choice"] {
|
||||
border-bottom-right-radius: 0 !important;
|
||||
border-bottom-left-radius: 0 !important;
|
||||
}
|
||||
|
||||
.select2-dropdown-open.select2-drop-above [class^="select2-choice"] {
|
||||
border-top-right-radius: 0 !important;
|
||||
border-top-left-radius: 0 !important;
|
||||
border-bottom-right-radius: 4px !important;
|
||||
border-bottom-left-radius: 4px !important;
|
||||
}
|
||||
.input-group.select2-bootstrap-prepend .select2-dropdown-open.select2-drop-above [class^="select2-choice"] {
|
||||
border-bottom-left-radius: 0 !important;
|
||||
border-top-left-radius: 0 !important;
|
||||
}
|
||||
.input-group.select2-bootstrap-append .select2-dropdown-open.select2-drop-above [class^="select2-choice"] {
|
||||
border-bottom-right-radius: 0 !important;
|
||||
border-top-right-radius: 0 !important;
|
||||
}
|
||||
.input-group.input-group-sm.select2-bootstrap-prepend .select2-dropdown-open.select2-drop-above [class^="select2-choice"] {
|
||||
border-bottom-right-radius: 3px !important;
|
||||
}
|
||||
.input-group.input-group-lg.select2-bootstrap-prepend .select2-dropdown-open.select2-drop-above [class^="select2-choice"] {
|
||||
border-bottom-right-radius: 6px !important;
|
||||
}
|
||||
.input-group.input-group-sm.select2-bootstrap-append .select2-dropdown-open.select2-drop-above [class^="select2-choice"] {
|
||||
border-bottom-left-radius: 3px !important;
|
||||
}
|
||||
.input-group.input-group-lg.select2-bootstrap-append .select2-dropdown-open.select2-drop-above [class^="select2-choice"] {
|
||||
border-bottom-left-radius: 6px !important;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust Select2's choices hover and selected styles to match Bootstrap 3's default dropdown styles.
|
||||
*/
|
||||
.select2-results .select2-highlighted {
|
||||
color: white;
|
||||
background-color: #428bca;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust alignment of Bootstrap 3 buttons in Bootstrap 3 Input Groups to address
|
||||
* Multi Select2's height which - depending on how many elements have been selected -
|
||||
* may grown higher than their initial size.
|
||||
*/
|
||||
.select2-bootstrap-append .select2-container-multiple,
|
||||
.select2-bootstrap-append .input-group-btn,
|
||||
.select2-bootstrap-append .input-group-btn .btn,
|
||||
.select2-bootstrap-prepend .select2-container-multiple,
|
||||
.select2-bootstrap-prepend .input-group-btn,
|
||||
.select2-bootstrap-prepend .input-group-btn .btn {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make Multi Select2's choices match Bootstrap 3's default button styles.
|
||||
*/
|
||||
.select2-container-multi .select2-choices .select2-search-choice {
|
||||
color: #555555;
|
||||
background: white;
|
||||
border-color: #cccccc;
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.select2-container-multi .select2-choices .select2-search-choice-focus {
|
||||
background: #ebebeb;
|
||||
border-color: #adadad;
|
||||
color: #333333;
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address Multi Select2's choice close-button vertical alignment.
|
||||
*/
|
||||
.select2-search-choice-close {
|
||||
margin-top: -7px;
|
||||
top: 50%;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust the single Select2's clear button position (used to reset the select box
|
||||
* back to the placeholder value and visible once a selection is made
|
||||
* activated by Select2's "allowClear" option).
|
||||
*/
|
||||
.select2-container .select2-choice abbr {
|
||||
top: 50%;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust "no results" and "selection limit" messages to make use
|
||||
* of Bootstrap 3's default "Alert" style.
|
||||
*
|
||||
* @see http://getbootstrap.com/components/#alerts-default
|
||||
*/
|
||||
.select2-results .select2-no-results,
|
||||
.select2-results .select2-searching,
|
||||
.select2-results .select2-selection-limit {
|
||||
background-color: #fcf8e3;
|
||||
color: #8a6d3b;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address disabled Select2 styles.
|
||||
*
|
||||
* 1. For Select2 v.3.3.2.
|
||||
* 2. Revert border-left:0 inherited from Select2's CSS to prevent the arrow
|
||||
* from jumping when switching from disabled to enabled state and vice versa.
|
||||
*/
|
||||
.select2-container.select2-container-disabled .select2-choice,
|
||||
.select2-container.select2-container-disabled .select2-choices {
|
||||
cursor: not-allowed;
|
||||
background-color: #eeeeee;
|
||||
border-color: #cccccc;
|
||||
}
|
||||
.select2-container.select2-container-disabled .select2-choice .select2-arrow,
|
||||
.select2-container.select2-container-disabled .select2-choice div,
|
||||
.select2-container.select2-container-disabled .select2-choices .select2-arrow,
|
||||
.select2-container.select2-container-disabled .select2-choices div {
|
||||
background-color: transparent;
|
||||
border-left: 1px solid transparent;
|
||||
/* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Address Select2's loading indicator position - which should not stick
|
||||
* to the right edge of Select2's search input.
|
||||
*
|
||||
* 1. in .select2-search input
|
||||
* 2. in Multi Select2's .select2-search-field input
|
||||
* 3. in the status-message of infinite-scroll with remote data (@see http://ivaynberg.github.io/select2/#infinite)
|
||||
*
|
||||
* These styles alter Select2's default background-position of 100%
|
||||
* and supply the new background-position syntax to browsers which support it:
|
||||
*
|
||||
* 1. Android, Safari < 6/Mobile, IE<9: change to a relative background-position of 99%
|
||||
* 2. Chrome 25+, Firefox 13+, IE 9+, Opera 10.5+: use the new CSS3-background-position syntax
|
||||
*
|
||||
* @see http://www.w3.org/TR/css3-background/#background-position
|
||||
*
|
||||
* @todo Since both Select2 and Bootstrap 3 only support IE8 and above,
|
||||
* we could use the :after-pseudo-element to display the loading indicator.
|
||||
* Alternatively, we could supply an altered loading indicator image which already
|
||||
* contains an offset to the right.
|
||||
*/
|
||||
.select2-search input.select2-active,
|
||||
.select2-container-multi .select2-choices .select2-search-field input.select2-active,
|
||||
.select2-more-results.select2-active {
|
||||
background-position: 99%;
|
||||
/* 4 */
|
||||
background-position: right 4px center;
|
||||
/* 5 */
|
||||
}
|
||||
|
||||
/**
|
||||
* To support Select2 pre v3.4.2 in combination with Bootstrap v3.2.0,
|
||||
* ensure that .select2-offscreen width, height and position can not be overwritten.
|
||||
*
|
||||
* This adresses changes in Bootstrap somewhere after the initial v3.0.0 which -
|
||||
* in combination with Select2's pre-v3.4.2 CSS missing the "!important" after
|
||||
* the following rules - allow Bootstrap to overwrite the latter, which results in
|
||||
* the original <select> element Select2 is replacing not be properly being hidden
|
||||
* when used in a "Bootstrap Input Group with Addon".
|
||||
**/
|
||||
.select2-offscreen,
|
||||
.select2-offscreen:focus {
|
||||
width: 1px !important;
|
||||
height: 1px !important;
|
||||
position: absolute !important;
|
||||
}
|
BIN
static/facelift/css/lib/select2-spinner.gif
Normal file
BIN
static/facelift/css/lib/select2-spinner.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.8 KiB |
704
static/facelift/css/lib/select2.css
Normal file
704
static/facelift/css/lib/select2.css
Normal file
|
@ -0,0 +1,704 @@
|
|||
/*
|
||||
Version: 3.5.2 Timestamp: Sat Nov 1 14:43:36 EDT 2014
|
||||
*/
|
||||
.select2-container {
|
||||
margin: 0;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
/* inline-block for ie7 */
|
||||
zoom: 1;
|
||||
*display: inline;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.select2-container,
|
||||
.select2-drop,
|
||||
.select2-search,
|
||||
.select2-search input {
|
||||
/*
|
||||
Force border-box so that % widths fit the parent
|
||||
container without overlap because of margin/padding.
|
||||
More Info : http://www.quirksmode.org/css/box.html
|
||||
*/
|
||||
-webkit-box-sizing: border-box; /* webkit */
|
||||
-moz-box-sizing: border-box; /* firefox */
|
||||
box-sizing: border-box; /* css3 */
|
||||
}
|
||||
|
||||
.select2-container .select2-choice {
|
||||
display: block;
|
||||
height: 26px;
|
||||
padding: 0 0 0 8px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
|
||||
border: 1px solid #aaa;
|
||||
white-space: nowrap;
|
||||
line-height: 26px;
|
||||
color: #444;
|
||||
text-decoration: none;
|
||||
|
||||
border-radius: 4px;
|
||||
|
||||
background-clip: padding-box;
|
||||
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
|
||||
background-color: #fff;
|
||||
background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #eee), color-stop(0.5, #fff));
|
||||
background-image: -webkit-linear-gradient(center bottom, #eee 0%, #fff 50%);
|
||||
background-image: -moz-linear-gradient(center bottom, #eee 0%, #fff 50%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr = '#ffffff', endColorstr = '#eeeeee', GradientType = 0);
|
||||
background-image: linear-gradient(to top, #eee 0%, #fff 50%);
|
||||
}
|
||||
|
||||
html[dir="rtl"] .select2-container .select2-choice {
|
||||
padding: 0 8px 0 0;
|
||||
}
|
||||
|
||||
.select2-container.select2-drop-above .select2-choice {
|
||||
border-bottom-color: #aaa;
|
||||
|
||||
border-radius: 0 0 4px 4px;
|
||||
|
||||
background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #eee), color-stop(0.9, #fff));
|
||||
background-image: -webkit-linear-gradient(center bottom, #eee 0%, #fff 90%);
|
||||
background-image: -moz-linear-gradient(center bottom, #eee 0%, #fff 90%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#eeeeee', GradientType=0);
|
||||
background-image: linear-gradient(to bottom, #eee 0%, #fff 90%);
|
||||
}
|
||||
|
||||
.select2-container.select2-allowclear .select2-choice .select2-chosen {
|
||||
margin-right: 42px;
|
||||
}
|
||||
|
||||
.select2-container .select2-choice > .select2-chosen {
|
||||
margin-right: 26px;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
|
||||
white-space: nowrap;
|
||||
|
||||
text-overflow: ellipsis;
|
||||
float: none;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
html[dir="rtl"] .select2-container .select2-choice > .select2-chosen {
|
||||
margin-left: 26px;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.select2-container .select2-choice abbr {
|
||||
display: none;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
position: absolute;
|
||||
right: 24px;
|
||||
top: 8px;
|
||||
|
||||
font-size: 1px;
|
||||
text-decoration: none;
|
||||
|
||||
border: 0;
|
||||
background: url('select2.png') right top no-repeat;
|
||||
cursor: pointer;
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
.select2-container.select2-allowclear .select2-choice abbr {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.select2-container .select2-choice abbr:hover {
|
||||
background-position: right -11px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.select2-drop-mask {
|
||||
border: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
min-height: 100%;
|
||||
min-width: 100%;
|
||||
height: auto;
|
||||
width: auto;
|
||||
opacity: 0;
|
||||
z-index: 9998;
|
||||
/* styles required for IE to work */
|
||||
background-color: #fff;
|
||||
filter: alpha(opacity=0);
|
||||
}
|
||||
|
||||
.select2-drop {
|
||||
width: 100%;
|
||||
margin-top: -1px;
|
||||
position: absolute;
|
||||
z-index: 9999;
|
||||
top: 100%;
|
||||
|
||||
background: #fff;
|
||||
color: #000;
|
||||
border: 1px solid #aaa;
|
||||
border-top: 0;
|
||||
|
||||
border-radius: 0 0 4px 4px;
|
||||
|
||||
-webkit-box-shadow: 0 4px 5px rgba(0, 0, 0, .15);
|
||||
box-shadow: 0 4px 5px rgba(0, 0, 0, .15);
|
||||
}
|
||||
|
||||
.select2-drop.select2-drop-above {
|
||||
margin-top: 1px;
|
||||
border-top: 1px solid #aaa;
|
||||
border-bottom: 0;
|
||||
|
||||
border-radius: 4px 4px 0 0;
|
||||
|
||||
-webkit-box-shadow: 0 -4px 5px rgba(0, 0, 0, .15);
|
||||
box-shadow: 0 -4px 5px rgba(0, 0, 0, .15);
|
||||
}
|
||||
|
||||
.select2-drop-active {
|
||||
border: 1px solid #5897fb;
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
.select2-drop.select2-drop-above.select2-drop-active {
|
||||
border-top: 1px solid #5897fb;
|
||||
}
|
||||
|
||||
.select2-drop-auto-width {
|
||||
border-top: 1px solid #aaa;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.select2-drop-auto-width .select2-search {
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
||||
.select2-container .select2-choice .select2-arrow {
|
||||
display: inline-block;
|
||||
width: 18px;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
|
||||
border-left: 1px solid #aaa;
|
||||
border-radius: 0 4px 4px 0;
|
||||
|
||||
background-clip: padding-box;
|
||||
|
||||
background: #ccc;
|
||||
background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #ccc), color-stop(0.6, #eee));
|
||||
background-image: -webkit-linear-gradient(center bottom, #ccc 0%, #eee 60%);
|
||||
background-image: -moz-linear-gradient(center bottom, #ccc 0%, #eee 60%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr = '#eeeeee', endColorstr = '#cccccc', GradientType = 0);
|
||||
background-image: linear-gradient(to top, #ccc 0%, #eee 60%);
|
||||
}
|
||||
|
||||
html[dir="rtl"] .select2-container .select2-choice .select2-arrow {
|
||||
left: 0;
|
||||
right: auto;
|
||||
|
||||
border-left: none;
|
||||
border-right: 1px solid #aaa;
|
||||
border-radius: 4px 0 0 4px;
|
||||
}
|
||||
|
||||
.select2-container .select2-choice .select2-arrow b {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: url('select2.png') no-repeat 0 1px;
|
||||
}
|
||||
|
||||
html[dir="rtl"] .select2-container .select2-choice .select2-arrow b {
|
||||
background-position: 2px 1px;
|
||||
}
|
||||
|
||||
.select2-search {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
min-height: 26px;
|
||||
margin: 0;
|
||||
padding-left: 4px;
|
||||
padding-right: 4px;
|
||||
|
||||
position: relative;
|
||||
z-index: 10000;
|
||||
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.select2-search input {
|
||||
width: 100%;
|
||||
height: auto !important;
|
||||
min-height: 26px;
|
||||
padding: 4px 20px 4px 5px;
|
||||
margin: 0;
|
||||
|
||||
outline: 0;
|
||||
font-family: sans-serif;
|
||||
font-size: 1em;
|
||||
|
||||
border: 1px solid #aaa;
|
||||
border-radius: 0;
|
||||
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
|
||||
background: #fff url('select2.png') no-repeat 100% -22px;
|
||||
background: url('select2.png') no-repeat 100% -22px, -webkit-gradient(linear, left bottom, left top, color-stop(0.85, #fff), color-stop(0.99, #eee));
|
||||
background: url('select2.png') no-repeat 100% -22px, -webkit-linear-gradient(center bottom, #fff 85%, #eee 99%);
|
||||
background: url('select2.png') no-repeat 100% -22px, -moz-linear-gradient(center bottom, #fff 85%, #eee 99%);
|
||||
background: url('select2.png') no-repeat 100% -22px, linear-gradient(to bottom, #fff 85%, #eee 99%) 0 0;
|
||||
}
|
||||
|
||||
html[dir="rtl"] .select2-search input {
|
||||
padding: 4px 5px 4px 20px;
|
||||
|
||||
background: #fff url('select2.png') no-repeat -37px -22px;
|
||||
background: url('select2.png') no-repeat -37px -22px, -webkit-gradient(linear, left bottom, left top, color-stop(0.85, #fff), color-stop(0.99, #eee));
|
||||
background: url('select2.png') no-repeat -37px -22px, -webkit-linear-gradient(center bottom, #fff 85%, #eee 99%);
|
||||
background: url('select2.png') no-repeat -37px -22px, -moz-linear-gradient(center bottom, #fff 85%, #eee 99%);
|
||||
background: url('select2.png') no-repeat -37px -22px, linear-gradient(to bottom, #fff 85%, #eee 99%) 0 0;
|
||||
}
|
||||
|
||||
.select2-drop.select2-drop-above .select2-search input {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.select2-search input.select2-active {
|
||||
background: #fff url('select2-spinner.gif') no-repeat 100%;
|
||||
background: url('select2-spinner.gif') no-repeat 100%, -webkit-gradient(linear, left bottom, left top, color-stop(0.85, #fff), color-stop(0.99, #eee));
|
||||
background: url('select2-spinner.gif') no-repeat 100%, -webkit-linear-gradient(center bottom, #fff 85%, #eee 99%);
|
||||
background: url('select2-spinner.gif') no-repeat 100%, -moz-linear-gradient(center bottom, #fff 85%, #eee 99%);
|
||||
background: url('select2-spinner.gif') no-repeat 100%, linear-gradient(to bottom, #fff 85%, #eee 99%) 0 0;
|
||||
}
|
||||
|
||||
.select2-container-active .select2-choice,
|
||||
.select2-container-active .select2-choices {
|
||||
border: 1px solid #5897fb;
|
||||
outline: none;
|
||||
|
||||
-webkit-box-shadow: 0 0 5px rgba(0, 0, 0, .3);
|
||||
box-shadow: 0 0 5px rgba(0, 0, 0, .3);
|
||||
}
|
||||
|
||||
.select2-dropdown-open .select2-choice {
|
||||
border-bottom-color: transparent;
|
||||
-webkit-box-shadow: 0 1px 0 #fff inset;
|
||||
box-shadow: 0 1px 0 #fff inset;
|
||||
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
|
||||
background-color: #eee;
|
||||
background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #fff), color-stop(0.5, #eee));
|
||||
background-image: -webkit-linear-gradient(center bottom, #fff 0%, #eee 50%);
|
||||
background-image: -moz-linear-gradient(center bottom, #fff 0%, #eee 50%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#eeeeee', endColorstr='#ffffff', GradientType=0);
|
||||
background-image: linear-gradient(to top, #fff 0%, #eee 50%);
|
||||
}
|
||||
|
||||
.select2-dropdown-open.select2-drop-above .select2-choice,
|
||||
.select2-dropdown-open.select2-drop-above .select2-choices {
|
||||
border: 1px solid #5897fb;
|
||||
border-top-color: transparent;
|
||||
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #fff), color-stop(0.5, #eee));
|
||||
background-image: -webkit-linear-gradient(center top, #fff 0%, #eee 50%);
|
||||
background-image: -moz-linear-gradient(center top, #fff 0%, #eee 50%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#eeeeee', endColorstr='#ffffff', GradientType=0);
|
||||
background-image: linear-gradient(to bottom, #fff 0%, #eee 50%);
|
||||
}
|
||||
|
||||
.select2-dropdown-open .select2-choice .select2-arrow {
|
||||
background: transparent;
|
||||
border-left: none;
|
||||
filter: none;
|
||||
}
|
||||
html[dir="rtl"] .select2-dropdown-open .select2-choice .select2-arrow {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.select2-dropdown-open .select2-choice .select2-arrow b {
|
||||
background-position: -18px 1px;
|
||||
}
|
||||
|
||||
html[dir="rtl"] .select2-dropdown-open .select2-choice .select2-arrow b {
|
||||
background-position: -16px 1px;
|
||||
}
|
||||
|
||||
.select2-hidden-accessible {
|
||||
border: 0;
|
||||
clip: rect(0 0 0 0);
|
||||
height: 1px;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
}
|
||||
|
||||
/* results */
|
||||
.select2-results {
|
||||
max-height: 200px;
|
||||
padding: 0 0 0 4px;
|
||||
margin: 4px 4px 4px 0;
|
||||
position: relative;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
html[dir="rtl"] .select2-results {
|
||||
padding: 0 4px 0 0;
|
||||
margin: 4px 0 4px 4px;
|
||||
}
|
||||
|
||||
.select2-results ul.select2-result-sub {
|
||||
margin: 0;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.select2-results li {
|
||||
list-style: none;
|
||||
display: list-item;
|
||||
background-image: none;
|
||||
}
|
||||
|
||||
.select2-results li.select2-result-with-children > .select2-result-label {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.select2-results .select2-result-label {
|
||||
padding: 3px 7px 4px;
|
||||
margin: 0;
|
||||
cursor: pointer;
|
||||
|
||||
min-height: 1em;
|
||||
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.select2-results-dept-1 .select2-result-label { padding-left: 20px }
|
||||
.select2-results-dept-2 .select2-result-label { padding-left: 40px }
|
||||
.select2-results-dept-3 .select2-result-label { padding-left: 60px }
|
||||
.select2-results-dept-4 .select2-result-label { padding-left: 80px }
|
||||
.select2-results-dept-5 .select2-result-label { padding-left: 100px }
|
||||
.select2-results-dept-6 .select2-result-label { padding-left: 110px }
|
||||
.select2-results-dept-7 .select2-result-label { padding-left: 120px }
|
||||
|
||||
.select2-results .select2-highlighted {
|
||||
background: #3875d7;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.select2-results li em {
|
||||
background: #feffde;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.select2-results .select2-highlighted em {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.select2-results .select2-highlighted ul {
|
||||
background: #fff;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.select2-results .select2-no-results,
|
||||
.select2-results .select2-searching,
|
||||
.select2-results .select2-ajax-error,
|
||||
.select2-results .select2-selection-limit {
|
||||
background: #f4f4f4;
|
||||
display: list-item;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
/*
|
||||
disabled look for disabled choices in the results dropdown
|
||||
*/
|
||||
.select2-results .select2-disabled.select2-highlighted {
|
||||
color: #666;
|
||||
background: #f4f4f4;
|
||||
display: list-item;
|
||||
cursor: default;
|
||||
}
|
||||
.select2-results .select2-disabled {
|
||||
background: #f4f4f4;
|
||||
display: list-item;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.select2-results .select2-selected {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.select2-more-results.select2-active {
|
||||
background: #f4f4f4 url('select2-spinner.gif') no-repeat 100%;
|
||||
}
|
||||
|
||||
.select2-results .select2-ajax-error {
|
||||
background: rgba(255, 50, 50, .2);
|
||||
}
|
||||
|
||||
.select2-more-results {
|
||||
background: #f4f4f4;
|
||||
display: list-item;
|
||||
}
|
||||
|
||||
/* disabled styles */
|
||||
|
||||
.select2-container.select2-container-disabled .select2-choice {
|
||||
background-color: #f4f4f4;
|
||||
background-image: none;
|
||||
border: 1px solid #ddd;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.select2-container.select2-container-disabled .select2-choice .select2-arrow {
|
||||
background-color: #f4f4f4;
|
||||
background-image: none;
|
||||
border-left: 0;
|
||||
}
|
||||
|
||||
.select2-container.select2-container-disabled .select2-choice abbr {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
/* multiselect */
|
||||
|
||||
.select2-container-multi .select2-choices {
|
||||
height: auto !important;
|
||||
height: 1%;
|
||||
margin: 0;
|
||||
padding: 0 5px 0 0;
|
||||
position: relative;
|
||||
|
||||
border: 1px solid #aaa;
|
||||
cursor: text;
|
||||
overflow: hidden;
|
||||
|
||||
background-color: #fff;
|
||||
background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(1%, #eee), color-stop(15%, #fff));
|
||||
background-image: -webkit-linear-gradient(top, #eee 1%, #fff 15%);
|
||||
background-image: -moz-linear-gradient(top, #eee 1%, #fff 15%);
|
||||
background-image: linear-gradient(to bottom, #eee 1%, #fff 15%);
|
||||
}
|
||||
|
||||
html[dir="rtl"] .select2-container-multi .select2-choices {
|
||||
padding: 0 0 0 5px;
|
||||
}
|
||||
|
||||
.select2-locked {
|
||||
padding: 3px 5px 3px 5px !important;
|
||||
}
|
||||
|
||||
.select2-container-multi .select2-choices {
|
||||
min-height: 26px;
|
||||
}
|
||||
|
||||
.select2-container-multi.select2-container-active .select2-choices {
|
||||
border: 1px solid #5897fb;
|
||||
outline: none;
|
||||
|
||||
-webkit-box-shadow: 0 0 5px rgba(0, 0, 0, .3);
|
||||
box-shadow: 0 0 5px rgba(0, 0, 0, .3);
|
||||
}
|
||||
.select2-container-multi .select2-choices li {
|
||||
float: left;
|
||||
list-style: none;
|
||||
}
|
||||
html[dir="rtl"] .select2-container-multi .select2-choices li
|
||||
{
|
||||
float: right;
|
||||
}
|
||||
.select2-container-multi .select2-choices .select2-search-field {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.select2-container-multi .select2-choices .select2-search-field input {
|
||||
padding: 5px;
|
||||
margin: 1px 0;
|
||||
|
||||
font-family: sans-serif;
|
||||
font-size: 100%;
|
||||
color: #666;
|
||||
outline: 0;
|
||||
border: 0;
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.select2-container-multi .select2-choices .select2-search-field input.select2-active {
|
||||
background: #fff url('select2-spinner.gif') no-repeat 100% !important;
|
||||
}
|
||||
|
||||
.select2-default {
|
||||
color: #999 !important;
|
||||
}
|
||||
|
||||
.select2-container-multi .select2-choices .select2-search-choice {
|
||||
padding: 3px 5px 3px 18px;
|
||||
margin: 3px 0 3px 5px;
|
||||
position: relative;
|
||||
|
||||
line-height: 13px;
|
||||
color: #333;
|
||||
cursor: default;
|
||||
border: 1px solid #aaaaaa;
|
||||
|
||||
border-radius: 3px;
|
||||
|
||||
-webkit-box-shadow: 0 0 2px #fff inset, 0 1px 0 rgba(0, 0, 0, 0.05);
|
||||
box-shadow: 0 0 2px #fff inset, 0 1px 0 rgba(0, 0, 0, 0.05);
|
||||
|
||||
background-clip: padding-box;
|
||||
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
|
||||
background-color: #e4e4e4;
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#eeeeee', endColorstr='#f4f4f4', GradientType=0);
|
||||
background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(20%, #f4f4f4), color-stop(50%, #f0f0f0), color-stop(52%, #e8e8e8), color-stop(100%, #eee));
|
||||
background-image: -webkit-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eee 100%);
|
||||
background-image: -moz-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eee 100%);
|
||||
background-image: linear-gradient(to bottom, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eee 100%);
|
||||
}
|
||||
html[dir="rtl"] .select2-container-multi .select2-choices .select2-search-choice
|
||||
{
|
||||
margin: 3px 5px 3px 0;
|
||||
padding: 3px 18px 3px 5px;
|
||||
}
|
||||
.select2-container-multi .select2-choices .select2-search-choice .select2-chosen {
|
||||
cursor: default;
|
||||
}
|
||||
.select2-container-multi .select2-choices .select2-search-choice-focus {
|
||||
background: #d4d4d4;
|
||||
}
|
||||
|
||||
.select2-search-choice-close {
|
||||
display: block;
|
||||
width: 12px;
|
||||
height: 13px;
|
||||
position: absolute;
|
||||
right: 3px;
|
||||
top: 4px;
|
||||
|
||||
font-size: 1px;
|
||||
outline: none;
|
||||
background: url('select2.png') right top no-repeat;
|
||||
}
|
||||
html[dir="rtl"] .select2-search-choice-close {
|
||||
right: auto;
|
||||
left: 3px;
|
||||
}
|
||||
|
||||
.select2-container-multi .select2-search-choice-close {
|
||||
left: 3px;
|
||||
}
|
||||
|
||||
html[dir="rtl"] .select2-container-multi .select2-search-choice-close {
|
||||
left: auto;
|
||||
right: 2px;
|
||||
}
|
||||
|
||||
.select2-container-multi .select2-choices .select2-search-choice .select2-search-choice-close:hover {
|
||||
background-position: right -11px;
|
||||
}
|
||||
.select2-container-multi .select2-choices .select2-search-choice-focus .select2-search-choice-close {
|
||||
background-position: right -11px;
|
||||
}
|
||||
|
||||
/* disabled styles */
|
||||
.select2-container-multi.select2-container-disabled .select2-choices {
|
||||
background-color: #f4f4f4;
|
||||
background-image: none;
|
||||
border: 1px solid #ddd;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.select2-container-multi.select2-container-disabled .select2-choices .select2-search-choice {
|
||||
padding: 3px 5px 3px 5px;
|
||||
border: 1px solid #ddd;
|
||||
background-image: none;
|
||||
background-color: #f4f4f4;
|
||||
}
|
||||
|
||||
.select2-container-multi.select2-container-disabled .select2-choices .select2-search-choice .select2-search-choice-close { display: none;
|
||||
background: none;
|
||||
}
|
||||
/* end multiselect */
|
||||
|
||||
|
||||
.select2-result-selectable .select2-match,
|
||||
.select2-result-unselectable .select2-match {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.select2-offscreen, .select2-offscreen:focus {
|
||||
clip: rect(0 0 0 0) !important;
|
||||
width: 1px !important;
|
||||
height: 1px !important;
|
||||
border: 0 !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
overflow: hidden !important;
|
||||
position: absolute !important;
|
||||
outline: 0 !important;
|
||||
left: 0px !important;
|
||||
top: 0px !important;
|
||||
}
|
||||
|
||||
.select2-display-none {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.select2-measure-scrollbar {
|
||||
position: absolute;
|
||||
top: -10000px;
|
||||
left: -10000px;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
overflow: scroll;
|
||||
}
|
||||
|
||||
/* Retina-ize icons */
|
||||
|
||||
@media only screen and (-webkit-min-device-pixel-ratio: 1.5), only screen and (min-resolution: 2dppx) {
|
||||
.select2-search input,
|
||||
.select2-search-choice-close,
|
||||
.select2-container .select2-choice abbr,
|
||||
.select2-container .select2-choice .select2-arrow b {
|
||||
background-image: url('select2x2.png') !important;
|
||||
background-repeat: no-repeat !important;
|
||||
background-size: 60px 40px !important;
|
||||
}
|
||||
|
||||
.select2-search input {
|
||||
background-position: 100% -21px !important;
|
||||
}
|
||||
}
|
BIN
static/facelift/css/lib/select2.png
Normal file
BIN
static/facelift/css/lib/select2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 613 B |
140
static/facelift/js/edit-milestones.js
Normal file
140
static/facelift/js/edit-milestones.js
Normal file
|
@ -0,0 +1,140 @@
|
|||
$(document).ready(function () {
|
||||
var idCounter = -1;
|
||||
var milestonesForm = $('#milestones-form');
|
||||
|
||||
// make sure we got the lowest number for idCounter
|
||||
milestonesForm.find('.edit-milestone input[name$="-id"]').each(function () {
|
||||
var v = +this.value;
|
||||
if (!isNaN(v) && v < idCounter)
|
||||
idCounter = v - 1;
|
||||
});
|
||||
|
||||
function setChanged() {
|
||||
$(this).closest(".edit-milestone").addClass("changed");
|
||||
setSubmitButtonState();
|
||||
}
|
||||
|
||||
milestonesForm.on("change", '.edit-milestone select,.edit-milestone input,.edit-milestone textarea', setChanged);
|
||||
milestonesForm.on("click", '.edit-milestone .select2 input', setChanged);
|
||||
|
||||
// the required stuff seems to trip up many browsers with dynamic forms
|
||||
milestonesForm.find("input").prop("required", false);
|
||||
|
||||
|
||||
function setSubmitButtonState() {
|
||||
var action, label;
|
||||
if (milestonesForm.find("input[name$=delete]:visible").length > 0)
|
||||
action = "review";
|
||||
else
|
||||
action = "save";
|
||||
|
||||
milestonesForm.find("input[name=action]").val(action);
|
||||
|
||||
var submit = milestonesForm.find("[type=submit]");
|
||||
submit.text(submit.data("label" + action));
|
||||
if (milestonesForm.find(".edit-milestone.changed").length > 0 || action == "review")
|
||||
submit.show();
|
||||
else
|
||||
submit.hide();
|
||||
}
|
||||
|
||||
milestonesForm.find(".milestone").click(function () {
|
||||
var row = $(this), editRow = row.next(".edit-milestone");
|
||||
row.hide();
|
||||
editRow.show();
|
||||
|
||||
editRow.find('input[name$="desc"]').focus();
|
||||
|
||||
setSubmitButtonState();
|
||||
|
||||
// collapse unchanged rows
|
||||
milestonesForm.find(".milestone").not(this).each(function () {
|
||||
var e = $(this).next('.edit-milestone');
|
||||
if (e.is(":visible") && !e.hasClass("changed")) {
|
||||
$(this).show();
|
||||
e.hide();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
milestonesForm.find(".add-milestone").click(function() {
|
||||
// move Add milestone row and duplicate hidden template
|
||||
var row = $(this).closest("tr"), editRow = row.next(".edit-milestone");
|
||||
row.closest("table").append(row).append(editRow.clone());
|
||||
|
||||
// fixup template
|
||||
var newId = idCounter;
|
||||
--idCounter;
|
||||
|
||||
var prefix = "m" + newId;
|
||||
editRow.find('input[name="prefix"]').val(prefix);
|
||||
|
||||
editRow.find("input,select,textarea").each(function () {
|
||||
if (this.name == "prefix")
|
||||
return;
|
||||
|
||||
if (this.name == "id")
|
||||
this.value = "" + idCounter;
|
||||
|
||||
this.name = prefix + "-" + this.name;
|
||||
this.id = prefix + "-" + this.id;
|
||||
});
|
||||
editRow.find("label").each(function () {
|
||||
if (this.htmlFor)
|
||||
this.htmlFor = prefix + "-" + this.htmlFor;
|
||||
});
|
||||
|
||||
editRow.removeClass("template");
|
||||
editRow.show();
|
||||
|
||||
editRow.find(".select2-field").each(function () {
|
||||
window.setupSelect2Field($(this)); // from ietf.js
|
||||
});
|
||||
});
|
||||
|
||||
function setResolvedState() {
|
||||
var resolved = $(this).is(":checked");
|
||||
var label = $(this).closest(".edit-milestone").find("label[for=" + this.id + "]");
|
||||
var reason = $(this).closest(".edit-milestone").find("[name$=resolved]");
|
||||
if (resolved) {
|
||||
reason.closest(".form-group").show();
|
||||
if (!reason.val())
|
||||
reason.val(reason.data("default"));
|
||||
}
|
||||
else {
|
||||
reason.closest(".form-group").hide();
|
||||
reason.val("");
|
||||
}
|
||||
}
|
||||
|
||||
milestonesForm.find(".edit-milestone [name$=resolved_checkbox]").each(setResolvedState);
|
||||
milestonesForm.on("change", ".edit-milestone [name$=resolved_checkbox]", setResolvedState);
|
||||
|
||||
function setDeleteState() {
|
||||
var edit = $(this).closest(".edit-milestone"), row = edit.prev(".milestone");
|
||||
|
||||
if ($(this).is(":checked")) {
|
||||
if (+edit.find('input[name$="id"]').val() < 0) {
|
||||
edit.remove();
|
||||
setSubmitButtonState();
|
||||
}
|
||||
else {
|
||||
row.addClass("delete");
|
||||
edit.addClass("delete");
|
||||
}
|
||||
}
|
||||
else {
|
||||
row.removeClass("delete");
|
||||
edit.removeClass("delete");
|
||||
}
|
||||
}
|
||||
|
||||
milestonesForm.find(".edit-milestone [name$=delete]").each(setDeleteState);
|
||||
milestonesForm.on("change", ".edit-milestone input[name$=delete]", setDeleteState);
|
||||
|
||||
milestonesForm.find('.edit-milestone .has-error').each(function () {
|
||||
$(this).closest(".edit-milestone").prev().click();
|
||||
});
|
||||
|
||||
setSubmitButtonState();
|
||||
});
|
|
@ -192,160 +192,59 @@ $(".snippet .show-all").click(function () {
|
|||
// }
|
||||
// });
|
||||
|
||||
function setupSelect2Field(e) {
|
||||
var url = e.data("ajax-url");
|
||||
if (!url)
|
||||
return;
|
||||
|
||||
function to_disp(t) {
|
||||
// typehead/tokenfield don't fully deal with HTML entities
|
||||
return $('<div/>').html(t).text().replace(/[<>"]/g, function (m) {
|
||||
return {
|
||||
'<': '(',
|
||||
'>': ')',
|
||||
'"': ''
|
||||
}[m];
|
||||
});
|
||||
var maxEntries = e.data("max-entries");
|
||||
var multiple = maxEntries != 1;
|
||||
var prefetched = e.data("pre");
|
||||
e.select2({
|
||||
multiple: multiple,
|
||||
minimumInputLength: 2,
|
||||
width: "off",
|
||||
allowClear: true,
|
||||
maximumSelectionSize: maxEntries,
|
||||
ajax: {
|
||||
url: url,
|
||||
dataType: "json",
|
||||
quietMillis: 250,
|
||||
data: function (term, page) {
|
||||
return {
|
||||
q: term,
|
||||
p: page
|
||||
};
|
||||
},
|
||||
results: function (results) {
|
||||
return {
|
||||
results: results,
|
||||
more: results.length == 10
|
||||
};
|
||||
}
|
||||
},
|
||||
escapeMarkup: function (m) {
|
||||
return m;
|
||||
},
|
||||
initSelection: function (element, cb) {
|
||||
if (!multiple && prefetched.length > 0)
|
||||
cb(prefetched[0]);
|
||||
else
|
||||
cb(prefetched);
|
||||
|
||||
},
|
||||
dropdownCssClass: "bigdrop"
|
||||
});
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
$(".select2-field").each(function () {
|
||||
if ($(this).closest(".template").length > 0)
|
||||
return;
|
||||
|
||||
$(".tokenized-form").submit(function (e) {
|
||||
$(this).find(".tokenized-field").each(function () {
|
||||
var f = $(this);
|
||||
var io = f.data("io");
|
||||
var format = f.data("format");
|
||||
var t = f.tokenfield("getTokens");
|
||||
|
||||
var v = $.map(t, function(o) { return o["value"]; });
|
||||
if (format === "json") {
|
||||
v = JSON.stringify(v);
|
||||
} else if (format === "csv") {
|
||||
v = v.join(", ");
|
||||
} else {
|
||||
console.log(io, "unknown format");
|
||||
v = v.join(" ");
|
||||
}
|
||||
f.val(v);
|
||||
if (io) {
|
||||
$(io).val(v);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
$(".tokenized-field").each(function () {
|
||||
// autocomplete interferes with the token popup
|
||||
$(this).attr("autocomplete", "off");
|
||||
|
||||
// in which field ID are we expected to place the result
|
||||
// (we also read the prefill information from there)
|
||||
var io = $(this).data("io");
|
||||
var raw = "";
|
||||
if (io) {
|
||||
raw = $(io).val();
|
||||
} else {
|
||||
io = "#" + this.id;
|
||||
raw = $(this).val();
|
||||
}
|
||||
console.log("io: ", io);
|
||||
console.log(io, "raw", raw);
|
||||
$(this).data("io", io);
|
||||
|
||||
// which field of the JSON are we supposed to display
|
||||
var display = $(this).data("display");
|
||||
if (!display) {
|
||||
display = "name";
|
||||
}
|
||||
console.log(io, "display", display);
|
||||
$(this).data("display", display);
|
||||
|
||||
// which field of the JSON are we supposed to return
|
||||
var result = $(this).data("result");
|
||||
if (!result) {
|
||||
result = "id";
|
||||
}
|
||||
console.log(io, "result", result);
|
||||
$(this).data("result", result);
|
||||
|
||||
// what kind of data are we returning (json or csv)
|
||||
var format = $(this).data("format");
|
||||
if (!format) {
|
||||
format = "csv";
|
||||
}
|
||||
console.log(io, "format", format);
|
||||
$(this).data("format", format);
|
||||
|
||||
// make tokens to prefill the input
|
||||
if (raw) {
|
||||
raw = $.parseJSON(raw);
|
||||
var pre = [];
|
||||
if (!raw[0] || !raw[0][display]) {
|
||||
$.each(raw, function(k, v) {
|
||||
var obj = {};
|
||||
obj["value"] = k;
|
||||
obj["label"] = to_disp(v);
|
||||
pre.push(obj);
|
||||
});
|
||||
} else {
|
||||
for (var i in raw) {
|
||||
var obj = {};
|
||||
obj["value"] = raw[i][result];
|
||||
obj["label"] = to_disp(raw[i][display]);
|
||||
pre.push(obj);
|
||||
}
|
||||
}
|
||||
$(this).val(pre);
|
||||
}
|
||||
console.log(io, "pre", pre);
|
||||
|
||||
// check if the ajax-url contains a query parameter, add one if not
|
||||
var url = $(this).data("ajax-url");
|
||||
if (url.indexOf("?") === -1) {
|
||||
url += "?q=";
|
||||
}
|
||||
$(this).data("ajax-url", url);
|
||||
console.log(io, "ajax-url", url);
|
||||
|
||||
var bh = new Bloodhound({
|
||||
datumTokenizer: function (d) {
|
||||
return Bloodhound.tokenizers.nonword(d[display]);
|
||||
},
|
||||
queryTokenizer: Bloodhound.tokenizers.nonword,
|
||||
limit: 20,
|
||||
remote: {
|
||||
url: url + "%QUERY",
|
||||
filter: function (data) {
|
||||
return $.map($.grep(data, function (n, i) {
|
||||
return true;
|
||||
}), function (n, i) {
|
||||
n["label"] = to_disp(n[display]);
|
||||
n["value"] = n[result];
|
||||
return n;
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
bh.initialize();
|
||||
$(this).tokenfield({
|
||||
typeahead: [{
|
||||
highlight: true,
|
||||
minLength: 3,
|
||||
hint: true
|
||||
}, {
|
||||
source: bh.ttAdapter(),
|
||||
displayKey: "label"
|
||||
}],
|
||||
beautify: true,
|
||||
delimiter: [',', ';']
|
||||
}).tokenfield("setTokens", pre);
|
||||
|
||||
// only allow tokens from the popup to be added to the field, no free text
|
||||
$(this).on('tokenfield:createtoken', function (event) {
|
||||
var existingTokens = $(this).tokenfield('getTokens');
|
||||
$.each(existingTokens, function(index, token) {
|
||||
if (event.attrs.id === undefined) {
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
setupSelect2Field($(this));
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// Use the Bootstrap3 tooltip plugin for all elements with a title attribute
|
||||
$('[title][title!=""]').tooltip();
|
||||
|
|
23
static/facelift/js/lib/select2-3.5.2.min.js
vendored
Normal file
23
static/facelift/js/lib/select2-3.5.2.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue