diff --git a/aincrad/tests.py b/aincrad/tests.py
index 1d9f6a38..940e8f11 100644
--- a/aincrad/tests.py
+++ b/aincrad/tests.py
@@ -19,9 +19,9 @@
RegistrationContainerFactory,
StudentFactory,
StudentRegistrationFactory,
- UnitInquiryFactory,
+ UnitPetitionFactory,
)
-from roster.models import ApplyUUID, Invoice, Student, UnitInquiry
+from roster.models import ApplyUUID, Invoice, Student, UnitPetition
EXAMPLE_PASSWORD = "take just the first 24"
TARGET_HASH = sha256(EXAMPLE_PASSWORD.encode("ascii")).hexdigest()
@@ -98,14 +98,14 @@ def aincrad_setup(db):
PSetFactory.create_batch(4, student=old_alice, status="A")
PSetFactory.create_batch(2, student=old_alice, status="P")
- UnitInquiryFactory.create_batch(
- 5, student=alice, action_type="INQ_ACT_UNLOCK", status="INQ_ACC"
+ UnitPetitionFactory.create_batch(
+ 5, student=alice, action_type="PET_ACT_UNLOCK", status="PET_ACC"
)
- UnitInquiryFactory.create_batch(
- 2, student=alice, action_type="INQ_ACT_DROP", status="INQ_ACC"
+ UnitPetitionFactory.create_batch(
+ 2, student=alice, action_type="PET_ACT_DROP", status="PET_ACC"
)
- UnitInquiryFactory.create_batch(
- 3, student=alice, action_type="INQ_ACT_UNLOCK", status="INQ_NEW"
+ UnitPetitionFactory.create_batch(
+ 3, student=alice, action_type="PET_ACT_UNLOCK", status="PET_NEW"
)
alice.curriculum.add(submitted_unit)
@@ -185,9 +185,9 @@ def test_init(otis, aincrad_setup):
else:
pytest.fail("Could not find a pset from Bôb B. in aincrad test")
- inquiries = out["_children"][1]["inquiries"]
- assert len(inquiries) == 3
- assert inquiries[0]["unlock_inquiry_count"] == 8
+ petitions = out["_children"][1]["petitions"]
+ assert len(petitions) == 3
+ assert petitions[0]["unlock_petition_count"] == 8
@pytest.mark.django_db
@@ -293,17 +293,17 @@ def test_invoice(otis, aincrad_setup):
@pytest.mark.django_db
@override_settings(API_TARGET_HASH=TARGET_HASH)
-def test_accept_inquiries(otis, aincrad_setup):
+def test_accept_petitions(otis, aincrad_setup):
resp = otis.post_20x(
"api",
json={
- "action": "accept_inquiries",
+ "action": "accept_petitions",
"token": EXAMPLE_PASSWORD,
},
)
assert resp.json()["result"] == "success"
assert resp.json()["count"] == 3
- assert not UnitInquiry.objects.filter(status="INQ_NEW").exists()
+ assert not UnitPetition.objects.filter(status="PET_NEW").exists()
@pytest.mark.django_db
diff --git a/aincrad/views.py b/aincrad/views.py
index 906ae5b3..5f104823 100644
--- a/aincrad/views.py
+++ b/aincrad/views.py
@@ -33,7 +33,7 @@
Invoice,
Student,
StudentRegistration,
- UnitInquiry,
+ UnitPetition,
)
from suggestions.models import ProblemSuggestion
@@ -145,17 +145,17 @@ class JSONData(TypedDict):
"student__user__profile__email_on_pset_complete",
)
-INQUIRY_VENUEQ_INIT_QUERYSET = UnitInquiry.objects.filter(
- status="INQ_NEW",
+PETITION_VENUEQ_INIT_QUERYSET = UnitPetition.objects.filter(
+ status="PET_NEW",
student__semester__active=True,
student__legit=True,
).annotate(
- unlock_inquiry_count=SubqueryCount(
- "student__unitinquiry",
- filter=Q(action_type="INQ_ACT_UNLOCK"),
+ unlock_petition_count=SubqueryCount(
+ "student__unitpetition",
+ filter=Q(action_type="PET_ACT_UNLOCK"),
),
)
-INQUIRY_VENUEQ_INIT_KEYS = (
+PETITION_VENUEQ_INIT_KEYS = (
"action_type",
"unit__group__name",
"unit__code",
@@ -164,14 +164,14 @@ class JSONData(TypedDict):
"student__user__email",
"explanation",
"created_at",
- "unlock_inquiry_count",
- "student__user__profile__email_on_inquiry_complete",
+ "unlock_petition_count",
+ "student__user__profile__email_on_petition_complete",
)
-INQUIRY_VENUEQ_AUTO_QUERYSET = UnitInquiry.objects.filter(
+PETITION_VENUEQ_AUTO_QUERYSET = UnitPetition.objects.filter(
was_auto_processed=True,
created_at__gte=timezone.now() + timedelta(days=-2),
)
-INQUIRY_VENUEQ_AUTO_KEYS = (
+PETITION_VENUEQ_AUTO_KEYS = (
"action_type",
"unit__group__name",
"unit__code",
@@ -245,12 +245,12 @@ def venueq_handler(action: str, data: JSONData) -> JsonResponse:
),
},
{
- "_name": "Inquiries",
- "inquiries": list(
- INQUIRY_VENUEQ_INIT_QUERYSET.values(*INQUIRY_VENUEQ_INIT_KEYS)
+ "_name": "Petitions",
+ "petitions": list(
+ PETITION_VENUEQ_INIT_QUERYSET.values(*PETITION_VENUEQ_INIT_KEYS)
),
"reading": list(
- INQUIRY_VENUEQ_AUTO_QUERYSET.values(*INQUIRY_VENUEQ_AUTO_KEYS)
+ PETITION_VENUEQ_AUTO_QUERYSET.values(*PETITION_VENUEQ_AUTO_KEYS)
),
},
{
@@ -267,14 +267,14 @@ def venueq_handler(action: str, data: JSONData) -> JsonResponse:
},
]
return JsonResponse(output_data, status=200)
- elif action == "accept_inquiries":
+ elif action == "accept_petitions":
n = 0
- for inquiry in UnitInquiry.objects.filter(
- status="INQ_NEW",
+ for petition in UnitPetition.objects.filter(
+ status="PET_NEW",
student__semester__active=True,
student__legit=True,
):
- inquiry.run_accept()
+ petition.run_accept()
n += 1
if n > 0:
return JsonResponse({"result": "success", "count": n}, status=200)
@@ -814,7 +814,7 @@ def api(request: HttpRequest) -> JsonResponse:
if action in (
"grade_problem_set",
- "accept_inquiries",
+ "accept_petitions",
"mark_suggestion",
"triage_job",
"init",
diff --git a/core/migrations/0073_rename_userprofile_email_on_petition_complete.py b/core/migrations/0073_rename_userprofile_email_on_petition_complete.py
new file mode 100644
index 00000000..56d86a54
--- /dev/null
+++ b/core/migrations/0073_rename_userprofile_email_on_petition_complete.py
@@ -0,0 +1,20 @@
+# Generated by Django 6.0.8 on 2026-08-25 14:32
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ (
+ "core",
+ "0072_rename_first_payment_deadline_semester_half_payment_deadline_and_more",
+ ),
+ ]
+
+ operations = [
+ migrations.RenameField(
+ model_name="userprofile",
+ old_name="email_on_inquiry_complete",
+ new_name="email_on_petition_complete",
+ ),
+ ]
diff --git a/core/models.py b/core/models.py
index 6a17aadb..1836c2cf 100644
--- a/core/models.py
+++ b/core/models.py
@@ -325,7 +325,7 @@ class UserProfile(models.Model):
help_text="Receive all-student announcements. If this is set to False, announcements will only appear on OTIS-WEB.",
default=True,
)
- email_on_inquiry_complete = models.BooleanField(
+ email_on_petition_complete = models.BooleanField(
verbose_name="Receive email on petition processed",
help_text="Receive an email when your petition has been processed.",
default=False,
@@ -357,5 +357,5 @@ def __str__(self) -> str:
"email_on_announcement",
"email_on_pset_complete",
"email_on_suggestion_processed",
- "email_on_inquiry_complete",
+ "email_on_petition_complete",
)
diff --git a/core/views.py b/core/views.py
index cbe6d13e..65372782 100644
--- a/core/views.py
+++ b/core/views.py
@@ -289,7 +289,7 @@ class UserProfileUpdateView(
model = UserProfile
fields = (
"email_on_announcement",
- "email_on_inquiry_complete",
+ "email_on_petition_complete",
"email_on_pset_complete",
"email_on_suggestion_processed",
"show_bars",
diff --git a/dashboard/templates/dashboard/portal.html b/dashboard/templates/dashboard/portal.html
index 42c921cb..7a3d4b91 100644
--- a/dashboard/templates/dashboard/portal.html
+++ b/dashboard/templates/dashboard/portal.html
@@ -337,7 +337,7 @@
Discord
Petitions
{% if request.user.is_staff %}
- Manage units
+ Manage units
Edit units
{% else %}
{% if not student.enabled %}
@@ -347,7 +347,7 @@ Petitions
{% elif student.newborn %}
Pick units first!
{% else %}
- Manage units
+ Manage units
{% if bonus_levels %}
You are also sufficiently high level to
diff --git a/otisweb/templates/sidebar.html b/otisweb/templates/sidebar.html
index bdeed160..0634ca64 100644
--- a/otisweb/templates/sidebar.html
+++ b/otisweb/templates/sidebar.html
@@ -131,7 +131,7 @@ Admin
{% if not student.newborn and semester.active and student.enabled %}
-
- Manage units
+ Manage units
{% endif %}
{% if student.semester.social_url %}
diff --git a/roster/admin.py b/roster/admin.py
index f4174247..928ba872 100644
--- a/roster/admin.py
+++ b/roster/admin.py
@@ -20,7 +20,7 @@
RegistrationContainer,
Student,
StudentRegistration,
- UnitInquiry,
+ UnitPetition,
)
@@ -407,9 +407,9 @@ class StudentRegistrationAdmin(ImportExportModelAdmin):
inlines = (StudentRegistrationStudentInline,)
-# INQUIRY
-@admin.register(UnitInquiry)
-class UnitInquiryAdmin(admin.ModelAdmin):
+# PETITION
+@admin.register(UnitPetition)
+class UnitPetitionAdmin(admin.ModelAdmin):
readonly_fields = (
"created_at",
"updated_at",
@@ -442,22 +442,22 @@ class UnitInquiryAdmin(admin.ModelAdmin):
actions = ("hold_petition", "reject_petition", "accept_petition", "reset_petition")
- def hold_petition(self, request: HttpRequest, queryset: QuerySet[UnitInquiry]):
+ def hold_petition(self, request: HttpRequest, queryset: QuerySet[UnitPetition]):
del request
- queryset.update(status="INQ_HOLD")
+ queryset.update(status="PET_HOLD")
- def reject_petition(self, request: HttpRequest, queryset: QuerySet[UnitInquiry]):
+ def reject_petition(self, request: HttpRequest, queryset: QuerySet[UnitPetition]):
del request
- queryset.update(status="INQ_REJ")
+ queryset.update(status="PET_REJ")
- def accept_petition(self, request: HttpRequest, queryset: QuerySet[UnitInquiry]):
+ def accept_petition(self, request: HttpRequest, queryset: QuerySet[UnitPetition]):
del request
- for inquiry in queryset:
- inquiry.run_accept()
+ for petition in queryset:
+ petition.run_accept()
- def reset_petition(self, request: HttpRequest, queryset: QuerySet[UnitInquiry]):
+ def reset_petition(self, request: HttpRequest, queryset: QuerySet[UnitPetition]):
del request
- queryset.update(status="INQ_NEW")
+ queryset.update(status="PET_NEW")
# REGISTRATION
diff --git a/roster/factories.py b/roster/factories.py
index 2dec1c16..8b75f95d 100644
--- a/roster/factories.py
+++ b/roster/factories.py
@@ -11,7 +11,7 @@
RegistrationContainer,
Student,
StudentRegistration,
- UnitInquiry,
+ UnitPetition,
)
@@ -67,11 +67,11 @@ class Meta:
preps_taught = 2
-class UnitInquiryFactory(DjangoModelFactory):
+class UnitPetitionFactory(DjangoModelFactory):
class Meta:
- model = UnitInquiry
+ model = UnitPetition
student = SubFactory(StudentFactory)
unit = SubFactory(UnitFactory)
- action_type = "INQ_ACT_UNLOCK"
+ action_type = "PET_ACT_UNLOCK"
explanation = Faker("sentence")
diff --git a/roster/forms.py b/roster/forms.py
index d939d92c..2de57452 100644
--- a/roster/forms.py
+++ b/roster/forms.py
@@ -8,7 +8,7 @@
from core.models import EMAIL_PREFERENCE_FIELDS, Semester, Unit
from dashboard.models import PSet
-from roster.models import Student, StudentRegistration, UnitInquiry
+from roster.models import Student, StudentRegistration, UnitPetition
class UnitChoiceBoundField(forms.BoundField):
@@ -126,7 +126,7 @@ def __init__(self, *args: Any, **kwargs: Any):
)
-class InquiryForm(forms.ModelForm):
+class PetitionForm(forms.ModelForm):
def __init__(self, *args: Any, **kwargs: Any):
student: Student = kwargs.pop("student")
super().__init__(*args, **kwargs)
@@ -139,7 +139,7 @@ def __init__(self, *args: Any, **kwargs: Any):
self.fields["unit"].empty_label = "Search for a unit..." # type: ignore
class Meta:
- model = UnitInquiry
+ model = UnitPetition
fields = ("unit", "action_type", "explanation")
widgets: ClassVar[dict[str, forms.Widget]] = {
"explanation": forms.Textarea(attrs={"cols": 40, "rows": 3}),
@@ -176,7 +176,7 @@ class DecisionForm(forms.ModelForm):
help_text="Receive all-student announcements. If this is set to False, announcements will only appear on OTIS-WEB.",
required=False,
)
- email_on_inquiry_complete = forms.BooleanField(
+ email_on_petition_complete = forms.BooleanField(
label="Receive email on petition processed",
help_text="Receive an email when your petition has been processed.",
required=False,
diff --git a/roster/migrations/0119_rename_unitinquiry_unitpetition.py b/roster/migrations/0119_rename_unitinquiry_unitpetition.py
new file mode 100644
index 00000000..96587cdd
--- /dev/null
+++ b/roster/migrations/0119_rename_unitinquiry_unitpetition.py
@@ -0,0 +1,61 @@
+# Renames UnitInquiry -> UnitPetition.
+#
+# RenameModel issues an ALTER TABLE ... RENAME, so every row is carried over
+# untouched; it must never be replaced by a CreateModel/DeleteModel pair (which
+# is what makemigrations autodetects here, since the choices changed at the same
+# time). The choice *values* stored in action_type and status are remapped
+# separately in 0120, which runs immediately after this one.
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ("roster", "0118_applyuuid_applicant_name_applyuuid_memo"),
+ ]
+
+ operations = [
+ migrations.RenameModel(
+ old_name="UnitInquiry",
+ new_name="UnitPetition",
+ ),
+ migrations.AlterField(
+ model_name="unitpetition",
+ name="action_type",
+ field=models.CharField(
+ choices=[
+ ("PET_ACT_UNLOCK", "Unlock now"),
+ ("PET_ACT_APPEND", "Add for later"),
+ ("PET_ACT_DROP", "Drop"),
+ ("PET_ACT_LOCK", "Lock (Drop + Add for later)"),
+ ],
+ help_text="Describe the action you want to make.",
+ max_length=15,
+ ),
+ ),
+ migrations.AlterField(
+ model_name="unitpetition",
+ name="status",
+ field=models.CharField(
+ choices=[
+ ("PET_ACC", "Accepted"),
+ ("PET_REJ", "Rejected"),
+ ("PET_NEW", "Pending"),
+ ("PET_HOLD", "On hold"),
+ ("PET_CANC", "Canceled"),
+ ],
+ default="PET_NEW",
+ help_text="The current status of the petition.",
+ max_length=10,
+ ),
+ ),
+ migrations.AlterField(
+ model_name="unitpetition",
+ name="was_auto_processed",
+ field=models.BooleanField(
+ default=False,
+ help_text="Whether the petition was automatically accepted or rejected by auto-criteria.",
+ verbose_name="Auto",
+ ),
+ ),
+ ]
diff --git a/roster/migrations/0120_unitpetition_choice_values.py b/roster/migrations/0120_unitpetition_choice_values.py
new file mode 100644
index 00000000..6b317ece
--- /dev/null
+++ b/roster/migrations/0120_unitpetition_choice_values.py
@@ -0,0 +1,53 @@
+# Rewrites the stored INQ_* choice values of UnitPetition to PET_*.
+#
+# Both directions are plain UPDATEs over an explicit old <-> new mapping: no row
+# is created or deleted, and a value outside the mapping is left alone rather
+# than being coerced into some default. The two vocabularies are disjoint, so
+# re-running a direction is a no-op.
+
+from django.db import migrations
+
+RENAMES = {
+ "action_type": {
+ "INQ_ACT_UNLOCK": "PET_ACT_UNLOCK",
+ "INQ_ACT_APPEND": "PET_ACT_APPEND",
+ "INQ_ACT_DROP": "PET_ACT_DROP",
+ "INQ_ACT_LOCK": "PET_ACT_LOCK",
+ },
+ "status": {
+ "INQ_ACC": "PET_ACC",
+ "INQ_REJ": "PET_REJ",
+ "INQ_NEW": "PET_NEW",
+ "INQ_HOLD": "PET_HOLD",
+ "INQ_CANC": "PET_CANC",
+ },
+}
+
+
+def remap(apps, reverse: bool):
+ UnitPetition = apps.get_model("roster", "UnitPetition")
+ for field, mapping in RENAMES.items():
+ for old, new in mapping.items():
+ if reverse:
+ old, new = new, old
+ UnitPetition.objects.filter(**{field: old}).update(**{field: new})
+
+
+def forwards(apps, schema_editor):
+ del schema_editor
+ remap(apps, reverse=False)
+
+
+def backwards(apps, schema_editor):
+ del schema_editor
+ remap(apps, reverse=True)
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ("roster", "0119_rename_unitinquiry_unitpetition"),
+ ]
+
+ operations = [
+ migrations.RunPython(forwards, backwards),
+ ]
diff --git a/roster/models.py b/roster/models.py
index aad4b576..6023fdc9 100644
--- a/roster/models.py
+++ b/roster/models.py
@@ -428,7 +428,7 @@ def cleared(self) -> bool:
return self.total_owed <= 0
-class UnitInquiry(models.Model):
+class UnitPetition(models.Model):
unit = models.ForeignKey(
Unit, on_delete=models.CASCADE, help_text="The unit being requested."
)
@@ -441,23 +441,23 @@ class UnitInquiry(models.Model):
action_type = models.CharField(
max_length=15,
choices=(
- ("INQ_ACT_UNLOCK", "Unlock now"),
- ("INQ_ACT_APPEND", "Add for later"),
- ("INQ_ACT_DROP", "Drop"),
- ("INQ_ACT_LOCK", "Lock (Drop + Add for later)"),
+ ("PET_ACT_UNLOCK", "Unlock now"),
+ ("PET_ACT_APPEND", "Add for later"),
+ ("PET_ACT_DROP", "Drop"),
+ ("PET_ACT_LOCK", "Lock (Drop + Add for later)"),
),
help_text="Describe the action you want to make.",
)
status = models.CharField(
max_length=10,
choices=(
- ("INQ_ACC", "Accepted"),
- ("INQ_REJ", "Rejected"),
- ("INQ_NEW", "Pending"),
- ("INQ_HOLD", "On hold"),
- ("INQ_CANC", "Canceled"),
+ ("PET_ACC", "Accepted"),
+ ("PET_REJ", "Rejected"),
+ ("PET_NEW", "Pending"),
+ ("PET_HOLD", "On hold"),
+ ("PET_CANC", "Canceled"),
),
- default="INQ_NEW",
+ default="PET_NEW",
help_text="The current status of the petition.",
)
explanation = models.TextField(
@@ -465,7 +465,7 @@ class UnitInquiry(models.Model):
)
was_auto_processed = models.BooleanField(
default=False,
- help_text="Whether the inquiry was automatically accepted or rejected by auto-criteria.",
+ help_text="Whether the petition was automatically accepted or rejected by auto-criteria.",
verbose_name="Auto",
)
@@ -479,19 +479,19 @@ def __str__(self) -> str:
def run_accept(self):
unit = self.unit
- if self.action_type == "INQ_ACT_UNLOCK":
+ if self.action_type == "PET_ACT_UNLOCK":
self.student.curriculum.add(unit)
self.student.unlocked_units.add(unit)
- elif self.action_type == "INQ_ACT_APPEND":
+ elif self.action_type == "PET_ACT_APPEND":
self.student.curriculum.add(unit)
- elif self.action_type == "INQ_ACT_DROP":
+ elif self.action_type == "PET_ACT_DROP":
self.student.curriculum.remove(unit)
self.student.unlocked_units.remove(unit)
- elif self.action_type == "INQ_ACT_LOCK":
+ elif self.action_type == "PET_ACT_LOCK":
self.student.unlocked_units.remove(unit)
else:
raise ValueError(f"No action {self.action_type}")
- self.status = "INQ_ACC"
+ self.status = "PET_ACC"
self.save()
diff --git a/roster/templates/roster/inquiry.html b/roster/templates/roster/petition.html
similarity index 83%
rename from roster/templates/roster/inquiry.html
rename to roster/templates/roster/petition.html
index 9e21d81c..def465a1 100644
--- a/roster/templates/roster/inquiry.html
+++ b/roster/templates/roster/petition.html
@@ -59,29 +59,29 @@ Current petitions
- {% for inquiry in inquiries %}
+ {% for petition in petitions %}
- | {{ inquiry.created_at|date:"M d" }} |
+ {{ petition.created_at|date:"M d" }} |
- {{ inquiry.unit }}
+ {{ petition.unit }}
|
- {{ inquiry.get_action_type_display }} |
+ {{ petition.get_action_type_display }} |
- {% if inquiry.status == "INQ_NEW" %}
+ {% if petition.status == "PET_NEW" %}
Pending
- {% elif inquiry.status == "INQ_ACC" %}
+ {% elif petition.status == "PET_ACC" %}
Accepted
- {% elif inquiry.status == "INQ_REJ" %}
+ {% elif petition.status == "PET_REJ" %}
Rejected
- {% elif inquiry.status == "INQ_CANC" %}
+ {% elif petition.status == "PET_CANC" %}
Canceled
- {% elif inquiry.status == "INQ_HOLD" %}
+ {% elif petition.status == "PET_HOLD" %}
On hold
{% endif %}
|
- {% if inquiry.status == "INQ_NEW" %}
- |
|
- {{ inquiry.explanation }} |
+ {{ petition.explanation }} |
{% endfor %}
diff --git a/roster/tests.py b/roster/tests.py
index 23927c92..aa62c55f 100644
--- a/roster/tests.py
+++ b/roster/tests.py
@@ -40,7 +40,7 @@
RegistrationContainer,
Student,
StudentRegistration,
- UnitInquiry,
+ UnitPetition,
build_student,
)
@@ -671,7 +671,16 @@ def test_master_schedule(otis) -> None:
@pytest.mark.django_db
-def test_inquiry(otis) -> None:
+def test_legacy_inquiry_url_redirects_to_petition(otis) -> None:
+ alice: Student = StudentFactory.create()
+ otis.login(alice)
+ resp = otis.client.get(f"/roster/inquiry/{alice.pk}/")
+ assert resp.status_code == 302
+ assert resp["Location"] == reverse("petition", args=(alice.pk,))
+
+
+@pytest.mark.django_db
+def test_petition(otis) -> None:
firefly: Assistant = AssistantFactory.create()
alice: Student = StudentFactory.create(assistant=firefly)
# Create units with non-secret subjects to avoid random subject="K" causing flaky tests
@@ -689,11 +698,11 @@ def test_inquiry(otis) -> None:
# Check that an invalid unit is not processed
invalid_resp = otis.post(
- "inquiry",
+ "petition",
alice.pk,
data={
"unit": "invalid",
- "action_type": "INQ_ACT_UNLOCK",
+ "action_type": "PET_ACT_UNLOCK",
"explanation": "hi",
},
)
@@ -705,23 +714,23 @@ def test_inquiry(otis) -> None:
# Alice unlocks 6 units, should be autoprocessed.
for i in range(6):
resp = otis.post(
- "inquiry",
+ "petition",
alice.pk,
data={
"unit": units[i].pk,
- "action_type": "INQ_ACT_UNLOCK",
+ "action_type": "PET_ACT_UNLOCK",
"explanation": "hi",
},
follow=True,
)
otis.assert_message(resp, "Petition automatically processed.")
- inq = UnitInquiry.objects.get(
- student=alice, unit=units[i].pk, action_type="INQ_ACT_UNLOCK"
+ pet = UnitPetition.objects.get(
+ student=alice, unit=units[i].pk, action_type="PET_ACT_UNLOCK"
)
- assert inq.was_auto_processed
- assert inq.status == "INQ_ACC"
+ assert pet.was_auto_processed
+ assert pet.status == "PET_ACC"
- otis.get_20x("inquiry", alice.pk, follow=True)
+ otis.get_20x("petition", alice.pk, follow=True)
# Now Alice has done 6 units, they shouldn't be able to get more
# (This differs from production behavior because production also gives you a default three units)
@@ -729,22 +738,22 @@ def test_inquiry(otis) -> None:
assert alice.unlocked_units.count() == 6
otis.assert_message(
otis.post(
- "inquiry",
+ "petition",
alice.pk,
data={
"unit": units[19].pk,
- "action_type": "INQ_ACT_UNLOCK",
+ "action_type": "PET_ACT_UNLOCK",
"explanation": "hi",
},
follow=True,
),
"Petition submitted, wait for it!",
)
- inq = UnitInquiry.objects.get(
- student=alice, unit=units[19].pk, action_type="INQ_ACT_UNLOCK"
+ pet = UnitPetition.objects.get(
+ student=alice, unit=units[19].pk, action_type="PET_ACT_UNLOCK"
)
- assert not inq.was_auto_processed
- assert inq.status == "INQ_NEW"
+ assert not pet.was_auto_processed
+ assert pet.status == "PET_NEW"
assert alice.curriculum.count() == 6
assert alice.unlocked_units.count() == 6
@@ -753,11 +762,11 @@ def test_inquiry(otis) -> None:
otis.login(firefly)
otis.assert_message(
otis.post(
- "inquiry",
+ "petition",
alice.pk,
data={
"unit": units[3].pk,
- "action_type": "INQ_ACT_LOCK",
+ "action_type": "PET_ACT_LOCK",
"explanation": "hi",
},
follow=True,
@@ -766,33 +775,33 @@ def test_inquiry(otis) -> None:
)
assert alice.curriculum.count() == 6
assert alice.unlocked_units.count() == 5
- inq = UnitInquiry.objects.get(
- student=alice, unit=units[3].pk, action_type="INQ_ACT_LOCK"
+ pet = UnitPetition.objects.get(
+ student=alice, unit=units[3].pk, action_type="PET_ACT_LOCK"
)
- assert inq.was_auto_processed
- assert inq.status == "INQ_ACC"
+ assert pet.was_auto_processed
+ assert pet.status == "PET_ACC"
assert not alice.unlocked_units.contains(units[3])
# Now dropping should be autoprocessed by Alice
otis.login(alice)
otis.assert_message(
otis.post(
- "inquiry",
+ "petition",
alice.pk,
data={
"unit": units[3].pk,
- "action_type": "INQ_ACT_DROP",
+ "action_type": "PET_ACT_DROP",
"explanation": "hi",
},
follow=True,
),
"Petition automatically processed.",
)
- inq = UnitInquiry.objects.get(
- student=alice, unit=units[3].pk, action_type="INQ_ACT_DROP"
+ pet = UnitPetition.objects.get(
+ student=alice, unit=units[3].pk, action_type="PET_ACT_DROP"
)
- assert inq.was_auto_processed
- assert inq.status == "INQ_ACC"
+ assert pet.was_auto_processed
+ assert pet.status == "PET_ACC"
assert alice.curriculum.count() == 5
assert alice.unlocked_units.count() == 5
@@ -803,22 +812,22 @@ def test_inquiry(otis) -> None:
for i in range(6, 10):
otis.assert_message(
otis.post(
- "inquiry",
+ "petition",
alice.pk,
data={
"unit": units[i].pk,
- "action_type": "INQ_ACT_UNLOCK",
+ "action_type": "PET_ACT_UNLOCK",
"explanation": "hi",
},
follow=True,
),
"Petition automatically processed.",
)
- inq = UnitInquiry.objects.get(
- student=alice, unit=units[i].pk, action_type="INQ_ACT_UNLOCK"
+ pet = UnitPetition.objects.get(
+ student=alice, unit=units[i].pk, action_type="PET_ACT_UNLOCK"
)
- assert inq.was_auto_processed
- assert inq.status == "INQ_ACC"
+ assert pet.was_auto_processed
+ assert pet.status == "PET_ACC"
assert alice.curriculum.count() == 9
assert alice.unlocked_units.count() == 9
@@ -827,22 +836,22 @@ def test_inquiry(otis) -> None:
for i in range(11, 14):
otis.assert_message(
otis.post(
- "inquiry",
+ "petition",
alice.pk,
data={
"unit": units[i].pk,
- "action_type": "INQ_ACT_UNLOCK",
+ "action_type": "PET_ACT_UNLOCK",
"explanation": "hi",
},
follow=True,
),
"You can't have more than 9 unfinished units unlocked at once.",
)
- inq = UnitInquiry.objects.get(
- student=alice, unit=units[i].pk, action_type="INQ_ACT_UNLOCK"
+ pet = UnitPetition.objects.get(
+ student=alice, unit=units[i].pk, action_type="PET_ACT_UNLOCK"
)
- assert inq.was_auto_processed
- assert inq.status == "INQ_REJ"
+ assert pet.was_auto_processed
+ assert pet.status == "PET_REJ"
assert alice.curriculum.count() == 9
assert alice.unlocked_units.count() == 9
@@ -850,33 +859,33 @@ def test_inquiry(otis) -> None:
for i in range(15, 18):
otis.assert_message(
otis.post(
- "inquiry",
+ "petition",
alice.pk,
data={
"unit": units[i].pk,
- "action_type": "INQ_ACT_APPEND",
+ "action_type": "PET_ACT_APPEND",
"explanation": "hi",
},
follow=True,
),
"Petition automatically processed.",
)
- inq = UnitInquiry.objects.get(
- student=alice, unit=units[i].pk, action_type="INQ_ACT_APPEND"
+ pet = UnitPetition.objects.get(
+ student=alice, unit=units[i].pk, action_type="PET_ACT_APPEND"
)
- assert inq.was_auto_processed
- assert inq.status == "INQ_ACC"
+ assert pet.was_auto_processed
+ assert pet.status == "PET_ACC"
assert alice.curriculum.count() == 12
assert alice.unlocked_units.count() == 9
# check that petitions are now locked because of abnormally large count
otis.assert_message(
otis.post(
- "inquiry",
+ "petition",
alice.pk,
data={
"unit": units[19].pk,
- "action_type": "INQ_ACT_DROP",
+ "action_type": "PET_ACT_DROP",
"explanation": "hi",
},
follow=True,
@@ -884,44 +893,44 @@ def test_inquiry(otis) -> None:
"You have submitted an abnormally large number of petitions "
"so you should contact Evan specially to explain why.",
)
- inq = UnitInquiry.objects.get(
- student=alice, unit=units[19].pk, action_type="INQ_ACT_DROP"
+ pet = UnitPetition.objects.get(
+ student=alice, unit=units[19].pk, action_type="PET_ACT_DROP"
)
- assert not inq.was_auto_processed
- assert inq.status == "INQ_HOLD"
+ assert not pet.was_auto_processed
+ assert pet.status == "PET_HOLD"
# drop a bunch of units for alice
otis.login(firefly)
for i in range(4, 14):
otis.assert_message(
otis.post(
- "inquiry",
+ "petition",
alice.pk,
data={
"unit": units[i].pk,
- "action_type": "INQ_ACT_DROP",
+ "action_type": "PET_ACT_DROP",
"explanation": "hi",
},
follow=True,
),
"Petition automatically processed.",
)
- inq = UnitInquiry.objects.get(
- student=alice, unit=units[i].pk, action_type="INQ_ACT_DROP"
+ pet = UnitPetition.objects.get(
+ student=alice, unit=units[i].pk, action_type="PET_ACT_DROP"
)
- assert inq.was_auto_processed
- assert inq.status == "INQ_ACC"
+ assert pet.was_auto_processed
+ assert pet.status == "PET_ACC"
assert alice.curriculum.count() == 6
assert alice.unlocked_units.count() == 3
with freeze_time("2025-10-31", tz_offset=0):
otis.assert_message(
otis.post(
- "inquiry",
+ "petition",
alice.pk,
data={
"unit": units[5].pk,
- "action_type": "INQ_ACT_UNLOCK",
+ "action_type": "PET_ACT_UNLOCK",
"explanation": "add back in",
},
follow=True,
@@ -930,14 +939,14 @@ def test_inquiry(otis) -> None:
)
assert alice.curriculum.count() == 7
assert alice.unlocked_units.count() == 4
- inq = UnitInquiry.objects.get(
+ pet = UnitPetition.objects.get(
student=alice,
unit=units[5].pk,
- action_type="INQ_ACT_UNLOCK",
+ action_type="PET_ACT_UNLOCK",
explanation="add back in",
)
- assert inq.was_auto_processed
- assert inq.status == "INQ_ACC"
+ assert pet.was_auto_processed
+ assert pet.status == "PET_ACC"
# Alice hit the hold limit earlier, this just circumvents it.
otis.login(alice)
@@ -953,11 +962,11 @@ def test_inquiry(otis) -> None:
otis.assert_message(
otis.post(
- "inquiry",
+ "petition",
alice.pk,
data={
"unit": secret_unit.pk,
- "action_type": "INQ_ACT_UNLOCK",
+ "action_type": "PET_ACT_UNLOCK",
"explanation": "its almost halloween and my family wants to host it at our house.",
},
follow=True,
@@ -966,24 +975,24 @@ def test_inquiry(otis) -> None:
)
assert alice.curriculum.count() == 8
assert alice.unlocked_units.count() == 5
- inq = UnitInquiry.objects.get(
- student=alice, unit=secret_unit.pk, action_type="INQ_ACT_UNLOCK"
+ pet = UnitPetition.objects.get(
+ student=alice, unit=secret_unit.pk, action_type="PET_ACT_UNLOCK"
)
- assert inq.was_auto_processed
- assert inq.status == "INQ_ACC"
+ assert pet.was_auto_processed
+ assert pet.status == "PET_ACC"
# test a bunch of fail conditions
bob: Student = StudentFactory.create(semester=SemesterFactory.create(active=False))
otis.login(bob)
- otis.get_denied("inquiry", bob.pk)
+ otis.get_denied("petition", bob.pk)
carl: Student = StudentFactory.create(enabled=False)
otis.login(carl)
- otis.get_denied("inquiry", carl.pk)
+ otis.get_denied("petition", carl.pk)
dave: Student = StudentFactory.create(newborn=True)
otis.login(dave)
- otis.get_denied("inquiry", dave.pk)
+ otis.get_denied("petition", dave.pk)
invoice_semester = SemesterFactory.create(
show_invoices=True,
@@ -995,22 +1004,22 @@ def test_inquiry(otis) -> None:
InvoiceFactory.create(student=eve)
with freeze_time("2021-07-30", tz_offset=0):
- otis.get_denied("inquiry", eve.pk)
+ otis.get_denied("petition", eve.pk)
@pytest.mark.django_db
-def test_inquiry_cant_rapid_fire(otis) -> None:
+def test_petition_cant_rapid_fire(otis) -> None:
with freeze_time("2025-10-31", tz_offset=0):
alice = StudentFactory.create()
unit = UnitFactory.create()
otis.login(alice)
otis.assert_message(
otis.post(
- "inquiry",
+ "petition",
alice.pk,
data={
"unit": unit.pk,
- "action_type": "INQ_ACT_UNLOCK",
+ "action_type": "PET_ACT_UNLOCK",
"explanation": "unlock a unit",
},
follow=True,
@@ -1019,11 +1028,11 @@ def test_inquiry_cant_rapid_fire(otis) -> None:
)
otis.assert_message(
otis.post(
- "inquiry",
+ "petition",
alice.pk,
data={
"unit": unit.pk,
- "action_type": "INQ_ACT_UNLOCK",
+ "action_type": "PET_ACT_UNLOCK",
"explanation": "accidentally pressed again because trigger happy",
},
follow=True,
@@ -1033,7 +1042,7 @@ def test_inquiry_cant_rapid_fire(otis) -> None:
@pytest.mark.django_db
-def test_inquiry_cant_rapid_fire_after_auto_reject(otis) -> None:
+def test_petition_cant_rapid_fire_after_auto_reject(otis) -> None:
"""A double submit is caught even when the first copy was auto-rejected."""
with freeze_time("2025-10-31", tz_offset=0):
alice: Student = StudentFactory.create()
@@ -1045,46 +1054,46 @@ def test_inquiry_cant_rapid_fire_after_auto_reject(otis) -> None:
data = {
"unit": unit.pk,
- "action_type": "INQ_ACT_DROP",
+ "action_type": "PET_ACT_DROP",
"explanation": "drop this unit please",
}
otis.assert_message(
- otis.post("inquiry", alice.pk, data=data, follow=True),
+ otis.post("petition", alice.pk, data=data, follow=True),
"You have a pending submission for this unit.",
)
otis.assert_message(
- otis.post("inquiry", alice.pk, data=data, follow=True),
+ otis.post("petition", alice.pk, data=data, follow=True),
"The same petition already was submitted within the last 90 seconds.",
)
assert (
- UnitInquiry.objects.filter(
- student=alice, unit=unit, action_type="INQ_ACT_DROP"
+ UnitPetition.objects.filter(
+ student=alice, unit=unit, action_type="PET_ACT_DROP"
).count()
== 1
)
@pytest.mark.django_db
-def test_inquiry_can_resubmit_after_cancel(otis) -> None:
+def test_petition_can_resubmit_after_cancel(otis) -> None:
"""Canceling a petition lets the student submit the same one right away."""
with freeze_time("2025-10-31", tz_offset=0):
alice: Student = StudentFactory.create()
unit: Unit = UnitFactory.create()
- inquiry = UnitInquiry.objects.create(
+ petition = UnitPetition.objects.create(
student=alice,
unit=unit,
- action_type="INQ_ACT_UNLOCK",
+ action_type="PET_ACT_UNLOCK",
explanation="changed my mind",
- status="INQ_CANC",
+ status="PET_CANC",
)
otis.login(alice)
otis.assert_message(
otis.post(
- "inquiry",
+ "petition",
alice.pk,
data={
"unit": unit.pk,
- "action_type": "INQ_ACT_UNLOCK",
+ "action_type": "PET_ACT_UNLOCK",
"explanation": "actually i do want this one",
},
follow=True,
@@ -1092,15 +1101,15 @@ def test_inquiry_can_resubmit_after_cancel(otis) -> None:
"Petition automatically processed.",
)
assert (
- UnitInquiry.objects.filter(student=alice, unit=unit)
- .exclude(pk=inquiry.pk)
+ UnitPetition.objects.filter(student=alice, unit=unit)
+ .exclude(pk=petition.pk)
.count()
== 1
)
@pytest.mark.django_db
-def test_inquiry_rejects_drop_lock_if_pending_pset(otis) -> None:
+def test_petition_rejects_drop_lock_if_pending_pset(otis) -> None:
"""Drop/lock petition auto-rejected if there's a pending submission."""
alice: Student = StudentFactory.create()
unit: Unit = UnitFactory.create()
@@ -1110,71 +1119,71 @@ def test_inquiry_rejects_drop_lock_if_pending_pset(otis) -> None:
otis.login(alice)
resp = otis.post(
- "inquiry",
+ "petition",
alice.pk,
data={
"unit": unit.pk,
- "action_type": "INQ_ACT_DROP",
+ "action_type": "PET_ACT_DROP",
"explanation": "i'm a cool haxx0r who wants 10 units",
},
follow=True,
)
otis.assert_message(resp, "You have a pending submission for this unit.")
- inq = UnitInquiry.objects.get(student=alice, unit=unit, action_type="INQ_ACT_DROP")
- assert inq.status == "INQ_REJ"
+ pet = UnitPetition.objects.get(student=alice, unit=unit, action_type="PET_ACT_DROP")
+ assert pet.status == "PET_REJ"
resp = otis.post(
- "inquiry",
+ "petition",
alice.pk,
data={
"unit": unit.pk,
- "action_type": "INQ_ACT_LOCK",
+ "action_type": "PET_ACT_LOCK",
"explanation": "o no evan is too smart for me 😂",
},
follow=True,
)
otis.assert_message(resp, "You have a pending submission for this unit.")
- inq = UnitInquiry.objects.get(student=alice, unit=unit, action_type="INQ_ACT_LOCK")
- assert inq.status == "INQ_REJ"
+ pet = UnitPetition.objects.get(student=alice, unit=unit, action_type="PET_ACT_LOCK")
+ assert pet.status == "PET_REJ"
@pytest.mark.django_db
-def test_cancel_inquiry_sets_status_to_canceled(otis) -> None:
+def test_cancel_petition_sets_status_to_canceled(otis) -> None:
alice = StudentFactory.create()
unit = UnitFactory.create()
- inquiry = UnitInquiry.objects.create(
+ petition = UnitPetition.objects.create(
student=alice,
unit=unit,
- action_type="INQ_ACT_UNLOCK",
- status="INQ_NEW",
+ action_type="PET_ACT_UNLOCK",
+ status="PET_NEW",
explanation="Please unlock",
)
otis.login(alice)
resp = otis.post_20x(
- "inquiry-cancel",
- inquiry.pk,
+ "petition-cancel",
+ petition.pk,
follow=True,
)
- inquiry.refresh_from_db()
- assert inquiry.status == "INQ_CANC"
- otis.assert_message(resp, "Inquiry successfully canceled.")
+ petition.refresh_from_db()
+ assert petition.status == "PET_CANC"
+ otis.assert_message(resp, "Petition successfully canceled.")
@pytest.mark.django_db
-def test_cancel_inquiry_rejects_get(otis) -> None:
+def test_cancel_petition_rejects_get(otis) -> None:
alice = StudentFactory.create()
unit = UnitFactory.create()
- inquiry = UnitInquiry.objects.create(
+ petition = UnitPetition.objects.create(
student=alice,
unit=unit,
- action_type="INQ_ACT_UNLOCK",
- status="INQ_NEW",
+ action_type="PET_ACT_UNLOCK",
+ status="PET_NEW",
explanation="Please unlock",
)
otis.login(alice)
# a cross-site navigation is a GET, so canceling can't be reachable that way
- assert otis.get("inquiry-cancel", inquiry.pk).status_code == 405
- inquiry.refresh_from_db()
- assert inquiry.status == "INQ_NEW"
+ assert otis.get("petition-cancel", petition.pk).status_code == 405
+ petition.refresh_from_db()
+ assert petition.status == "PET_NEW"
@pytest.mark.django_db
@@ -1183,58 +1192,58 @@ def test_only_owner_or_staff_can_cancel(otis):
bob = StudentFactory.create()
staff = UserFactory.create(is_staff=True, is_superuser=True)
unit = UnitFactory.create()
- inquiry = UnitInquiry.objects.create(
+ petition = UnitPetition.objects.create(
student=alice,
unit=unit,
- action_type="INQ_ACT_UNLOCK",
- status="INQ_NEW",
+ action_type="PET_ACT_UNLOCK",
+ status="PET_NEW",
explanation="Please unlock",
)
- # Bob cannot cancel Alice's inquiry
+ # Bob cannot cancel Alice's petition
otis.login(bob)
- otis.post_40x("inquiry-cancel", inquiry.pk)
- inquiry.refresh_from_db()
- assert inquiry.status == "INQ_NEW" # Ensure status is still "INQ_NEW"
+ otis.post_40x("petition-cancel", petition.pk)
+ petition.refresh_from_db()
+ assert petition.status == "PET_NEW" # Ensure status is still "PET_NEW"
# Staff can cancel
otis.login(staff)
- otis.post_20x("inquiry-cancel", inquiry.pk, follow=True)
- inquiry.refresh_from_db()
- assert inquiry.status == "INQ_CANC"
+ otis.post_20x("petition-cancel", petition.pk, follow=True)
+ petition.refresh_from_db()
+ assert petition.status == "PET_CANC"
@pytest.mark.django_db
def test_cancel_button_only_for_pending(otis):
- for status in ["INQ_ACC", "INQ_REJ", "INQ_HOLD", "INQ_CANC"]:
+ for status in ["PET_ACC", "PET_REJ", "PET_HOLD", "PET_CANC"]:
alice = StudentFactory.create()
unit = UnitFactory.create()
- UnitInquiry.objects.create(
+ UnitPetition.objects.create(
student=alice,
unit=unit,
- action_type="INQ_ACT_UNLOCK",
+ action_type="PET_ACT_UNLOCK",
status=status,
explanation="Test",
)
otis.login(alice)
- otis.get_20x("inquiry", alice.pk)
+ otis.get_20x("petition", alice.pk)
@pytest.mark.django_db
def test_cannot_cancel_non_pending(otis):
alice = StudentFactory.create()
unit = UnitFactory.create()
- for status in ["INQ_ACC", "INQ_REJ", "INQ_HOLD", "INQ_CANC"]:
- inquiry = UnitInquiry.objects.create(
+ for status in ["PET_ACC", "PET_REJ", "PET_HOLD", "PET_CANC"]:
+ petition = UnitPetition.objects.create(
student=alice,
unit=unit,
- action_type="INQ_ACT_UNLOCK",
+ action_type="PET_ACT_UNLOCK",
status=status,
explanation="Test",
)
otis.login(alice)
- otis.post_40x("inquiry-cancel", inquiry.pk)
- inquiry.refresh_from_db()
- assert inquiry.status == status
+ otis.post_40x("petition-cancel", petition.pk)
+ petition.refresh_from_db()
+ assert petition.status == status
@pytest.mark.django_db
@@ -1611,7 +1620,7 @@ def test_reg(otis) -> None:
"email_on_announcement": False,
"email_on_pset_complete": True,
"email_on_suggestion_processed": False,
- "email_on_inquiry_complete": False,
+ "email_on_petition_complete": False,
},
follow=True,
)
@@ -1642,7 +1651,7 @@ def test_reg(otis) -> None:
"email_on_announcement": False,
"email_on_pset_complete": True,
"email_on_suggestion_processed": False,
- "email_on_inquiry_complete": False,
+ "email_on_petition_complete": False,
},
follow=True,
)
@@ -1662,7 +1671,7 @@ def test_reg(otis) -> None:
assert not profile.email_on_announcement
assert profile.email_on_pset_complete
assert not profile.email_on_suggestion_processed
- assert not profile.email_on_inquiry_complete
+ assert not profile.email_on_petition_complete
resp = otis.post_20x(
"register",
@@ -1681,7 +1690,7 @@ def test_reg(otis) -> None:
"email_on_announcement": False,
"email_on_pset_complete": True,
"email_on_suggestion_processed": False,
- "email_on_inquiry_complete": False,
+ "email_on_petition_complete": False,
},
follow=True,
)
@@ -1755,7 +1764,7 @@ def test_reg_with_apply_uuid(otis) -> None:
"email_on_announcement": False,
"email_on_pset_complete": True,
"email_on_suggestion_processed": False,
- "email_on_inquiry_complete": False,
+ "email_on_petition_complete": False,
},
follow=True,
)
@@ -1786,7 +1795,7 @@ def test_reg_with_apply_uuid(otis) -> None:
"email_on_announcement": False,
"email_on_pset_complete": True,
"email_on_suggestion_processed": False,
- "email_on_inquiry_complete": False,
+ "email_on_petition_complete": False,
},
follow=True,
)
@@ -1835,7 +1844,7 @@ def test_reg_with_apply_uuid(otis) -> None:
"email_on_announcement": False,
"email_on_pset_complete": True,
"email_on_suggestion_processed": False,
- "email_on_inquiry_complete": False,
+ "email_on_petition_complete": False,
},
follow=True,
)
@@ -1861,7 +1870,7 @@ def test_reg_with_apply_uuid(otis) -> None:
"email_on_announcement": False,
"email_on_pset_complete": True,
"email_on_suggestion_processed": False,
- "email_on_inquiry_complete": False,
+ "email_on_petition_complete": False,
},
follow=True,
)
@@ -1902,7 +1911,7 @@ def test_reg_with_disabled_apply_uuid(otis) -> None:
"email_on_announcement": False,
"email_on_pset_complete": True,
"email_on_suggestion_processed": False,
- "email_on_inquiry_complete": False,
+ "email_on_petition_complete": False,
},
follow=True,
)
@@ -2173,7 +2182,7 @@ def test_registration_is_all_or_nothing(otis) -> None:
"email_on_announcement": False,
"email_on_pset_complete": True,
"email_on_suggestion_processed": False,
- "email_on_inquiry_complete": False,
+ "email_on_petition_complete": False,
},
)
diff --git a/roster/urls.py b/roster/urls.py
index cc5319c2..832fca5c 100644
--- a/roster/urls.py
+++ b/roster/urls.py
@@ -1,5 +1,6 @@
from django.shortcuts import redirect
from django.urls import path
+from django.views.generic.base import RedirectView
from . import views
@@ -11,8 +12,14 @@
path(r"invoice//", views.invoice, name="invoice"),
path(r"master-schedule/", views.master_schedule, name="master-schedule"),
path(r"edit-invoice//", views.UpdateInvoice.as_view(), name="edit-invoice"),
- path(r"inquiry//", views.inquiry, name="inquiry"),
- path(r"inquiry/cancel//", views.cancel_inquiry, name="inquiry-cancel"),
+ path(r"petition//", views.petition, name="petition"),
+ path(r"petition/cancel//", views.cancel_petition, name="petition-cancel"),
+ # "inquiry" was the old internal name for a unit petition; keep the URL
+ # students may have bookmarked pointing at the renamed view.
+ path(
+ r"inquiry//",
+ RedirectView.as_view(pattern_name="petition"),
+ ),
path(r"register/", views.register, name="register"),
path(r"profile/", views.update_profile, name="update-profile"),
path(r"giga-chart//", views.giga_chart, name="giga-chart"),
diff --git a/roster/views.py b/roster/views.py
index 40d259c7..1d542a10 100644
--- a/roster/views.py
+++ b/roster/views.py
@@ -67,7 +67,7 @@
AdvanceForm,
CurriculumForm,
DecisionForm,
- InquiryForm,
+ PetitionForm,
UserForm,
UserLookupForm,
UserMergeForm,
@@ -77,7 +77,7 @@
RegistrationContainer,
Student,
StudentRegistration,
- UnitInquiry,
+ UnitPetition,
build_student,
)
@@ -274,21 +274,21 @@ def get_success_url(self):
return reverse("invoice", args=(self.object.student.pk,))
-def handle_inquiry(request: AuthHttpRequest, inquiry: UnitInquiry, student: Student):
- current_inquiries = UnitInquiry.objects.filter(student=student)
- inquiry.student = student
+def handle_petition(request: AuthHttpRequest, petition: UnitPetition, student: Student):
+ current_petitions = UnitPetition.objects.filter(student=student)
+ petition.student = student
# check if exists already and created recently.
- # every status counts here except INQ_CANC: canceling a petition is the one
+ # every status counts here except PET_CANC: canceling a petition is the one
# action that should let a student immediately submit the same one again.
# in particular a double submit whose first copy was auto-rejected or put
# on hold still needs to be caught.
if (
- current_inquiries.filter(
- unit=inquiry.unit,
- action_type=inquiry.action_type,
+ current_petitions.filter(
+ unit=petition.unit,
+ action_type=petition.action_type,
created_at__gte=timezone.now() - datetime.timedelta(seconds=90),
)
- .exclude(status="INQ_CANC")
+ .exclude(status="PET_CANC")
.exists()
):
messages.warning(
@@ -297,63 +297,63 @@ def handle_inquiry(request: AuthHttpRequest, inquiry: UnitInquiry, student: Stud
)
return
- inquiry.save()
+ petition.save()
# early auto accept criteria
- if inquiry.action_type == "INQ_ACT_APPEND" or request.user.is_staff:
- inquiry.run_accept()
- inquiry.was_auto_processed = True
- inquiry.save()
+ if petition.action_type == "PET_ACT_APPEND" or request.user.is_staff:
+ petition.run_accept()
+ petition.was_auto_processed = True
+ petition.save()
messages.success(request, "Petition automatically processed.")
return
- past_unlock_inquiries = current_inquiries.filter(action_type="INQ_ACT_UNLOCK")
+ past_unlock_petitions = current_petitions.filter(action_type="PET_ACT_UNLOCK")
- num_past_unlock_inquiries = past_unlock_inquiries.count()
+ num_past_unlock_petitions = past_unlock_petitions.count()
unlocked_count = (
- past_unlock_inquiries.filter(status="INQ_NEW").count()
+ past_unlock_petitions.filter(status="PET_NEW").count()
+ student.unlocked_units.count()
)
auto_reject_checks = [
(
- inquiry.action_type == "INQ_ACT_UNLOCK" and unlocked_count > 9,
+ petition.action_type == "PET_ACT_UNLOCK" and unlocked_count > 9,
"You can't have more than 9 unfinished units unlocked at once.",
),
(
- inquiry.action_type in ("INQ_ACT_DROP", "INQ_ACT_LOCK")
+ petition.action_type in ("PET_ACT_DROP", "PET_ACT_LOCK")
and PSet.objects.filter(
- student=student, unit=inquiry.unit, status__in=("P", "PA", "PR")
+ student=student, unit=petition.unit, status__in=("P", "PA", "PR")
),
"You have a pending submission for this unit.",
),
(
- inquiry.action_type == "INQ_ACT_LOCK"
- and PSet.objects.filter(student=student, unit=inquiry.unit, status="A"),
+ petition.action_type == "PET_ACT_LOCK"
+ and PSet.objects.filter(student=student, unit=petition.unit, status="A"),
"You can't lock units with accepted submissions.",
),
]
for condition, message in auto_reject_checks:
if condition:
- inquiry.status = "INQ_REJ"
- inquiry.was_auto_processed = True
- inquiry.save()
+ petition.status = "PET_REJ"
+ petition.was_auto_processed = True
+ petition.save()
messages.error(request, message=message)
return
# auto hold criteria
num_psets = PSet.objects.filter(student=student).count()
- auto_hold_criteria = num_past_unlock_inquiries > (10 + 2.5 * num_psets**1.2)
+ auto_hold_criteria = num_past_unlock_petitions > (10 + 2.5 * num_psets**1.2)
if auto_hold_criteria:
- inquiry.status = "INQ_HOLD"
- inquiry.save()
+ petition.status = "PET_HOLD"
+ petition.save()
logger.log(
SUCCESS_LOG_LEVEL,
- f"Held {student}'s petition to {inquiry.action_type} {inquiry.unit} "
- f"({num_psets} psets and {num_past_unlock_inquiries} unlock petitions).",
+ f"Held {student}'s petition to {petition.action_type} {petition.unit} "
+ f"({num_psets} psets and {num_past_unlock_petitions} unlock petitions).",
extra={"request": request},
)
messages.warning(
@@ -363,33 +363,33 @@ def handle_inquiry(request: AuthHttpRequest, inquiry: UnitInquiry, student: Stud
)
return
- unit = inquiry.unit
+ unit = petition.unit
# auto accepting criteria for unlocking
- if inquiry.action_type == "INQ_ACT_UNLOCK" and unlocked_count <= 9:
+ if petition.action_type == "PET_ACT_UNLOCK" and unlocked_count <= 9:
# when less than 6 past unlock (newbie) or a secret unit (currently uses subject to determine this)
auto_accept_criteria = (
- num_past_unlock_inquiries <= 6 or unit.group.subject == "K"
+ num_past_unlock_petitions <= 6 or unit.group.subject == "K"
)
- elif inquiry.action_type == "INQ_ACT_DROP":
+ elif petition.action_type == "PET_ACT_DROP":
# auto dropping locked units
auto_accept_criteria = not student.unlocked_units.contains(unit)
else:
auto_accept_criteria = False
if auto_accept_criteria:
- inquiry.run_accept()
- inquiry.was_auto_processed = True
- inquiry.save()
+ petition.run_accept()
+ petition.was_auto_processed = True
+ petition.save()
messages.success(request, "Petition automatically processed.")
return
messages.success(request, "Petition submitted, wait for it!")
-# Inquiry views
+# Petition views
@login_required
-def inquiry(request: AuthHttpRequest, student_pk: int) -> HttpResponse:
+def petition(request: AuthHttpRequest, student_pk: int) -> HttpResponse:
student = get_student_by_pk(request, student_pk)
if not request.user.is_staff:
if not student.semester.active:
@@ -406,37 +406,37 @@ def inquiry(request: AuthHttpRequest, student_pk: int) -> HttpResponse:
)
context: dict[str, Any] = {}
- # Create form for submitting new inquiries
+ # Create form for submitting new petitions
if request.method == "POST":
- form = InquiryForm(request.POST, student=student)
+ form = PetitionForm(request.POST, student=student)
if form.is_valid():
- inquiry: UnitInquiry = form.save(commit=False)
- handle_inquiry(request, inquiry, student)
- return HttpResponseRedirect(reverse("inquiry", args=(student.pk,)))
+ petition: UnitPetition = form.save(commit=False)
+ handle_petition(request, petition, student)
+ return HttpResponseRedirect(reverse("petition", args=(student.pk,)))
else:
- form = InquiryForm(student=student)
+ form = PetitionForm(student=student)
context["form"] = form
- context["inquiries"] = UnitInquiry.objects.filter(student=student)
+ context["petitions"] = UnitPetition.objects.filter(student=student)
context["student"] = student
context["curriculum"] = student.generate_curriculum_rows()
- return render(request, "roster/inquiry.html", context)
+ return render(request, "roster/petition.html", context)
@login_required
@require_POST
-def cancel_inquiry(request: AuthHttpRequest, pk: int) -> HttpResponse:
- inquiry = get_object_or_404(UnitInquiry, pk=pk)
- if inquiry.student.user != request.user and not request.user.is_staff:
- raise PermissionDenied("You are not authorized to cancel this inquiry.")
- if inquiry.status != "INQ_NEW":
+def cancel_petition(request: AuthHttpRequest, pk: int) -> HttpResponse:
+ petition = get_object_or_404(UnitPetition, pk=pk)
+ if petition.student.user != request.user and not request.user.is_staff:
+ raise PermissionDenied("You are not authorized to cancel this petition.")
+ if petition.status != "PET_NEW":
raise PermissionDenied
- inquiry.status = "INQ_CANC"
- inquiry.save()
- messages.success(request, "Inquiry successfully canceled.")
- return HttpResponseRedirect(reverse("inquiry", args=(inquiry.student.pk,)))
+ petition.status = "PET_CANC"
+ petition.save()
+ messages.success(request, "Petition successfully canceled.")
+ return HttpResponseRedirect(reverse("petition", args=(petition.student.pk,)))
@login_required
@@ -775,7 +775,7 @@ def post(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse:
@staff_required
def link_assistant(request: HttpRequest) -> HttpResponse:
assistant = get_object_or_404(Assistant, user=request.user)
- # Create form for submitting new inquiries
+ # Create form for submitting new petitions
if request.method == "POST":
form = LinkAssistantForm(request.POST)
if form.is_valid():
|