Skip to content

[BACKEND] Replace occupation-based course creation gate with permission group #274

Description

@smattymatty

Claiming This Task

Before you start working, check the Assignees section. If no one is assigned, leave a comment claiming the issue and assign it to yourself. This prevents duplicate work.

See the Community Wiki for contributing guidelines and git workflow.

Problem

Course creation is currently gated by profile.occupation == "teacher" (via CanCreateCourse permission and the COURSE_CREATION_REQUIRES_TEACHER setting). This has several problems:

  1. Users must lie about their profession — a student who wants to create a study group course has to change their occupation to "Teacher" on their profile, which is misleading
  2. No admin control — there's no way for admins to grant course creation rights to specific users without those users changing their own occupation
  3. Even superusers are blocked — the permission doesn't check is_superuser, so even admin accounts can't create courses unless their occupation is "teacher"
  4. All-or-nothing — the COURSE_CREATION_REQUIRES_TEACHER flag is either on (only "teacher" occupation) or off (everyone), with no middle ground

Requirements

  1. Create a Django permission group (e.g. course_creators) that controls who can create courses
  2. Replace the occupation check in CanCreateCourse with a group membership check:
    • If COURSE_CREATION_MODE is "open": any authenticated user can create courses
    • If COURSE_CREATION_MODE is "group" (default): only users in the course_creators group (or superusers) can create courses
  3. Superusers should always be allowed regardless of the mode
  4. Add a management command (e.g. manage.py grant_course_creation <username>) to easily add users to the group from the CLI
  5. The group should be manageable from Django admin — admins can add/remove users via the admin panel
  6. Deprecate COURSE_CREATION_REQUIRES_TEACHER — replace with COURSE_CREATION_MODE setting
  7. Migration: create the permission group in a data migration so it exists on first deploy

Architecture Context

Current implementation:

  • courses/permissions.pyCanCreateCourse checks profile.occupation == "teacher"
  • EduLite/settings.pyCOURSE_CREATION_REQUIRES_TEACHER = True
  • courses/views.pyCourseListCreateView uses CanCreateCourse in its permission classes

Approach:

  • Use Django's built-in Group model (django.contrib.auth.models.Group) — no custom models needed
  • The permission check becomes user.groups.filter(name="course_creators").exists() or user.is_superuser
  • A data migration creates the group so it's available immediately

Files to be Altered

  • backend/EduLite/courses/permissions.py — rewrite CanCreateCourse to check group membership
  • backend/EduLite/EduLite/settings.py — replace COURSE_CREATION_REQUIRES_TEACHER with COURSE_CREATION_MODE
  • backend/EduLite/courses/management/commands/grant_course_creation.py — new management command
  • backend/EduLite/courses/migrations/XXXX_create_course_creators_group.py — data migration for group creation
  • backend/EduLite/courses/tests/test_permissions.py — update existing tests, add group-based tests
  • backend/EduLite/courses/tests/test_api_course_create.py — update tests for new permission logic

Testing Requirements

  • Superuser can always create courses regardless of mode
  • In "group" mode: user in course_creators group can create, user not in group cannot
  • In "open" mode: any authenticated user can create
  • Management command adds user to group correctly
  • Management command handles non-existent username gracefully
  • Existing tests updated to use group-based permissions instead of occupation
  • Unauthenticated users still blocked in all modes

Additional Context

This was discovered while building the Course Create/Edit Form Page (#251). A user with admin access couldn't create a course because their profile occupation wasn't set to "teacher" — they had to change their profession on their profile just to use the feature, which defeats the purpose of having an occupation field that reflects reality.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions