diff --git a/js/admin/duplicate-sponsors.js b/js/admin/duplicate-sponsors.js
new file mode 100644
index 00000000..b9478309
--- /dev/null
+++ b/js/admin/duplicate-sponsors.js
@@ -0,0 +1,106 @@
+import React from 'react';
+import Cookies from "js-cookie";
+
+class DuplicateSponsors extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ submitSuccess: false,
+ state: this.props.state,
+ sponsors: ['sponsorGroup0'],
+ matchRequests: []
+ }
+ this.handleInputChange = this.handleInputChange.bind(this);
+ this.handleSubmit = this.handleSubmit.bind(this);
+ this.pullFormData = this.pullFormData.bind(this);
+ this.addNewSponsorGroup = this.addNewSponsorGroup.bind(this);
+ }
+
+ handleSubmit(event) {
+ const csrftoken = Cookies.get("csrftoken");
+ const url = "/admin/people/update/duplicate_sponsors/";
+ const sponsorData = this.pullFormData();
+
+ fetch(url, {
+ method: "POST",
+ credentials: "same-origin",
+ headers: {
+ "Accept": "application/json",
+ "X-Requested-With": "XMLHttpRequest",
+ "X-CSRFToken": csrftoken,
+ },
+ body: JSON.stringify(sponsorData),
+ }).then(response => {
+ this.setState({submitSuccess: true});
+ return response.json();
+ }).catch(error => {
+ console.log(error);
+ })
+ event.preventDefault();
+ }
+
+ addNewSponsorGroup(event) {
+ event.preventDefault();
+ let sponsorDivs = [...this.state.sponsors];
+ sponsorDivs.push(`sponsorGroup${sponsorDivs.length}`);
+ this.setState({sponsors: sponsorDivs });
+ }
+
+ pullFormData() {
+ const requested = [];
+ const sponsors = [...this.state.sponsors];
+ sponsors.forEach((sponsor) => {
+ const parent = document.getElementById(sponsor);
+ const request = {};
+ parent.childNodes.forEach(child => {
+ const select = child.childNodes[1];
+ const { name, value } = select;
+ const sponsorName = value.split(': ')[0];
+ const sponsorId = value.split(': ')[1];
+ request[`${name}Name`] = sponsorName;
+ request[`${name}Id`] = sponsorId;
+ });
+ requested.push({ ...request });
+ });
+ return requested;
+ }
+
+ render() {
+ const { submitSuccess } = this.state;
+ const options = this.props.state_sponsors.map((sponsor) => {
+ const value = `${sponsor.name }: ${sponsor.id}`;
+ return });
+ const sponsorGroups = this.state.sponsors.map((sponsor) => {
+ return
+ });
+
+ return(
+
+ {submitSuccess ? (
+
+ ) : (
+
+ )}
+
+
+ )
+ }
+}
+
+export default DuplicateSponsors;
diff --git a/js/admin/people.js b/js/admin/people.js
index 86416bda..ce3abb10 100644
--- a/js/admin/people.js
+++ b/js/admin/people.js
@@ -2,6 +2,8 @@ import React from "react";
import {addDataHookListener} from "../utils";
import PeopleList from "./people-list";
import NewPersonForm from "./add-person";
+import DuplicateSponsors from "./duplicate-sponsors";
addDataHookListener("people-list", "context", PeopleList);
addDataHookListener("new-person", "context", NewPersonForm);
+addDataHookListener("duplicate-sponsors", "context", DuplicateSponsors);
diff --git a/people_admin/urls.py b/people_admin/urls.py
index 4502f54a..ff1663d0 100644
--- a/people_admin/urls.py
+++ b/people_admin/urls.py
@@ -9,6 +9,8 @@
new_legislator,
apply_new_legislator,
apply_bulk_edits,
+ duplicate_sponsors,
+ apply_duplicate_sponsors,
create_delta_sets,
create_pr,
)
@@ -59,6 +61,16 @@
apply_bulk_edits,
name="apply_bulk_edits",
),
+ re_path(
+ r"^(?P{})/duplicate_sponsors/$".format(state_abbr_pattern),
+ duplicate_sponsors,
+ name="duplicate_sponsors",
+ ),
+ re_path(
+ r"^update/duplicate_sponsors/",
+ apply_duplicate_sponsors,
+ name="apply_duplicate_sponsors",
+ ),
re_path(
r"^(?P{})/deltas/$".format(state_abbr_pattern),
create_delta_sets,
diff --git a/people_admin/views.py b/people_admin/views.py
index 71e0acdb..8e8319ec 100644
--- a/people_admin/views.py
+++ b/people_admin/views.py
@@ -3,6 +3,7 @@
from django.shortcuts import render, get_object_or_404
from django.db.models import Count
from openstates.data.models import LegislativeSession, Person
+
from utils.common import abbr_to_jid, sessions_with_bills, states
from django.views.decorators.http import require_http_methods
from django.views.decorators.cache import never_cache
@@ -248,6 +249,51 @@ def apply_bulk_edits(request):
return JsonResponse({"status": "success"})
+@user_passes_test(lambda u: u.has_perm(EDIT_PERM))
+def duplicate_sponsors(request, state):
+ jid = abbr_to_jid(state)
+ state_sponsors = [
+ person_data(p)
+ for p in Person.objects.filter(
+ current_jurisdiction_id=jid, current_role__isnull=False
+ ).order_by("family_name", "name")
+ ]
+
+ context = {"state": state, "state_sponsors": state_sponsors}
+
+ return render(request, "people_admin/duplicate_sponsors.html", {"context": context})
+
+
+@user_passes_test(lambda u: u.has_perm(EDIT_PERM))
+def apply_duplicate_sponsors(request):
+ form_data = json.load(request)
+
+ delta = DeltaSet.objects.get_or_create(
+ name=f"duplicates by {request.user}",
+ created_by=request.user,
+ )
+
+ for match in form_data:
+ additional_id = [
+ "append",
+ "other_identifiers",
+ {"scheme": "openstates", "identifier": match["secondId"]},
+ ]
+ additional_name = ["append", "other_names", {"name": match["secondName"]}]
+ PersonDelta.objects.create(
+ person_id=match["firstId"],
+ delta_set=delta,
+ data_changes=[additional_id, additional_name],
+ )
+
+ ds = DeltaSet.objects.get(id=delta, pr_status="N")
+ print(f"creating {ds.id} | {ds.name} | {ds.created_by}")
+ ds.pr_url = delta_set_to_pr(ds)
+ ds.pr_status = "C"
+ ds.save()
+ return JsonResponse({"status": "success"})
+
+
@never_cache
@user_passes_test(lambda u: u.has_perm(EDIT_PERM))
def create_delta_sets(request, state):
diff --git a/templates/people_admin/duplicate_sponsors.html b/templates/people_admin/duplicate_sponsors.html
new file mode 100644
index 00000000..545c3206
--- /dev/null
+++ b/templates/people_admin/duplicate_sponsors.html
@@ -0,0 +1,12 @@
+{% extends "public/components/base.html" %}
+{% load static %}
+
+{% block scripts %}
+ {{ context|json_script:"context" }}
+
+
+{% endblock %}
+
+{% block content %}
+
+{% endblock %}
diff --git a/templates/people_admin/people_matcher.html b/templates/people_admin/people_matcher.html
index edb546b5..a455e826 100644
--- a/templates/people_admin/people_matcher.html
+++ b/templates/people_admin/people_matcher.html
@@ -68,6 +68,8 @@ People Matcher
{{ session|default:"All Sessions" }}
Unmatched Total: {{ unmatched_total }}
+ Think you see multiple sponsors available for a match?
+