Revamp the AutocompletedEmailsField API a bit to allow for an AutocompletedPersonsField
- Legacy-Id: 8282
This commit is contained in:
parent
065660b66f
commit
92e5694ed3
|
@ -1043,7 +1043,7 @@ def edit_shepherd_writeup(request, name):
|
|||
context_instance=RequestContext(request))
|
||||
|
||||
class ShepherdForm(forms.Form):
|
||||
shepherd = AutocompletedEmailField(required=False)
|
||||
shepherd = AutocompletedEmailField(required=False, only_users=True)
|
||||
|
||||
def edit_shepherd(request, name):
|
||||
"""Change the shepherd for a Document"""
|
||||
|
|
|
@ -29,10 +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)
|
||||
secretaries = AutocompletedEmailsField(label="Secretaries", required=False)
|
||||
techadv = AutocompletedEmailsField(label="Technical Advisors", required=False)
|
||||
delegates = AutocompletedEmailsField(label="Delegates", required=False, help_text=mark_safe("Chairs can delegate the authority to update the state of group documents - max %s persons at a given time" % MAX_GROUP_DELEGATES), max_entries=MAX_GROUP_DELEGATES)
|
||||
chairs = AutocompletedEmailsField(required=False, only_users=True)
|
||||
secretaries = AutocompletedEmailsField(required=False, only_users=True)
|
||||
techadv = AutocompletedEmailsField(label="Technical Advisors", required=False, only_users=True)
|
||||
delegates = AutocompletedEmailsField(required=False, only_users=True, max_entries=MAX_GROUP_DELEGATES,
|
||||
help_text=mark_safe("Chairs can delegate the authority to update the state of group documents - max %s persons at a given time" % MAX_GROUP_DELEGATES))
|
||||
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)
|
||||
|
|
|
@ -31,7 +31,7 @@ def stream_documents(request, acronym):
|
|||
return render_to_response('group/stream_documents.html', {'stream':stream, 'docs':docs, 'meta':meta }, context_instance=RequestContext(request))
|
||||
|
||||
class StreamEditForm(forms.Form):
|
||||
delegates = AutocompletedEmailsField(label="Delegates", required=False)
|
||||
delegates = AutocompletedEmailsField(required=False, only_users=True)
|
||||
|
||||
def stream_edit(request, acronym):
|
||||
group = get_object_or_404(Group, acronym=acronym)
|
||||
|
|
|
@ -6,26 +6,42 @@ from django.core.urlresolvers import reverse as urlreverse
|
|||
|
||||
import debug # pyflakes:ignore
|
||||
|
||||
from ietf.person.models import Email
|
||||
from ietf.person.models import Email, Person
|
||||
|
||||
def json_emails(emails):
|
||||
if isinstance(emails, basestring):
|
||||
emails = Email.objects.filter(address__in=[x.strip() for x in emails.split(",") if x.strip()]).select_related("person")
|
||||
return json.dumps([{"id": e.address + "", "name": escape(u"%s <%s>" % (e.person.name, e.address))} for e in emails])
|
||||
def tokeninput_id_name_json(objs):
|
||||
def format_email(e):
|
||||
return escape(u"%s <%s>" % (e.person.name, e.address))
|
||||
def format_person(p):
|
||||
return escape(p.name)
|
||||
|
||||
class AutocompletedEmailsField(forms.CharField):
|
||||
"""Multi-select field using jquery.tokeninput.js. Since the API of
|
||||
tokeninput" is asymmetric, we have to pass it a JSON
|
||||
representation on the way out and parse the ids coming back as a
|
||||
comma-separated list on the way in."""
|
||||
formatter = format_email if objs and isinstance(objs[0], Email) else format_person
|
||||
|
||||
def __init__(self, max_entries=None, hint_text="Type in name or email to search for person and email address", only_users=True,
|
||||
return json.dumps([{ "id": o.pk, "name": 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.
|
||||
|
||||
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.
|
||||
|
||||
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."""
|
||||
|
||||
def __init__(self,
|
||||
max_entries=None, # max number of selected objs
|
||||
only_users=False, # only select persons who also have a user
|
||||
model=Person, # or Email
|
||||
hint_text="Type in name to search for person",
|
||||
*args, **kwargs):
|
||||
kwargs["max_length"] = 1000
|
||||
self.max_entries = max_entries
|
||||
self.only_users = only_users
|
||||
self.model = model
|
||||
|
||||
super(AutocompletedEmailsField, self).__init__(*args, **kwargs)
|
||||
super(AutocompletedPersonsField, self).__init__(*args, **kwargs)
|
||||
|
||||
self.widget.attrs["class"] = "tokenized-field"
|
||||
self.widget.attrs["data-hint-text"] = hint_text
|
||||
|
@ -39,41 +55,64 @@ class AutocompletedEmailsField(forms.CharField):
|
|||
if not value:
|
||||
value = ""
|
||||
if isinstance(value, basestring):
|
||||
addresses = self.parse_tokenized_value(value)
|
||||
value = Email.objects.filter(address__in=addresses).select_related("person")
|
||||
if isinstance(value, Email):
|
||||
pks = self.parse_tokenized_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"] = json_emails(value)
|
||||
self.widget.attrs["data-pre"] = tokeninput_id_name_json(value)
|
||||
|
||||
# doing this in the constructor is difficult because the URL
|
||||
# patterns may not have been fully constructed there yet
|
||||
self.widget.attrs["data-ajax-url"] = urlreverse("ajax_search_emails")
|
||||
self.widget.attrs["data-ajax-url"] = urlreverse("ajax_tokeninput_search", 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)
|
||||
|
||||
def clean(self, value):
|
||||
value = super(AutocompletedEmailsField, self).clean(value)
|
||||
addresses = self.parse_tokenized_value(value)
|
||||
value = super(AutocompletedPersonsField, self).clean(value)
|
||||
pks = self.parse_tokenized_value(value)
|
||||
|
||||
emails = Email.objects.filter(address__in=addresses).exclude(person=None).select_related("person")
|
||||
# there are still a couple of active roles without accounts so don't disallow those yet
|
||||
#if self.only_users:
|
||||
# emails = emails.exclude(person__user=None)
|
||||
found_addresses = [e.address for e in emails]
|
||||
objs = self.model.objects.filter(pk__in=pks)
|
||||
if self.model == Email:
|
||||
objs = objs.exclude(person=None).select_related("person")
|
||||
|
||||
failed_addresses = [x for x in addresses if x not in found_addresses]
|
||||
if failed_addresses:
|
||||
raise forms.ValidationError(u"Could not recognize the following email addresses: %s. You can only input addresses already registered in the Datatracker." % ", ".join(failed_addresses))
|
||||
# there are still a couple of active roles without accounts so don't disallow those yet
|
||||
#if self.only_users:
|
||||
# objs = objs.exclude(person__user=None)
|
||||
|
||||
if self.max_entries != None and len(emails) > self.max_entries:
|
||||
found_pks = [e.pk for e in objs]
|
||||
|
||||
failed_pks = [x for x in pks if x not in found_pks]
|
||||
if failed_pks:
|
||||
raise forms.ValidationError(u"Could not recognize the following {model_name}s: {pks}. You can only input {model_name}s already registered in the Datatracker.".format(pks=", ".join(failed_pks), model_name=self.model.__name__.lower()))
|
||||
|
||||
if self.max_entries != None and len(objs) > self.max_entries:
|
||||
raise forms.ValidationError(u"You can select at most %s entries only." % self.max_entries)
|
||||
|
||||
return emails
|
||||
return objs
|
||||
|
||||
class AutocompletedPersonField(AutocompletedPersonsField):
|
||||
"""Version of AutocompletedPersonsField specialized to a single object."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs["max_entries"] = 1
|
||||
super(AutocompletedPersonField, self).__init__(*args, **kwargs)
|
||||
|
||||
def clean(self, value):
|
||||
return super(AutocompletedPersonField, self).clean(value).first()
|
||||
|
||||
|
||||
class AutocompletedEmailsField(AutocompletedPersonsField):
|
||||
"""Version of AutocompletedPersonsField 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)
|
||||
|
||||
class AutocompletedEmailField(AutocompletedEmailsField):
|
||||
"""Version of AutocompletedEmailsField specialized to a single Email."""
|
||||
"""Version of AutocompletedEmailsField specialized to a single object."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs["max_entries"] = 1
|
||||
|
@ -81,3 +120,5 @@ class AutocompletedEmailField(AutocompletedEmailsField):
|
|||
|
||||
def clean(self, value):
|
||||
return super(AutocompletedEmailField, 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_search_emails"), dict(q=person.name))
|
||||
r = self.client.get(urlreverse("ietf.person.views.ajax_tokeninput_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/$', "ietf.person.views.ajax_search_emails", None, 'ajax_search_emails'),
|
||||
(r'^search/(?P<model_name>(person|email))/$', "ietf.person.views.ajax_tokeninput_search", None, 'ajax_tokeninput_search'),
|
||||
(r'^(?P<personid>[a-z0-9]+).json$', ajax.person_json),
|
||||
)
|
||||
|
|
|
@ -1,22 +1,38 @@
|
|||
from django.http import HttpResponse
|
||||
from django.db.models import Q
|
||||
|
||||
from ietf.person.models import Email
|
||||
from ietf.person.fields import json_emails
|
||||
from ietf.person.models import Email, Person
|
||||
from ietf.person.fields import tokeninput_id_name_json
|
||||
|
||||
def ajax_tokeninput_search(request, model_name):
|
||||
if model_name == "email":
|
||||
model = Email
|
||||
else:
|
||||
model = Person
|
||||
|
||||
def ajax_search_emails(request):
|
||||
q = [w.strip() for w in request.GET.get('q', '').split() if w.strip()]
|
||||
|
||||
if not q:
|
||||
emails = Email.objects.none()
|
||||
objs = model.objects.none()
|
||||
else:
|
||||
query = Q()
|
||||
for t in q:
|
||||
query &= Q(person__alias__name__icontains=t) | Q(address__icontains=t)
|
||||
|
||||
emails = Email.objects.filter(query).exclude(person=None)
|
||||
objs = model.objects.filter(query)
|
||||
|
||||
if request.GET.get("user") == "1":
|
||||
emails = emails.exclude(person__user=None) # require an account at the Datatracker
|
||||
# require an account at the Datatracker
|
||||
only_users = request.GET.get("user") == "1"
|
||||
|
||||
emails = emails.filter(active=True).order_by('person__name').distinct()[:10]
|
||||
return HttpResponse(json_emails(emails), content_type='application/json')
|
||||
if model == Email:
|
||||
objs = objs.filter(active=True).order_by('person__name').exclude(person=None)
|
||||
if only_users:
|
||||
objs = objs.exclude(person__user=None)
|
||||
elif model == Person:
|
||||
objs = objs.order_by("name")
|
||||
if only_users:
|
||||
objs = objs.exclude(user=None)
|
||||
|
||||
objs = objs.distinct()[:10]
|
||||
|
||||
return HttpResponse(tokeninput_id_name_json(objs), content_type='application/json')
|
||||
|
|
|
@ -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)
|
||||
shepherd = AutocompletedEmailField(required=False, only_users=True)
|
||||
|
||||
class Meta:
|
||||
model = Document
|
||||
|
|
Loading…
Reference in a new issue