diff --git a/ietf/ietfauth/tests.py b/ietf/ietfauth/tests.py index 4a319d46e..cf459eaf4 100644 --- a/ietf/ietfauth/tests.py +++ b/ietf/ietfauth/tests.py @@ -647,4 +647,44 @@ class IetfAuthTests(TestCase): self.assertIn("API key usage", mail['subject']) self.assertIn(" %s times" % count, body) self.assertIn(date, body) + + def test_edit_person_extresources(self): + url = urlreverse('ietf.ietfauth.views.edit_person_externalresources') + person = PersonFactory() + + r = self.client.get(url) + self.assertNotEqual(r.status_code, 200) + + self.client.login(username=person.user.username,password=person.user.username+'+password') + + r = self.client.get(url) + self.assertEqual(r.status_code,200) + q = PyQuery(r.content) + self.assertEqual(len(q('form textarea[id=id_resources]')),1) + + badlines = ( + 'github_repo https://github3.com/some/repo', + 'github_notify badaddr', + 'website /not/a/good/url' + 'notavalidtag blahblahblah' + ) + + for line in badlines: + r = self.client.post(url, dict(resources=line, submit="1")) + self.assertEqual(r.status_code, 200) + q = PyQuery(r.content) + self.assertTrue(q('.alert-danger')) + + goodlines = """ + github_repo https://github.com/some/repo Some display text + github_notify notify@example.com + github_username githubuser + website http://example.com/http/is/fine + """ + + r = self.client.post(url, dict(resources=goodlines, submit="1")) + self.assertEqual(r.status_code,302) + self.assertEqual(person.personextresource_set.count(), 4) + self.assertEqual(person.personextresource_set.get(name__slug='github_repo').display_name, 'Some display text') + diff --git a/ietf/ietfauth/urls.py b/ietf/ietfauth/urls.py index d1b206c07..ddd4833cb 100644 --- a/ietf/ietfauth/urls.py +++ b/ietf/ietfauth/urls.py @@ -18,6 +18,7 @@ urlpatterns = [ url(r'^logout/$', logout), url(r'^password/$', views.change_password), url(r'^profile/$', views.profile), + url(r'^editexternalresources/$', views.edit_person_externalresources), url(r'^reset/$', views.password_reset), url(r'^reset/confirm/(?P[^/]+)/$', views.confirm_password_reset), url(r'^review/$', views.review_overview), diff --git a/ietf/ietfauth/views.py b/ietf/ietfauth/views.py index 129123b8f..6e7ff9bc6 100644 --- a/ietf/ietfauth/views.py +++ b/ietf/ietfauth/views.py @@ -67,12 +67,15 @@ from ietf.ietfauth.forms import ( RegistrationForm, PasswordForm, ResetPasswordF from ietf.ietfauth.htpasswd import update_htpasswd_file from ietf.ietfauth.utils import role_required, has_role from ietf.mailinglists.models import Subscribed, Whitelisted +from ietf.name.models import ExtResourceName from ietf.person.models import Person, Email, Alias, PersonalApiKey, PERSON_API_KEY_VALUES from ietf.review.models import ReviewerSettings, ReviewWish, ReviewAssignment from ietf.review.utils import unavailable_periods_to_list, get_default_filter_re from ietf.doc.fields import SearchableDocumentField from ietf.utils.decorators import person_required from ietf.utils.mail import send_mail +from ietf.utils.validators import validate_external_resource_value + def index(request): return render(request, 'registration/index.html') @@ -286,6 +289,79 @@ def profile(request): 'settings':settings, }) +@login_required +@person_required +def edit_person_externalresources(request): + class PersonExtResourceForm(forms.Form): + resources = forms.CharField(widget=forms.Textarea, label="Additional Resources", required=False, + help_text=("Format: 'tag value (Optional description)'." + " Separate multiple entries with newline. When the value is a URL, use https:// where possible.") ) + + def clean_resources(self): + lines = [x.strip() for x in self.cleaned_data["resources"].splitlines() if x.strip()] + errors = [] + for l in lines: + parts = l.split() + if len(parts) == 1: + errors.append("Too few fields: Expected at least tag and value: '%s'" % l) + elif len(parts) >= 2: + name_slug = parts[0] + try: + name = ExtResourceName.objects.get(slug=name_slug) + except ObjectDoesNotExist: + errors.append("Bad tag in '%s': Expected one of %s" % (l, ', '.join([ o.slug for o in ExtResourceName.objects.all() ]))) + continue + value = parts[1] + try: + validate_external_resource_value(name, value) + except ValidationError as e: + e.message += " : " + value + errors.append(e) + if errors: + raise ValidationError(errors) + return lines + + def format_resources(resources, fs="\n"): + res = [] + for r in resources: + if r.display_name: + res.append("%s %s (%s)" % (r.name.slug, r.value, r.display_name.strip('()'))) + else: + res.append("%s %s" % (r.name.slug, r.value)) + # TODO: This is likely problematic if value has spaces. How then to delineate value and display_name? Perhaps in the short term move to comma or pipe separation. + # Might be better to shift to a formset instead of parsing these lines. + return fs.join(res) + + person = request.user.person + + old_resources = format_resources(person.personextresource_set.all()) + + if request.method == 'POST': + form = PersonExtResourceForm(request.POST) + if form.is_valid(): + old_resources = sorted(old_resources.splitlines()) + new_resources = sorted(form.cleaned_data['resources']) + if old_resources != new_resources: + person.personextresource_set.all().delete() + for u in new_resources: + parts = u.split(None, 2) + name = parts[0] + value = parts[1] + display_name = ' '.join(parts[2:]).strip('()') + person.personextresource_set.create(value=value, name_id=name, display_name=display_name) + new_resources = format_resources(person.personextresource_set.all()) + messages.success(request,"Person resources updated.") + else: + messages.info(request,"No change in Person resources.") + return redirect('ietf.ietfauth.views.profile') + else: + form = PersonExtResourceForm(initial={'resources': old_resources, }) + + info = "Valid tags:

%s" % ', '.join([ o.slug for o in ExtResourceName.objects.all().order_by('slug') ]) + # May need to explain the tags more - probably more reason to move to a formset. + title = "Additional person resources" + return render(request, 'ietfauth/edit_field.html',dict(person=person, form=form, title=title, info=info) ) + def confirm_new_email(request, auth): try: username, email = django.core.signing.loads(auth, salt="add_email", max_age=settings.DAYS_TO_EXPIRE_REGISTRATION_LINK * 24 * 60 * 60) diff --git a/ietf/templates/ietfauth/edit_field.html b/ietf/templates/ietfauth/edit_field.html new file mode 100644 index 000000000..4a3ae692a --- /dev/null +++ b/ietf/templates/ietfauth/edit_field.html @@ -0,0 +1,33 @@ +{% extends "base.html" %} +{# Copyright The IETF Trust 2015, All Rights Reserved #} +{% load origin %} + +{% load bootstrap3 %} + +{% block title %} + {{ title }} {{ person.plain_name }} +{% endblock %} + +{% block content %} + {% origin %} +

{{ title }}
{{ person.plain_name }}

+ +

+ + {{ info|safe }} + +

+ + +
+ {% csrf_token %} + {% bootstrap_form form %} + + {% buttons %} + + + Back + {% endbuttons %} +
+ +{% endblock %} diff --git a/ietf/templates/person/profile.html b/ietf/templates/person/profile.html index 4893ba5ba..7a6f1d83a 100644 --- a/ietf/templates/person/profile.html +++ b/ietf/templates/person/profile.html @@ -57,7 +57,19 @@ {% endif %} - + {% if person.personextresource_set.exists %} +
+

External Resources

+ + {% for extres in person.personextresource_set.all %} + + + + + {% endfor %} +
{% firstof extres.display_name extres.name.name %}{{extres.value}}
+
+ {% endif %}

RFCs

diff --git a/ietf/templates/registration/edit_profile.html b/ietf/templates/registration/edit_profile.html index 23f63e31e..b879c4aa8 100644 --- a/ietf/templates/registration/edit_profile.html +++ b/ietf/templates/registration/edit_profile.html @@ -94,7 +94,7 @@
{{person|is_nomcom_eligible|yesno:'Yes,No,No'}}
-

+

This calculation is EXPERIMENTAL.
If you believe it is incorrect, make sure you've added all the @@ -111,6 +111,22 @@

+
+ +
+ {% for extres in person.personextresource_set.all %} +
+
{% firstof extres.display_name extres.name.name %}
+
{{extres.value}} + {% if forloop.first %} {% endif %} +
+
+ {% empty %} +
None 
+ {% endfor %} +
+
+