Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create User Invitation API #35575

Open
wants to merge 40 commits into
base: master
Choose a base branch
from
Open

Conversation

Jtang-1
Copy link
Contributor

@Jtang-1 Jtang-1 commented Jan 3, 2025

Product Description

Adds the POST endpoint https://www.commcarehq.org/a/[domain]/api/invitation/v1/ to create web user invitation.

Technical Summary

USH-5183
Brief tech spec doc

The supported parameters are: email, role, primary_location, assigned_locations, profile, custom_user_data, tableau_role, tableau_groups.

In this PR, I also made some update to the validation in preparation for it needing to support both creation and updates. None and an empty string "" can be treated the same on creation. But when doing an update, caution needs to be done to handle None (where no update is done to that parameter) and an empty string "" (where the parameter is being set to None) differently. On update, most of the time, validation for the parameter can be skipped if it is not updated but validation needs to be done if it is being set to "".

Feature Flag

no feature flag

Safety Assurance

Safety story

Tested locally. This is a completely new endpoint so does not affecting any existing features.

Automated test coverage

There exists tests for API input parameters. This PR adds a test that the endpoint successfully creates and Invitation.

QA Plan

no QA

Rollback instructions

  • This PR can be reverted after deploy with no further considerations

Labels & Review

  • Risk label is set correctly
  • The set of people pinged as reviewers is appropriate for the level of risk of the change

There is a slight difference in how bulk upload and the API works. For bulk upload, the existing
user information is typically included. So if the field is required, then the existing value is expected
to be included in the bulk upload. If the field is either empty ("''") or does not exist (None), validation
will failed as "the field is required" for bulk upload. However for the API, if it is a PUT and the field is not
included (None), then that should not fail the required validation.
…ocation is changed. Separate this out from User Access
…e to be explicit that primary location is one of the assigned locations
… function call so can not be cached by only the domain
@dimagimon dimagimon added Risk: High Change affects files that have been flagged as high risk. Risk: Medium Change affects files that have been flagged as medium risk. labels Jan 3, 2025
@Jtang-1 Jtang-1 force-pushed the jt/create_user_invitation_api branch from f6346e7 to dcf7e2d Compare January 3, 2025 09:09
@Jtang-1 Jtang-1 requested review from a team, MartinRiese, AddisonDunn and Robert-Costello and removed request for a team January 3, 2025 09:11
@Jtang-1 Jtang-1 marked this pull request as ready for review January 3, 2025 09:15
@Jtang-1 Jtang-1 requested a review from stephherbers as a code owner January 3, 2025 09:15
Copy link
Contributor

@MartinRiese MartinRiese left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks pretty good to me. But I don't have much django api experience, so I would recommend a second pair of eyes.

@Jtang-1 Jtang-1 requested a review from esoergel January 7, 2025 17:51
@Jtang-1
Copy link
Contributor Author

Jtang-1 commented Jan 10, 2025

Been making a bunch of changes... but I should be done now. Bumping for review

Copy link
Contributor

@Robert-Costello Robert-Costello left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left a couple of comments. Looks good to me otherwise.

corehq/apps/api/resources/v1_0.py Outdated Show resolved Hide resolved
corehq/apps/api/resources/v1_0.py Outdated Show resolved Hide resolved
corehq/apps/api/resources/v1_0.py Show resolved Hide resolved
corehq/apps/api/validation.py Show resolved Hide resolved
corehq/apps/api/resources/v1_0.py Show resolved Hide resolved
def obj_create(self, bundle, **kwargs):
domain = kwargs['domain']
validator = WebUserResourceValidator(domain, bundle.request.couch_user)
spec = WebUserSpec(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To avoid enumerating the fields again here, I think you can just do this:

spec = WebUserSpec(**bundle.data)

That will throw an exception if any unrecognized fields are included (I'm not sure whether tastypie cleans or prevents unrecognized fields). You could also do something like this if you do need explicit handling:

allowed_fields = [field.name for field in dataclasses.fields(WebUserSpec)]
spec = WebUserSpec(**{k: v for k, v in bundle.data.items() if k in allowed_fields})
has_unrecognized = any(k not in allowed_fields for k in bundle.data)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It makes sense to use the unpacking operator here since the WebUserSpec key matches the TastyPie field names. But I purposely introduced the WebUserSpec to decouple the key names used for validation and the TastyPie field names. This came in handy in the followup PR here

Although I still need to directly reference the Tastypie field names when doing validate_parameters...

corehq/apps/api/resources/v1_0.py Outdated Show resolved Hide resolved
corehq/apps/api/resources/v1_0.py Outdated Show resolved Hide resolved
Comment on lines +131 to +136
assigned_locs = SQLLocation.active_objects.filter(
location_id__in=spec.assigned_location_ids, domain=domain)
real_ids = [loc.location_id for loc in assigned_locs]

if missing_ids := set(spec.assigned_location_ids) - set(real_ids):
raise ImmediateHttpResponse(JsonResponse(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could the location validation stuff go in the validator class?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes it can, I decided to put it here to avoid having to fetch the location objects again. But maybe that extra fetch isn't a big deal, would definitely be cleaner to do the validation inside the validator class

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I definitely do support minimizing lookups, especially for an API, which can easily get spammed heavily. The validator code already looks up locations, though I think it's pretty far deep - not sure how straightforward it'd be to get access to it - it's inside validate_assigned_locations_allow_users

corehq/apps/api/validation.py Show resolved Hide resolved
1. avoid pulling whole objects from DB and fetch just location ids
2. primary_location location id can be accessed directly via primary_location_id
3. change "errors" to "error" since "errors" is used elsewhere as a list
@dimagimon dimagimon added the reindex/migration Reindex or migration will be required during or before deploy label Jan 16, 2025
@Jtang-1 Jtang-1 removed the reindex/migration Reindex or migration will be required during or before deploy label Jan 16, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Risk: High Change affects files that have been flagged as high risk. Risk: Medium Change affects files that have been flagged as medium risk.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants