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

CONCD-824 #2480

Merged
merged 2 commits into from
Aug 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 55 additions & 37 deletions concordia/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import pytesseract
from django.conf import settings
from django.contrib.auth.models import BaseUserManager, User
from django.contrib.auth.models import User
from django.core import signing
from django.core.cache import cache
from django.core.exceptions import ValidationError
Expand Down Expand Up @@ -40,28 +40,6 @@ def resource_file_upload_path(instance, filename):
return time.strftime(path)


class ConcordiaUserManager(BaseUserManager):
def review_incidents(self):
user_incident_count = []

for user in self.get_queryset().filter(is_superuser=False, is_staff=False):
incident_count = user.review_incidents()
if incident_count > 0:
user_incident_count.append((user.id, user.username, incident_count))

return user_incident_count

def transcribe_incidents(self):
user_incident_count = []

for user in self.get_queryset().filter(is_superuser=False, is_staff=False):
incident_count = user.transcribe_incidents()
if incident_count > 0:
user_incident_count.append((user.id, user.username, incident_count))

return user_incident_count


class ConcordiaUser(User):
# This class is a simple proxy model to add
# additional user functionality to, without changing
Expand Down Expand Up @@ -96,14 +74,14 @@ def get_email_reconfirmation_key(self):
def validate_reconfirmation_email(self, email):
return email == self.get_email_for_reconfirmation()

def review_incidents(self, start=ONE_DAY_AGO, threshold=THRESHOLD):
recent_accepts = Transcription.objects.filter(
accepted__gte=start, reviewed_by=self
).values_list("accepted", flat=True)
recent_rejects = Transcription.objects.filter(
rejected__gte=start, reviewed_by=self
).values_list("rejected", flat=True)
timestamps = list(recent_accepts) + list(recent_rejects)
def review_incidents(self, recent_accepts, recent_rejects, threshold=THRESHOLD):
accepts = recent_accepts.filter(reviewed_by=self).values_list(
"accepted", flat=True
)
rejects = recent_rejects.filter(reviewed_by=self).values_list(
"rejected", flat=True
)
timestamps = list(accepts) + list(rejects)
timestamps.sort()
incidents = 0
for i in range(len(timestamps)):
Expand All @@ -118,10 +96,8 @@ def review_incidents(self, start=ONE_DAY_AGO, threshold=THRESHOLD):
break
return incidents

def transcribe_incidents(self, start=ONE_DAY_AGO, threshold=THRESHOLD):
recent_transcriptions = Transcription.objects.filter(
submitted__gte=start, user=self
).order_by("submitted")
def transcribe_incidents(self, transcriptions, threshold=THRESHOLD):
recent_transcriptions = transcriptions.filter(user=self).order_by("submitted")
timestamps = recent_transcriptions.values_list("submitted", flat=True)
incidents = 0
for i in range(len(timestamps)):
Expand All @@ -136,8 +112,6 @@ def transcribe_incidents(self, start=ONE_DAY_AGO, threshold=THRESHOLD):
break
return incidents

objects = ConcordiaUserManager()


class UserProfile(MetricsModelMixin("userprofile"), models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="profile")
Expand Down Expand Up @@ -864,6 +838,50 @@ def recent_review_actions(self, days=1):
START = timezone.now() - datetime.timedelta(days=days)
return self.review_actions(START)

def review_incidents(self):
user_incident_count = []
recent_accepts = self.filter(
accepted__gte=ONE_DAY_AGO,
reviewed_by__is_superuser=False,
reviewed_by__is_staff=False,
)
recent_rejects = self.filter(
rejected__gte=ONE_DAY_AGO,
reviewed_by__is_superuser=False,
reviewed_by__is_staff=False,
)
recent_actions = recent_accepts.union(recent_rejects)
user_ids = set(
recent_actions.order_by("reviewed_by").values_list("reviewed_by", flat=True)
)

for user_id in user_ids:
user = ConcordiaUser.objects.get(id=user_id)
incident_count = user.review_incidents(recent_accepts, recent_rejects)
if incident_count > 0:
user_incident_count.append((user.id, user.username, incident_count))

return user_incident_count

def transcribe_incidents(self):
user_incident_count = []
transcriptions = self.get_queryset().filter(
submitted__gte=ONE_DAY_AGO, user__is_superuser=False, user__is_staff=False
)
user_ids = (
transcriptions.order_by("user")
.distinct("user")
.values_list("user", flat=True)
)

for user_id in user_ids:
user = ConcordiaUser.objects.get(id=user_id)
incident_count = user.transcribe_incidents(transcriptions)
if incident_count > 0:
user_incident_count.append((user.id, user.username, incident_count))

return user_incident_count


class Transcription(MetricsModelMixin("transcription"), models.Model):
asset = models.ForeignKey(Asset, on_delete=models.CASCADE)
Expand Down
5 changes: 2 additions & 3 deletions concordia/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
AssetTranscriptionReservation,
Campaign,
CampaignRetirementProgress,
ConcordiaUser,
Item,
Project,
ResourceFile,
Expand Down Expand Up @@ -1060,8 +1059,8 @@ def unusual_activity():
"title": "Unusual User Activity Report for "
+ timezone.now().strftime("%b %d %Y, %I:%M %p"),
"domain": "https://" + site.domain,
"transcriptions": ConcordiaUser.objects.transcribe_incidents(),
"reviews": ConcordiaUser.objects.review_incidents(),
"transcriptions": Transcription.objects.transcribe_incidents(),
"reviews": Transcription.objects.review_incidents(),
}

text_body_template = loader.get_template("emails/unusual_activity.txt")
Expand Down
2 changes: 1 addition & 1 deletion concordia/templates/emails/unusual_activity.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Incidents of two or more transcriptions submitted within a single minute:
No transcriptions fell within the window.
{% endfor %}
Incidents of two or more transcriptions reviewed within a single minute:
{% for row in reviews_by %}
{% for row in reviews %}
{{ row.1 }} | {{ row.2 }}
{% empty %}
No reviews fell within the window.
Expand Down
154 changes: 71 additions & 83 deletions concordia/tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
AssetTranscriptionReservation,
Campaign,
CardFamily,
ConcordiaUser,
Resource,
Transcription,
TranscriptionStatus,
Expand Down Expand Up @@ -41,7 +40,54 @@
)


class ConcordiaUserTestCase(CreateTestUsers, TestCase):
class AssetTestCase(CreateTestUsers, TestCase):
def setUp(self):
self.asset = create_asset()
anon = get_anonymous_user()
create_transcription(asset=self.asset, user=anon)
create_transcription(
asset=self.asset,
user=self.create_test_user(username="tester"),
reviewed_by=anon,
)

def test_get_ocr_transcript(self):
self.asset.storage_image = "tests/test-european.jpg"
self.asset.save()
phrase = "marrón rápido salta sobre el perro"
self.assertFalse(phrase in self.asset.get_ocr_transcript())
self.assertFalse(
phrase in self.asset.get_ocr_transcript(language="bad-language-code")
)
self.assertTrue(phrase in self.asset.get_ocr_transcript(language="spa"))

def test_get_contributor_count(self):
self.assertEqual(self.asset.get_contributor_count(), 2)

def test_turn_off_ocr(self):
self.assertFalse(self.asset.turn_off_ocr())
self.asset.disable_ocr = True
self.asset.save()
self.assertTrue(self.asset.turn_off_ocr())

self.assertFalse(self.asset.item.turn_off_ocr())
self.asset.item.disable_ocr = True
self.asset.item.save()
self.assertTrue(self.asset.item.turn_off_ocr())

self.assertFalse(self.asset.item.project.turn_off_ocr())
self.asset.item.project.disable_ocr = True
self.asset.item.project.save()
self.assertTrue(self.asset.item.project.turn_off_ocr())

def test_get_storage_path(self):
self.assertEqual(
self.asset.get_storage_path(filename=self.asset.storage_image),
"test-campaign/test-project/testitem.0123456789/1.jpg",
)


class TranscriptionManagerTestCase(CreateTestUsers, TestCase):
def setUp(self):
self.transcription1 = create_transcription(
user=self.create_user(username="tester1"),
Expand All @@ -51,6 +97,23 @@ def setUp(self):
asset=self.transcription1.asset, user=get_anonymous_user()
)

def test_recent_review_actions(self):
transcriptions = Transcription.objects
self.assertEqual(transcriptions.recent_review_actions().count(), 0)

self.transcription1.accepted = timezone.now()
self.transcription1.save()
self.assertEqual(transcriptions.recent_review_actions().count(), 1)

self.transcription2.rejected = timezone.now()
self.transcription2.save()
self.assertEqual(transcriptions.recent_review_actions().count(), 2)

def test_review_actions(self):
start = timezone.now() - timedelta(days=5)
end = timezone.now() - timedelta(days=1)
self.assertEqual(Transcription.objects.review_actions(start, end).count(), 1)

def test_review_incidents(self):
self.transcription1.accepted = timezone.now()
self.transcription1.reviewed_by = self.create_user(username="tester2")
Expand All @@ -60,7 +123,7 @@ def test_review_incidents(self):
)
self.transcription2.reviewed_by = self.transcription1.reviewed_by
self.transcription2.save()
users = ConcordiaUser.objects.review_incidents()
users = Transcription.objects.review_incidents()
self.assertNotIn(self.transcription1.user.id, users)

transcription3 = create_transcription(
Expand All @@ -75,7 +138,7 @@ def test_review_incidents(self):
reviewed_by=self.transcription1.reviewed_by,
accepted=transcription3.accepted + timedelta(minutes=1, seconds=1),
)
users = ConcordiaUser.objects.review_incidents()
users = Transcription.objects.review_incidents()
self.assertEqual(len(users), 1)
self.assertEqual(
users[0],
Expand All @@ -98,7 +161,7 @@ def test_review_incidents(self):
reviewed_by=self.transcription1.reviewed_by,
accepted=transcription4.accepted + timedelta(seconds=58),
)
users = ConcordiaUser.objects.review_incidents()
users = Transcription.objects.review_incidents()
self.assertEqual(len(users), 1)
self.assertEqual(
users[0],
Expand All @@ -117,7 +180,7 @@ def test_transcribe_incidents(self):
)
self.transcription2.user = self.transcription1.user
self.transcription2.save()
users = ConcordiaUser.objects.transcribe_incidents()
users = Transcription.objects.transcribe_incidents()
self.assertEqual(len(users), 0)
self.assertNotIn(self.transcription1.user.id, users)

Expand All @@ -136,7 +199,7 @@ def test_transcribe_incidents(self):
user=self.transcription1.user,
submitted=transcription4.submitted + timedelta(seconds=59),
)
users = ConcordiaUser.objects.transcribe_incidents()
users = Transcription.objects.transcribe_incidents()
self.assertEqual(len(users), 1)
self.assertEqual(
users[0],
Expand All @@ -148,89 +211,14 @@ def test_transcribe_incidents(self):
user=self.transcription1.user,
submitted=self.transcription1.submitted + timedelta(minutes=1, seconds=59),
)
users = ConcordiaUser.objects.transcribe_incidents()
users = Transcription.objects.transcribe_incidents()
self.assertEqual(len(users), 1)
self.assertEqual(
users[0],
(self.transcription1.user.id, self.transcription1.user.username, 2),
)


class AssetTestCase(CreateTestUsers, TestCase):
def setUp(self):
self.asset = create_asset()
anon = get_anonymous_user()
create_transcription(asset=self.asset, user=anon)
create_transcription(
asset=self.asset,
user=self.create_test_user(username="tester"),
reviewed_by=anon,
)

def test_get_ocr_transcript(self):
self.asset.storage_image = "tests/test-european.jpg"
self.asset.save()
phrase = "marrón rápido salta sobre el perro"
self.assertFalse(phrase in self.asset.get_ocr_transcript())
self.assertFalse(
phrase in self.asset.get_ocr_transcript(language="bad-language-code")
)
self.assertTrue(phrase in self.asset.get_ocr_transcript(language="spa"))

def test_get_contributor_count(self):
self.assertEqual(self.asset.get_contributor_count(), 2)

def test_turn_off_ocr(self):
self.assertFalse(self.asset.turn_off_ocr())
self.asset.disable_ocr = True
self.asset.save()
self.assertTrue(self.asset.turn_off_ocr())

self.assertFalse(self.asset.item.turn_off_ocr())
self.asset.item.disable_ocr = True
self.asset.item.save()
self.assertTrue(self.asset.item.turn_off_ocr())

self.assertFalse(self.asset.item.project.turn_off_ocr())
self.asset.item.project.disable_ocr = True
self.asset.item.project.save()
self.assertTrue(self.asset.item.project.turn_off_ocr())

def test_get_storage_path(self):
self.assertEqual(
self.asset.get_storage_path(filename=self.asset.storage_image),
"test-campaign/test-project/testitem.0123456789/1.jpg",
)


class TranscriptionManagerTestCase(CreateTestUsers, TestCase):
def setUp(self):
self.transcription1 = create_transcription(
user=self.create_user(username="tester1"),
rejected=timezone.now() - timedelta(days=2),
)
self.transcription2 = create_transcription(
asset=self.transcription1.asset, user=get_anonymous_user()
)

def test_recent_review_actions(self):
transcriptions = Transcription.objects
self.assertEqual(transcriptions.recent_review_actions().count(), 0)

self.transcription1.accepted = timezone.now()
self.transcription1.save()
self.assertEqual(transcriptions.recent_review_actions().count(), 1)

self.transcription2.rejected = timezone.now()
self.transcription2.save()
self.assertEqual(transcriptions.recent_review_actions().count(), 2)

def test_review_actions(self):
start = timezone.now() - timedelta(days=5)
end = timezone.now() - timedelta(days=1)
self.assertEqual(Transcription.objects.review_actions(start, end).count(), 1)


class TranscriptionTestCase(CreateTestUsers, TestCase):
def setUp(self):
self.user = self.create_user("test-user-1")
Expand Down