diff --git a/src/gened/profile.py b/src/gened/profile.py index f5d53b3..bdef131 100644 --- a/src/gened/profile.py +++ b/src/gened/profile.py @@ -60,7 +60,21 @@ def main() -> str: ORDER BY classes.id DESC """, [user_id, cur_class_id]).fetchall() - return render_template("profile_view.html", user=user, other_classes=other_classes, archived_classes=archived_classes) + # Get any classes created by this user + created_classes = db.execute(""" + SELECT classes.name + FROM classes_user + JOIN classes ON classes_user.class_id = classes.id + WHERE creator_user_id = ? + """, [user_id]).fetchall() + + return render_template( + "profile_view.html", + user=user, + other_classes=other_classes, + archived_classes=archived_classes, + created_classes=created_classes + ) @bp.route("/delete_data", methods=['POST']) @@ -71,10 +85,23 @@ def delete_data() -> Response: flash("Data deletion requires confirmation. Please type DELETE to confirm.", "warning") return safe_redirect(request.referrer, default_endpoint="profile.main") - db = get_db() auth = get_auth() user_id = auth.user_id assert user_id is not None # due to @login_required + db = get_db() + + # Check if user has any classes they created + created_classes = db.execute(""" + SELECT classes.name + FROM classes_user + JOIN classes ON classes_user.class_id = classes.id + WHERE creator_user_id = ? + """, [user_id]).fetchall() + + if created_classes: + class_names = ", ".join(row['name'] for row in created_classes) + flash(f"You must delete all classes you created before deleting your data. Please delete these classes first: {class_names}", "danger") + return safe_redirect(request.referrer, default_endpoint="profile.main") # Call application-specific data deletion handler(s) delete_user_data(user_id) diff --git a/src/gened/templates/profile_view.html b/src/gened/templates/profile_view.html index 35917b0..35053bc 100644 --- a/src/gened/templates/profile_view.html +++ b/src/gened/templates/profile_view.html @@ -130,22 +130,45 @@

Delete Your Data

{% if auth.user.auth_provider == 'lti' %} -

This user was created using a link from an LMS. If you follow that link after deleting your data here, you will create a new user on CodeHelp separate from this one.

+

This user was created using a link from an LMS. If you connect to CodeHelp from your LMS after deleting your data here, you will create a new user on CodeHelp separate from this one.

{% endif %}

Type "DELETE" in the text box to confirm you want to delete your data.

Warning: Deleting your data cannot be undone.

-
-
- + {% if created_classes %} +
+
+ +
+
+ +
-
- +
+

Classes cannot be orphaned.

+

Before you can delete your data, you must first delete all classes you have created:

+
    + {% for class in created_classes %} +
  • {{ class.name }}
  • + {% endfor %} +
+

This must be done manually, because it may require deleting other user's data.

-
+ {% else %} +
+
+ +
+
+ +
+
+ {% endif %}
diff --git a/tests/test_data.sql b/tests/test_data.sql index 79b5ece..617b0ee 100644 --- a/tests/test_data.sql +++ b/tests/test_data.sql @@ -74,7 +74,9 @@ VALUES -- testuser, testadmin, and testinstructor are in class 2 -- but not testuser2 (4, 11, 2, 'instructor'), -- testuser created the class (5, 12, 2, 'student'), -- testadmin is a student - (6, 13, 2, 'student'); -- testinstructor is a student + (6, 13, 2, 'student'), -- testinstructor is a student + (7, 11, 3, 'instructor'), -- testuser created the class + (8, 11, 4, 'instructor'); -- testuser created the class INSERT INTO context_strings (id, ctx_str) VALUES (1, 'context 1'), (2, 'context 2'); diff --git a/tests/test_privacy.py b/tests/test_privacy.py index 88eb929..abfc32a 100644 --- a/tests/test_privacy.py +++ b/tests/test_privacy.py @@ -24,7 +24,7 @@ def verify_row_count(table: str, where_clause: str, params: list[str | int], exp def test_delete_user_data_requires_confirmation(app, client, auth): """Test that user data deletion requires proper confirmation""" - auth.login() + login_instructor_in_class(client, auth) with app.app_context(): user_id = get_db().execute("SELECT id FROM users WHERE auth_name='testuser'").fetchone()['id'] @@ -47,7 +47,7 @@ def test_delete_user_data_requires_confirmation(app, client, auth): def test_delete_user_data_full_process(app, client, auth): """Test complete user data deletion process""" - auth.login() + login_instructor_in_class(client, auth) with app.app_context(): user_id = get_db().execute("SELECT id FROM users WHERE auth_name='testuser'").fetchone()['id'] @@ -64,7 +64,21 @@ def test_delete_user_data_full_process(app, client, auth): # Perform deletion with proper confirmation response = client.post('/profile/delete_data', data={'confirm_delete': 'DELETE'}) assert response.status_code == 302 - assert response.location == "/auth/login" + assert response.location == "/profile/" # redirect to profile because we have a user-created class + response = client.get('/profile/') + assert b"You must delete all classes you created before deleting your data." in response.data + + # Delete the classes so user deletion can proceed + client.post('/instructor/class/delete', data={'class_id': 2, 'confirm_delete': 'DELETE'}) + client.get('/classes/switch/3') + client.post('/instructor/class/delete', data={'class_id': 3, 'confirm_delete': 'DELETE'}) + client.get('/classes/switch/4') + client.post('/instructor/class/delete', data={'class_id': 4, 'confirm_delete': 'DELETE'}) + + # Perform deletion with proper confirmation + response = client.post('/profile/delete_data', data={'confirm_delete': 'DELETE'}) + assert response.status_code == 302 + assert response.location == "/auth/login" # redirect to login because user was successfully deleted # Verify final state of all affected tables with app.app_context():