Skip to content

Commit 74fde62

Browse files
fix: fixes based on review feedback, tests etc
Co-authored-by: Farhaan Bukhsh <[email protected]>
1 parent 5176a83 commit 74fde62

11 files changed

Lines changed: 161 additions & 103 deletions

File tree

openedx/core/djangoapps/agreements/admin.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,14 @@
33
"""
44

55
from django.contrib import admin
6-
from openedx.core.djangoapps.agreements.models import IntegritySignature, UserAgreement
7-
from openedx.core.djangoapps.agreements.models import LTIPIITool
8-
from openedx.core.djangoapps.agreements.models import LTIPIISignature
9-
from openedx.core.djangoapps.agreements.models import ProctoringPIISignature
6+
7+
from openedx.core.djangoapps.agreements.models import (
8+
IntegritySignature,
9+
LTIPIISignature,
10+
LTIPIITool,
11+
ProctoringPIISignature,
12+
UserAgreement
13+
)
1014

1115

1216
class IntegritySignatureAdmin(admin.ModelAdmin):

openedx/core/djangoapps/agreements/api.py

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,23 @@
44

55
import logging
66
from datetime import datetime
7-
from typing import Iterable, Optional
7+
from typing import Optional, Iterator
88

99
from django.contrib.auth import get_user_model
1010
from django.core.exceptions import ObjectDoesNotExist
1111
from opaque_keys.edx.keys import CourseKey
1212

13-
from .data import LTIPIISignatureData, LTIToolsReceivingPIIData, UserAgreementRecordData
14-
from .models import IntegritySignature, LTIPIISignature, LTIPIITool, UserAgreementRecord
13+
from openedx.core.djangoapps.agreements.data import (
14+
LTIPIISignatureData,
15+
LTIToolsReceivingPIIData,
16+
UserAgreementRecordData
17+
)
18+
from openedx.core.djangoapps.agreements.models import (
19+
IntegritySignature,
20+
LTIPIISignature,
21+
LTIPIITool,
22+
UserAgreementRecord, UserAgreement
23+
)
1524

1625
log = logging.getLogger(__name__)
1726
User = get_user_model()
@@ -240,11 +249,11 @@ def _user_signature_out_of_date(username, course_id):
240249
return user_lti_pii_signature_hash != course_lti_pii_tools_hash
241250

242251

243-
def get_user_agreement_records(user: User) -> Iterable[UserAgreementRecordData]:
252+
def get_user_agreement_records(user: User) -> Iterator[UserAgreementRecordData]:
244253
"""
245254
Retrieves all the agreements that the specified user has acknowledged.
246255
"""
247-
for agreement_record in UserAgreementRecord.objects.filter(user=user).select_related("agreement"):
256+
for agreement_record in UserAgreementRecord.objects.filter(user=user).select_related("agreement", "user"):
248257
yield UserAgreementRecordData.from_model(agreement_record)
249258

250259

@@ -258,25 +267,26 @@ def get_latest_user_agreement_record(
258267
An agreement update timestamp can be provided to return a record only if it
259268
was signed after that timestamp.
260269
"""
261-
try:
262-
record_query = UserAgreementRecord.objects.filter(
263-
user=user,
264-
agreement__type=agreement_type,
265-
)
266-
record = record_query.latest("timestamp")
267-
return UserAgreementRecordData.from_model(record)
268-
except UserAgreementRecord.DoesNotExist:
269-
return None
270+
record_query = UserAgreementRecord.objects.filter(
271+
user=user,
272+
agreement__type=agreement_type,
273+
)
274+
if record_query.exists():
275+
return UserAgreementRecordData.from_model(record_query.latest("timestamp"))
276+
return UserAgreementRecordData(
277+
username=user.get_username(),
278+
agreement_type=agreement_type,
279+
)
270280

271281

272282
def create_user_agreement_record(user: User, agreement_type: str) -> UserAgreementRecordData:
273283
"""
274-
Creates a user agreement record if one doesn't already exist, or updates existing
275-
record to current timestamp.
284+
Creates a user agreement record with current timestamp.
276285
"""
286+
agreement = UserAgreement.objects.get(type=agreement_type)
277287
record = UserAgreementRecord.objects.create(
278288
user=user,
279-
agreement__type=agreement_type,
289+
agreement=agreement,
280290
timestamp=datetime.now(),
281291
)
282292
return UserAgreementRecordData.from_model(record)

openedx/core/djangoapps/agreements/data.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
import attr
88

9-
from .models import UserAgreementRecord, UserAgreement
9+
from openedx.core.djangoapps.agreements.models import UserAgreement, UserAgreementRecord
1010

1111

1212
@attr.s(frozen=True, auto_attribs=True)
@@ -28,7 +28,6 @@ class LTIPIISignatureData:
2828
lti_tools_hash: str
2929

3030

31-
3231
@dataclass
3332
class UserAgreementData:
3433
"""
@@ -37,28 +36,29 @@ class UserAgreementData:
3736
type: str
3837
name: str
3938
summary: str
40-
text: str|None
41-
url: str|None
39+
has_text: bool
40+
url: str | None
4241

4342
@classmethod
4443
def from_model(cls, model: UserAgreement):
4544
return UserAgreementData(
4645
type=model.type,
4746
name=model.name,
4847
summary=model.summary,
49-
text=model.text,
50-
url=model.url
48+
url=model.url,
49+
has_text=bool(model.text),
5150
)
5251

52+
5353
@dataclass
5454
class UserAgreementRecordData:
5555
"""
5656
Data for a single user agreement record.
5757
"""
5858
username: str
5959
agreement_type: str
60-
accepted_at: datetime
61-
is_current: bool = True
60+
accepted_at: datetime | None = None
61+
is_current: bool = False
6262

6363
@classmethod
6464
def from_model(cls, model: UserAgreementRecord):

openedx/core/djangoapps/agreements/models.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,15 +75,15 @@ class Meta:
7575

7676
class UserAgreement(models.Model):
7777
"""
78-
This model stores agreements that as user can accept that can gate certain
78+
This model stores agreements that the user can accept, which can gate certain
7979
platform features.
8080
8181
.. no_pii:
8282
"""
8383
type = models.CharField(max_length=255, unique=True)
8484
name = models.CharField(
8585
max_length=255,
86-
help_text='Human-readable name for the agreement type. Will be displayed to users in alert to accept the agreement.',
86+
help_text='Human-readable name for the agreement type. Will be displayed to users in an alert to accept/reject the agreement.',
8787
)
8888
summary = models.TextField(
8989
max_length=1024,

openedx/core/djangoapps/agreements/serializers.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,9 @@
33
"""
44
from rest_framework import serializers
55

6+
from openedx.core.djangoapps.agreements.models import IntegritySignature, LTIPIISignature
67
from openedx.core.lib.api.serializers import CourseKeyField
78

8-
from .models import IntegritySignature, LTIPIISignature
9-
109

1110
class IntegritySignatureSerializer(serializers.ModelSerializer):
1211
"""
@@ -41,7 +40,7 @@ class UserAgreementSerializer(serializers.Serializer):
4140
type = serializers.CharField(read_only=True)
4241
name = serializers.CharField(read_only=True)
4342
summary = serializers.CharField(read_only=True)
44-
text = serializers.CharField(read_only=True)
43+
has_text = serializers.BooleanField(read_only=True)
4544
url = serializers.URLField(read_only=True)
4645
updated = serializers.DateTimeField(read_only=True)
4746

@@ -53,3 +52,4 @@ class UserAgreementRecordSerializer(serializers.Serializer):
5352
username = serializers.CharField(read_only=True)
5453
agreement_type = serializers.CharField(read_only=True)
5554
accepted_at = serializers.DateTimeField()
55+
is_current = serializers.BooleanField(read_only=True)

openedx/core/djangoapps/agreements/tests/test_api.py

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,21 @@
99
from testfixtures import LogCapture
1010

1111
from common.djangoapps.student.tests.factories import UserFactory
12-
from openedx.core.djangolib.testing.utils import skip_unless_lms
13-
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
14-
from xmodule.modulestore.tests.factories import CourseFactory
15-
16-
from ..api import (
12+
from openedx.core.djangoapps.agreements.api import (
1713
create_integrity_signature,
1814
create_lti_pii_signature,
1915
create_user_agreement_record,
2016
get_integrity_signature,
2117
get_integrity_signatures_for_course,
18+
get_latest_user_agreement_record,
2219
get_lti_pii_signature,
2320
get_pii_receiving_lti_tools,
24-
get_latest_user_agreement_record,
2521
get_user_agreement_records
2622
)
27-
from ..models import LTIPIITool
23+
from openedx.core.djangoapps.agreements.models import LTIPIITool, UserAgreement
24+
from openedx.core.djangolib.testing.utils import skip_unless_lms
25+
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
26+
from xmodule.modulestore.tests.factories import CourseFactory
2827

2928
LOGGER_NAME = "openedx.core.djangoapps.agreements.api"
3029

@@ -199,6 +198,14 @@ class UserAgreementsTests(TestCase):
199198
"""
200199
def setUp(self):
201200
self.user = UserFactory()
201+
self.agreement = UserAgreement.objects.create(
202+
type='test_type',
203+
name='test agreement',
204+
summary='test summary',
205+
url='https://example.com',
206+
text='test text',
207+
updated=datetime.now(),
208+
)
202209

203210
def test_get_user_agreements(self, ):
204211
result = list(get_user_agreement_records(self.user))
@@ -218,9 +225,13 @@ def test_get_user_agreement_record(self):
218225

219226
assert result == record
220227

221-
result = get_latest_user_agreement_record(self.user, 'test_type', datetime.now() + timedelta(days=1))
228+
self.agreement.updated = datetime.now() + timedelta(days=1)
229+
self.agreement.save()
230+
231+
result = get_latest_user_agreement_record(self.user, 'test_type')
222232

223-
assert result is None
233+
assert result.is_current is False
224234

225235
def tearDown(self):
226236
self.user.delete()
237+
self.agreement.delete()

openedx/core/djangoapps/agreements/tests/test_models.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
"""
22
Tests for Agreements models
33
"""
4+
from datetime import datetime
45

56
from django.db import IntegrityError
67
from django.test import TestCase
8+
79
from openedx.core.djangoapps.agreements.models import UserAgreement
810

911

@@ -21,7 +23,8 @@ def test_agreement_must_have_text_or_url(self):
2123
name='Name 1',
2224
summary='Summary 1',
2325
text='Some text',
24-
url='https://example.com'
26+
url='https://example.com',
27+
updated=datetime.now(),
2528
)
2629
self.assertIsNotNone(agreement.pk)
2730

@@ -31,7 +34,8 @@ def test_agreement_must_have_text_or_url(self):
3134
name='Name 2',
3235
summary='Summary 2',
3336
text='Some text',
34-
url=None
37+
url=None,
38+
updated=datetime.now(),
3539
)
3640
self.assertIsNotNone(agreement.pk)
3741

@@ -41,7 +45,8 @@ def test_agreement_must_have_text_or_url(self):
4145
name='Name 3',
4246
summary='Summary 3',
4347
text=None,
44-
url='https://example.com'
48+
url='https://example.com',
49+
updated=datetime.now(),
4550
)
4651
self.assertIsNotNone(agreement.pk)
4752

@@ -52,7 +57,8 @@ def test_agreement_must_have_text_or_url(self):
5257
name='Name 4',
5358
summary='Summary 4',
5459
text=None,
55-
url=None
60+
url=None,
61+
updated=datetime.now(),
5662
)
5763

5864
def test_agreement_with_empty_strings(self):
@@ -67,7 +73,8 @@ def test_agreement_with_empty_strings(self):
6773
name='Name 5',
6874
summary='Summary 5',
6975
text='',
70-
url=None
76+
url=None,
77+
updated=datetime.now(),
7178
)
7279
self.assertIsNotNone(agreement.pk)
7380

@@ -77,6 +84,7 @@ def test_agreement_with_empty_strings(self):
7784
name='Name 6',
7885
summary='Summary 6',
7986
text=None,
80-
url=''
87+
url='',
88+
updated=datetime.now(),
8189
)
8290
self.assertIsNotNone(agreement.pk)

0 commit comments

Comments
 (0)