-
Notifications
You must be signed in to change notification settings - Fork 3.9k
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
feat: add python apis to agreements models #32967
Changes from 10 commits
2c1a596
ed5a1e2
98ef1e9
add1fbe
7715f23
03adda8
ac5e15f
421e1b5
81afd41
92ad9b3
0c784f6
bf846f5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -9,6 +9,11 @@ | |||||
from opaque_keys.edx.keys import CourseKey | ||||||
|
||||||
from openedx.core.djangoapps.agreements.models import IntegritySignature | ||||||
from openedx.core.djangoapps.agreements.models import LTIPIITool | ||||||
from openedx.core.djangoapps.agreements.models import LTIPIISignature | ||||||
|
||||||
from .data import LTIToolsReceivingPIIData | ||||||
from .data import LTIPIISignatureData | ||||||
|
||||||
log = logging.getLogger(__name__) | ||||||
User = get_user_model() | ||||||
|
@@ -68,3 +73,179 @@ def get_integrity_signatures_for_course(course_id): | |||||
""" | ||||||
course_key = CourseKey.from_string(course_id) | ||||||
return IntegritySignature.objects.filter(course_key=course_key) | ||||||
|
||||||
|
||||||
def create_lti_pii_signature(username, course_id, lti_tools): | ||||||
""" | ||||||
Creates an lti pii tool signature. If the signature already exist, do not create a new one. | ||||||
|
||||||
Arguments: | ||||||
* course_key (str) | ||||||
* lti_tools (dict) | ||||||
* lti_tools_hash (int) | ||||||
Returns: | ||||||
* An LTIPIISignature, or None if a signature already exists. | ||||||
""" | ||||||
course_key = CourseKey.from_string(course_id) | ||||||
lti_tools_hash = hash(str(lti_tools)) | ||||||
|
||||||
# if user and course exists, update, otherwise create a new signature | ||||||
try: | ||||||
user = User.objects.get(username=username) | ||||||
LTIPIISignature.objects.get(user=user, course_key=course_key) | ||||||
except User.DoesNotExist: | ||||||
return None | ||||||
except LTIPIISignature.DoesNotExist: | ||||||
signature = LTIPIISignature.objects.create( | ||||||
user=user, | ||||||
course_key=course_key, | ||||||
lti_tools=lti_tools, | ||||||
lti_tools_hash=lti_tools_hash) | ||||||
else: | ||||||
signature = LTIPIISignature.objects.update( | ||||||
user=user, | ||||||
course_key=course_key, | ||||||
lti_tools=lti_tools, | ||||||
lti_tools_hash=lti_tools_hash) | ||||||
|
||||||
return signature | ||||||
|
||||||
|
||||||
def get_lti_pii_signature(username, course_id): | ||||||
""" | ||||||
Get the lti pii signature of a user in a course. | ||||||
|
||||||
Arguments: | ||||||
* username (str) | ||||||
* course_id (str) | ||||||
|
||||||
Returns: | ||||||
* An LTIPIISignature object, or None if one does not exist for the | ||||||
user + course combination. | ||||||
""" | ||||||
course_key = CourseKey.from_string(course_id) | ||||||
try: | ||||||
user = User.objects.get(username=username) | ||||||
signature = LTIPIISignature.objects.get(user=user, course_key=course_key) | ||||||
except (User.DoesNotExist, LTIPIISignature.DoesNotExist): | ||||||
return None | ||||||
else: | ||||||
return LTIPIISignatureData(user=signature.user, course_id=str(signature.course_key), | ||||||
lti_tools=signature.lti_tools, lti_tools_hash=signature.lti_tools_hash) | ||||||
|
||||||
|
||||||
def get_pii_receiving_lti_tools(course_id): | ||||||
""" | ||||||
Get a course's LTI tools that share PII. | ||||||
|
||||||
Arguments: | ||||||
* course_id (str) | ||||||
|
||||||
Returns: | ||||||
* A List of LTI tools sharing PII. | ||||||
""" | ||||||
|
||||||
course_key = CourseKey.from_string(course_id) | ||||||
try: | ||||||
course_ltipiitools = LTIPIITool.objects.get(course_key=course_key).lti_tools | ||||||
except LTIPIITool.DoesNotExist: | ||||||
return None | ||||||
|
||||||
return LTIToolsReceivingPIIData(lii_tools_receiving_pii=course_ltipiitools,) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit.
Suggested change
|
||||||
|
||||||
|
||||||
def user_lti_pii_signature_needed(username, course_id): | ||||||
""" | ||||||
Determines if a user needs to acknowledge the LTI PII Agreement | ||||||
|
||||||
Arguments: | ||||||
* username (str) | ||||||
|
||||||
Returns: | ||||||
* True if the user needs to sign a new acknowledgement. | ||||||
* False if the acknowledgements are up to date. | ||||||
""" | ||||||
course_has_lti_pii_tools = _course_has_lti_pii_tools(course_id) | ||||||
signature_exists = _user_lti_pii_signature_exists(username, course_id) | ||||||
signature_out_of_date = _user_signature_out_of_date(username, course_id) | ||||||
|
||||||
if (course_has_lti_pii_tools and (not signature_exists) or | ||||||
(course_has_lti_pii_tools and signature_exists and signature_out_of_date)): | ||||||
# write a new signature, or update an existing signature | ||||||
return True | ||||||
else: | ||||||
# course does not have lti pii tools, or signatue is up to date | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit.
Suggested change
|
||||||
return False | ||||||
|
||||||
|
||||||
def _course_has_lti_pii_tools(course_id): | ||||||
""" | ||||||
Determines if a specifc course has lti tools sharing pii | ||||||
|
||||||
Arguments: | ||||||
* course_id (str) | ||||||
|
||||||
Returns: | ||||||
* True if the course does have a list. | ||||||
* False if the course does not. | ||||||
""" | ||||||
course_key = CourseKey.from_string(course_id) | ||||||
try: | ||||||
LTIPIITool.objects.get(course_key=course_key) | ||||||
except LTIPIITool.DoesNotExist: | ||||||
# no entry in the database | ||||||
return False | ||||||
else: | ||||||
return True | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The only potential issue here is in the case that a course is in the database with no tools. This could happen if a course has tools that receive PII and then those tools are configured not to share PII. Because we don't yet know how we're going to handle that (i.e. remove that course's row from the database or just have the list of tools be empty and keep the row), I think it would be safest to check for both the |
||||||
|
||||||
|
||||||
def _user_lti_pii_signature_exists(username, course_id): | ||||||
""" | ||||||
Determines if a user's lti pii signature exists for a specfic course | ||||||
|
||||||
Arguments: | ||||||
* username (str) | ||||||
* course_id (str) | ||||||
|
||||||
Returns: | ||||||
* True if user has a signature for the given course. | ||||||
* False if the user does not have a signature for the given course. | ||||||
""" | ||||||
course_key = CourseKey.from_string(course_id) | ||||||
|
||||||
try: | ||||||
user = User.objects.get(username=username) | ||||||
LTIPIISignature.objects.get(user=user, course_key=course_key) | ||||||
except (User.DoesNotExist, LTIPIISignature.DoesNotExist): | ||||||
return False | ||||||
else: | ||||||
return True # signature exist | ||||||
|
||||||
|
||||||
def _user_signature_out_of_date(username, course_id): | ||||||
""" | ||||||
Determines if a user's existing lti pii signature is out-of-date for a given course. | ||||||
|
||||||
Arguments: | ||||||
* username (str) | ||||||
* course_id (str) | ||||||
|
||||||
Returns: | ||||||
* True if user needs a new signature | ||||||
* False if the user has an up-to-date signature. | ||||||
""" | ||||||
course_key = CourseKey.from_string(course_id) | ||||||
|
||||||
try: | ||||||
user = User.objects.get(username=username) | ||||||
user_lti_pii_signature_hash = LTIPIISignature.objects.get(course_key=course_key, user=user).lti_tools_hash | ||||||
course_lti_pii_tools_hash = LTIPIITool.objects.get(course_key=course_key).lti_tools_hash | ||||||
except (User.DoesNotExist, LTIPIISignature.DoesNotExist, LTIPIITool.DoesNotExist): | ||||||
return False | ||||||
else: | ||||||
if user_lti_pii_signature_hash == course_lti_pii_tools_hash: | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit. You can shorten this a bit, if you want. Because |
||||||
# Hashes are equal, therefore update is not need | ||||||
return False | ||||||
else: | ||||||
# Out of date signature, so update is needed | ||||||
return True |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
""" | ||
Public data structures for this app. | ||
""" | ||
import attr | ||
|
||
|
||
@attr.s(frozen=True, auto_attribs=True) | ||
class LTIToolsReceivingPIIData: | ||
""" | ||
Class that stores data about the list of LTI tools sharing PII | ||
""" | ||
lii_tools_receiving_pii: {} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Once we get to the modulestore stage of the project, we may want to change this a bit to give more expected structure to each value in the dictionary, but I think this is fine as it is currently. |
||
|
||
|
||
@attr.s(frozen=True, auto_attribs=True) | ||
class LTIPIISignatureData: | ||
""" | ||
Class that stores an lti pii signature | ||
""" | ||
user: str | ||
course_id: str | ||
lti_tools: str | ||
lti_tools_hash: str |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -119,3 +119,26 @@ def post(self, request, course_id): | |
signature = create_integrity_signature(username, course_id) | ||
serializer = IntegritySignatureSerializer(signature) | ||
return Response(serializer.data) | ||
|
||
|
||
class LTIPIISignatureView(AuthenticatedAPIView): | ||
""" | ||
Endpoint for an PII sharing LTI Signature | ||
/integrity_signature/{course_id} | ||
|
||
HTTP POST | ||
* If an integrity signature does not exist for the user + course, creates one and | ||
returns it. If one does exist, returns the existing signature. | ||
""" | ||
|
||
def post(self, request, course_id): | ||
""" | ||
Create an LTI PII signature for the requesting user and course. If a signature | ||
already exists, returns the existing signature instead of creating a new one. | ||
|
||
/api/agreements/v1/lti_pii_signature/{course_id} | ||
""" | ||
username = request.user.username | ||
#signature = create_lti_pii_signature(username, course_id, lti_tools) | ||
#serializer = IntegritySignatureSerializer(signature) | ||
#return Response(serializer.data) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What's the future for these commented out bits? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think they will be replaced with a serializer specifically for the LTIPIISignature There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since this isn't ready yet and will be added a later point, what do you think about removing this from this pull request? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using the
ObjectDoesNotExist
exception in atry...catch
block will "will catch DoesNotExist exceptions for all models". This could be a problem because we might catch exceptions for other models that we don't expect, and then the exception is buried and may not be handled appropriately. I think it would be better to catch the exception that you anticipate and want to handle, which isLTIPIISignature.DoesNotExist
. You can see the documentation for this exception here.I see this
ObjectDoesNotExist
class in a few places in this file.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ohh, I was wondering how to throw a
DoesNotExist
exception for specific models. I just resorted toObjectDoesNotExist
. I will correct the rest of them as well.