billdeitrick / pypco

A Python client for the Planning Center Online API.
MIT License
39 stars 13 forks source link

Unable to create a formfield #26

Closed pastorhudson closed 4 years ago

pastorhudson commented 4 years ago

I can create a form. I can create a formfield like phone, but I’m unable to create an option for a formfield. In the json from planning center it is adding the option in the “include”

I’ve tried different ways of creating but no luck. Which should I be using to get ‘include’

Create, update, set, add? I’ve read the code and the docs and am just stuck as to how to do it through pypco.

pastorhudson commented 4 years ago

Here's Code:

pco = pypco.PCO(os.environ.get('PCO_CLIENT_ID'),
                os.environ.get('PCO_CLIENT_SECRET'))
new_form = pco.new(pypco.models.people.Form)
new_form.name = 'Test'
new_form.description = "Test description"
new_form.create()

All good to this point. But the code below doesn't work. and Returns a 422 error

# Add dropdown field
pco_field = pco.new(pypco.models.people.FormField)
pco_field.field_type = "dropdown"
pco_field.label = "Favorite Pie?"
pco_field.description = "Please pick your favorite pie"
pco_field.required = False
pco_field.sequence = 0

new_form.rel.fields.create(pco_field)

I've also tried

new_form.rel.fields.add(pco_field)
pco_field.rel.fields.add(pco_field)
pco_field.rel.fields.create(pco_field)
pco_field.rel.fields.set(pco_field)

I tried:

# Add option
pco_option = pco.new(pypco.models.people.FormFieldOption)
pco_option.label = 'Apple Pie'
pco_option.sequence = 0
pco_field.rel.fields.add(pco_option)

pypco.models.base_model.PCOInvalidModelError: The model is missing the "type" or "id" attribute and cannot be created.

pco_field.rel.fields.create(pco_option)

self._model._data['links']['self'], KeyError: 'links'

pco_field.rel.fields.set(pco_option)

pypco.models.base_model.PCOInvalidModelError: Model not synced with PCO. Create or retrieve your model from PCO, then try again.

I know it's gotta be something I'm missing. I just can't see it. Thanks for all you're doing with this library!

PCOInvalidModelError("The model is missing the \"type\" or \"id\" attribute and cannot be created.")

pastorhudson commented 4 years ago

I should add that this is an issue with a field that needs other pieces. This code works fine for say the phone number field.

print(f"Created new form with id: {new_form.id}")
pco_field = pco.new(pypco.models.people.FormField)
print(f"pco_field.type: {pco_field.type}")
pco_field.field_type = "phone_number"
pco_field.label = "Phone Number"
pco_field.required = False
pco_field.sequence = 0
print(f"pco_field._data: {pco_field._data}")
print(f"new_form._data: {new_form._data}")
new_form.rel.fields.create(pco_field)
pastorhudson commented 4 years ago

This is what the json looks like when planning center creates the dropdown form field.

{
  "data": {
    "type": "FormField",
    "relationships": {
      "form_field_conditions": {
        "data": []
      }
    },
    "attributes": {
      "label": "What is your Favorite Pie?",
      "description": "It's pie time we find out!",
      "required": false,
      "sequence": 0,
      "field_type": "dropdown",
      "settings": {
        "single": false
      }
    }
  },
  "included": [
    {
      "type": "FormFieldOption",
      "id": null,
      "attributes": {
        "label": "Apple Pie",
        "sequence": 0,
        "optionable_id": null,
        "optionable_type": null
      }
    },
    {
      "type": "FormFieldOption",
      "id": null,
      "attributes": {
        "label": "Key Lime Pie",
        "sequence": 1,
        "optionable_id": null,
        "optionable_type": null
      }
    },
    {
      "type": "FormFieldOption",
      "id": null,
      "attributes": {
        "label": "Pecan Pie",
        "sequence": 2,
        "optionable_id": null,
        "optionable_type": null
      }
    },
    {
      "type": "FormFieldOption",
      "id": null,
      "attributes": {
        "label": "Pumpkin Pie",
        "sequence": 3,
        "optionable_id": null,
        "optionable_type": null
      }
    }
  ],
  "include": "options,form_field_conditions"
}
billdeitrick commented 4 years ago

Hey @pastorhudson! I'm having trouble finding form field creation in the API docs. I'm probably missing something obvious...could you post link? I'd like to see what the docs say about this before we go down the wrong rabbit hole. 🙂

pastorhudson commented 4 years ago

Yep 👍

https://developer.planning.center/docs/#/apps/people/2019-10-10/vertices/form_field_option

pastorhudson commented 4 years ago

I'm able to create a FormFieldOption using my api id and secret using this command:

curl -u app_id:secret 'https://api.planningcenteronline.com/people/v2/forms/92550/fields' --data-binary '{"data":{"type":"FormField", "attributes":{"label":"What do you want?","description":"","field_type":"dropdown"}},"included":[{"type":"FormFieldOption","attributes":{"label":"Thing 1"}},{"type":"FormFieldOption","attributes":{"label":"Thing 2","sequence":1}}]}'

This works on a newly created form with no fields.

pastorhudson commented 4 years ago

And a python example that also works fine.

import requests
from requests.auth import HTTPBasicAuth

data_json = """
{
  "data": {
    "type": "FormField",
    "attributes": {
      "label": "What do you want?",
      "description": "",
      "field_type": "dropdown"
    }
  },
  "included": [
    {
      "type": "FormFieldOption",
      "attributes": {
        "label": "Thing 1"
      }
    },
    {
      "type": "FormFieldOption",
      "attributes": {
        "label": "Thing 2"
      }
    }
  ]
}"""

r = requests.post('https://api.planningcenteronline.com/people/v2/forms/92550/fields', data = data_json,
                  auth=HTTPBasicAuth('app_id', 'secret'))
billdeitrick commented 4 years ago

Ah, very helpful--thanks. I had trouble finding a documented POST endpoint for form field options, so I wasn't immediately sure what the request should look like.

Now I think I understand what's going on. Pypco's current release doesn't currently have the ability to create new objects as includes. It can add them to objects that already exist on the PCO API, but it can't create them in the same request that it's using to create the parent object. It wasn't (still isn't?) documented when I wrote the current release, so it wasn't something I knew it needed to support. 🙂

So, the good news here is that pypco v1.0.0 is about ready to drop, which will support this. That bad news is it's completely incompatible with code written for the current pypco release (v0). I'm currently working on docs...keep an eye on the 1.0-dev branch (updated readme will hopefully be committed soon, full docs to follow).

I don't really want to spend a lot of dev time on v0 at this point; with versioning released for the PCO API I don't think the approach I used with v0 is viable moving forward. But, I know you've got a lot of code written against v0, so I'm not opposed to trying to patch this if it would be helpful for you.

Thoughts?

pastorhudson commented 4 years ago

You’re right it’s not technically documented. Though planning center is using these endpoints to add these things. I found out checkboxes work the same way in creating two objects at the same time. I’m glad to hear v1 will support this.

As for patching v0 I don’t think you need to burn the cycles. This is only an issue for pcochef. And since v1 is dropping soon I’ll probably wait to expand it till v1 and then refactor accordingly.

I will post a hack I’ve figured out for v0 Incase anyone needs it then you can close this up.

pastorhudson commented 4 years ago

In case you find this and are stuck on v0 and need to do the include this is a workable solution in a pinch.

import pypco
import os
import json
import requests
from requests.auth import HTTPBasicAuth

pco = pypco.PCO(os.environ.get('PCO_CLIENT_ID'),
                os.environ.get('PCO_CLIENT_SECRET'))

# Instantiate a new form
new_form = pco.new(pypco.models.people.Form)
new_form.name = 'Test'
new_form.description = "Test description"
new_form.active = True
new_form.create()

# Stage the new field with type 'dropdown' or 'checkboxes'
field = pco.new(pypco.models.people.FormField)
field.label = "What Do You Want"
field.description = ""
field.field_type = "dropdown"

# setup a list of options
options = []
# Do this in a for loop to add the options
option = pco.new(pypco.models.people.FormFieldOption)
option.label = "Thing 1"
options.append(option.data)

option1 = pco.new(pypco.models.people.FormFieldOption)
option1.label = "Thing 2"
options.append(option.data)

# build your payload 
payload = {"data": field.data, "included": options}

# Send POST to create the objects
r = requests.post(f'https://api.planningcenteronline.com/people/v2/forms/{new_form.id}/fields', data=json.dumps(payload), auth=HTTPBasicAuth(os.environ.get('PCO_CLIENT_ID'), os.environ.get('PCO_CLIENT_SECRET')))
billdeitrick commented 4 years ago

That’ll work. You could also use dispatch_single_request for this (that function is available in the base endpoint class, so all endpoints have it...you could call pco.people.forms.dispatch_single_request(...)). Then you wouldn’t have to bother with requests and do separate auth or lose pypco’s automatic rate limit handling.

https://github.com/billdeitrick/pypco/blob/da1a7d0b8044c2a3bee0db542381c11430875aeb/pypco/endpoints/base_endpoint.py#L83

Sent with GitHawk

pastorhudson commented 4 years ago

I actually tried that first, but:

pco.people.forms.dispatch_single_request(url=f'https://api.planningcenteronline.com/people/v2/forms/{new_form.id}/fields', payload=json.dumps(payload), method=PCOAPIMethod.POST)

gives a 400 error. I has to be a post request, and I couldn't figure out why it didn't work. So I just resorted to requests.

I just looked at the dev branch. I didn't realize it was there or I'd have built pcochef with it! That's going to break everything in pcobot. But I think it's a good change. It will remove some of the magic of the v0 library. And give me more control. I like that. Thank you so much for building this!