We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Remove the user profile avatar icons from the student group menus
The user profile avatar icon was removed from the user profile page in #590. Now the profile icon needs to be removed from the student group menus that include it.

We decided to remove the avatar in order to remove the dependency on the external service gravatar.com and Wordpress accounts.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `userprofile/models.py`
Content:
```
1 import hashlib
2 from typing import Any, Optional, TYPE_CHECKING
3 
4 from aplus_auth.payload import Permission
5 from django.conf import settings
6 from django.contrib.auth.models import User, AnonymousUser
7 from django.urls import reverse
8 from django.db import models
9 from django.db.models.signals import post_save
10 from django.utils.functional import cached_property
11 from django.utils.translation import gettext_lazy as _
12 from rest_framework.authtoken.models import Token
13 
14 from authorization.object_permissions import ObjectPermissions
15 
16 if TYPE_CHECKING:
17     from django.db.models.manager import RelatedManager
18 
19     from course.models import CourseInstance
20     from exercise.models import BaseExercise, Submission, SubmissionDraft
21     from external_services.models import LTIService
22 
23 
24 class UserProfileQuerySet(models.QuerySet['UserProfile']):
25     def prefetch_tags(self, instance: 'CourseInstance', to_attr: str = 'instance_taggings') -> 'UserProfileQuerySet':
26         return self.prefetch_related(
27             models.Prefetch(
28                 'taggings',
29                 instance.taggings.select_related('tag'),
30                 to_attr,
31             ),
32         )
33 
34 
35 class UserProfileManager(models.Manager):
36     _queryset_class = UserProfileQuerySet
37 
38     def get_queryset(self):
39         return super().get_queryset().select_related("user")
40 
41     def prefetch_tags(self, instance: 'CourseInstance', to_attr: str = 'instance_taggings') -> UserProfileQuerySet:
42         return self.all().prefetch_tags(instance, to_attr)
43 
44 
45 class UserProfile(models.Model):
46     """
47     Additional user information and methods.
48     """
49 
50     @classmethod
51     def get_by_student_id(cls, student_id, org=settings.LOCAL_ORGANIZATION):
52         # Because student ID is unique only within organization,
53         # also organization needs to be specified.
54         return cls.objects.get(student_id=student_id, organization=org)
55 
56     @classmethod
57     def get_by_email(cls, email):
58         return User.objects.filter(email=email).first().userprofile
59 
60     @classmethod
61     def get_by_request(cls, request):
62         user = request.user
63         if user.is_authenticated:
64             return user.userprofile
65         raise RuntimeError("Seeking user profile without authenticated user.")
66 
67     user = models.OneToOneField(User,
68         verbose_name=_('LABEL_USER'),
69         on_delete=models.CASCADE,
70     )
71     language = models.CharField(
72         verbose_name=_('LABEL_LANGUAGE'),
73         max_length=5,
74         blank=True,
75         default='',
76     )
77     student_id = models.CharField(
78         verbose_name=_('LABEL_STUDENT_ID'),
79         max_length=25,
80         blank=True, null=True,
81     )
82     organization = models.CharField(
83         verbose_name=_('LABEL_ORGANIZATION'),
84         max_length=80,
85         blank=True,
86     )
87     objects = UserProfileManager()
88 
89     if TYPE_CHECKING:
90         id: models.AutoField
91         submissions: RelatedManager['Submission']
92         graded_submissions: RelatedManager['Submission']
93         submission_drafts: RelatedManager['SubmissionDraft']
94 
95     class Meta:
96         verbose_name = _('MODEL_NAME_USER_PROFILE')
97         verbose_name_plural = _('MODEL_NAME_USER_PROFILE_PLURAL')
98         ordering = ['id']
99 
100     def __str__(self):
101         if self.student_id is None:
102             return f"{self.user.username} ({self.user.first_name} {self.user.last_name}, {self.user.email})"
103         return (
104             f"{self.user.username} ({self.user.first_name} {self.user.last_name}, "
105             f"{self.user.email}, "
106             f"{self.student_id})"
107         )
108 
109     @cached_property
110     def api_token(self):
111         # FIXME: implement support for more than 1 token
112         token, _created = Token.objects.get_or_create(user=self.user)
113         return token.key
114 
115     @cached_property
116     def avatar_url(self):
117         """
118         URL address for gravatar image based on the user email.
119         """
120         hash_key = hashlib.md5(self.user.email.encode('utf-8')).hexdigest()
121         return "http://www.gravatar.com/avatar/" + hash_key + "?d=identicon"
122 
123     @cached_property
124     def shortname(self):
125         """
126         A short version of the user's name in form "John D."
127         """
128         try:
129             return self.user.first_name + " " + self.user.last_name[0] + "."
130         except: # pylint: disable=bare-except
131             return self.user.username
132 
133     @cached_property
134     def name_with_student_id(self):
135         name = self.user.get_full_name()
136         if self.student_id:
137             return name + ', ' + self.student_id
138         return name
139 
140     @cached_property
141     def is_external(self):
142         """
143         Is this an external rather than internal account.
144         """
145         return (self.organization != settings.LOCAL_ORGANIZATION)
146 
147     def get_url(self, instance):
148         kwargs = dict(user_id=self.user.id, **instance.get_url_kwargs()) # pylint: disable=use-dict-literal
149         return reverse('user-results', kwargs=kwargs)
150 
151     def regenerate_api_token(self):
152         # FIXME: implement support for more than 1 token
153         token, created = Token.objects.get_or_create(user=self.user)
154         if not created:
155             token.delete()
156             Token.objects.create(user=self.user)
157 
158 
159 def create_user_profile(sender, instance, created, **kwargs): # pylint: disable=unused-argument
160     """
161     This function automatically creates an user profile for all new User models. The profiles
162     are used for extending the User models with domain specific attributes and behavior.
163 
164     @param sender: the signal that invoked the function
165     @param instance: the User object that was just created
166     @param created: a boolean whether the object was created and not just updated
167     """
168     if created:
169         UserProfile.objects.get_or_create(user=instance)
170 
171 
172 # Attach to the post_save signal.
173 post_save.connect(create_user_profile, sender=User)
174 
175 
176 class GraderUser(AnonymousUser):
177     def __init__(self, username: str, permissions: ObjectPermissions, **extra: Any):
178         self.username = username
179         self.permissions = permissions
180         self._extra = extra
181 
182     @property
183     def is_anonymous(self):
184         """GraderUser is anonymous, but not AnonymousUser"""
185         return True
186 
187     @property
188     def is_authenticated(self):
189         return True
190 
191     # A-plus interface
192     @property
193     def userprofile(self):
194         """Compatibilty with User.userprofile"""
195         return self
196 
197 
198 class LTIServiceUser(GraderUser):
199     def __init__(self, submission: "Submission" = None,
200             exercise: "BaseExercise" = None,
201             lti_service: Optional["LTIService"] = None,
202             user_id: int = None,
203             **kwargs: Any,
204             ) -> None:
205         """exercise and student_id must both be present if one is"""
206         self.lti_service = lti_service
207         permissions = ObjectPermissions()
208         if submission:
209             permissions.submissions.add(Permission.WRITE, submission)
210         if exercise:
211             if user_id:
212                 permissions.submissions.add_create(exercise=exercise, user_id=user_id)
213             else:
214                 permissions.submissions.add_create(exercise=exercise)
215         super().__init__("LTI", permissions, **kwargs)
216
```

Path: `course/renders.py`
Content:
```
1 from django.template import loader
2 
3 
4 def render_avatars(profiles):
5     template = loader.get_template("course/_avatars.html")
6     return template.render({ 'profiles': profiles })
7 
8 
9 def group_info_context(group, profile):
10     if not group:
11         return { 'id': None }
12     return {
13         'id': group.id,
14         'collaborators': group.collaborator_names(profile),
15         'avatars': render_avatars(group.members.all()),
16     }
17 
18 
19 def render_group_info(group, profile):
20     template = loader.get_template("course/_group_info.html")
21     return template.render(group_info_context(group, profile))
22
```

Path: `course/templatetags/course.py`
Content:
```
1 from typing import Any, Dict, List, Union
2 from copy import deepcopy
3 
4 from django import template
5 from django.db import models
6 from django.utils.safestring import mark_safe
7 from django.utils.translation import get_language
8 
9 from exercise.cache.content import CachedContent
10 from course.models import CourseInstance, UserTagging
11 from lib.localization_syntax import pick_localized
12 from userprofile.models import UserProfile
13 from ..cache.menu import CachedTopMenu
14 
15 
16 register = template.Library()
17 
18 
19 def _prepare_topmenu(context):
20     if 'topmenu' not in context:
21         request = context.get('request', None)
22         context['topmenu'] = CachedTopMenu(request.user if request else None)
23     return context['topmenu']
24 
25 
26 def _deadline_extended_exercise_open(entry, now):
27     personal_deadline = entry.get('personal_deadline')
28     return personal_deadline is not None and entry['opening_time'] <= now <= personal_deadline
29 
30 
31 @register.inclusion_tag("course/_course_dropdown_menu.html", takes_context=True)
32 def course_menu(context):
33     menu = _prepare_topmenu(context)
34     return { "instances": menu.courses() }
35 
36 
37 @register.inclusion_tag('course/_group_select.html', takes_context=True)
38 def group_select(context):
39     instance = context.get('instance', None)
40     if not instance:
41         return { 'groups': [] }
42     menu = _prepare_topmenu(context)
43     groups, selected = menu.groups(instance)
44     return {
45         'instance': instance,
46         'groups': groups,
47         'selected': selected,
48     }
49 
50 
51 @register.filter
52 def escape_slashes(string):
53     return str(string).replace('/', '\/') # noqa: W605
54 
55 @register.filter
56 def parse_localization(entry):
57     return pick_localized(entry, get_language())
58 
59 
60 @register.filter
61 def list_unselected(langs):
62     listed = list(filter(lambda x: x and x != get_language(), langs.split("|")))
63     return listed
64 
65 
66 @register.filter
67 def is_visible(entry):
68     return CachedContent.is_visible(entry)
69 
70 
71 @register.filter
72 def is_visible_to(entry, user):
73     return entry.is_visible_to(user)
74 
75 
76 @register.filter
77 def is_listed(entry):
78     return CachedContent.is_listed(entry)
79 
80 
81 @register.filter
82 def len_listed(entries):
83     return len([e for e in entries if CachedContent.is_listed(e)])
84 
85 
86 @register.filter
87 def is_in_maintenance(entry):
88     return CachedContent.is_in_maintenance(entry)
89 
90 
91 @register.filter
92 def exercises_open(entry, now):
93     return entry['opening_time'] <= now <= entry['closing_time']
94 
95 
96 @register.filter
97 def deadline_extended_exercise_open(entry, now):
98     return _deadline_extended_exercise_open(entry, now)
99 
100 
101 @register.filter
102 def deadline_extended_exercises_open(entry, now):
103     entries = deepcopy(entry['flatted'])
104     return any(_deadline_extended_exercise_open(entry, now) for entry in entries)
105 
106 
107 @register.filter
108 def exercises_submittable(entry, now):
109     if entry['late_allowed']:
110         return entry['opening_time'] <= now <= entry['late_time']
111     return entry['opening_time'] <= now <= entry['closing_time']
112 
113 
114 @register.filter
115 def has_opened(entry, now):
116     return entry['opening_time'] <= now
117 
118 
119 @register.filter
120 def url(model_object, name=None):
121     if name:
122         return model_object.get_url(name)
123     return model_object.get_display_url()
124 
125 
126 @register.filter
127 def names(profiles):
128     return ", ".join(p.user.get_full_name() for p in profiles)
129 
130 
131 @register.inclusion_tag('course/_avatars.html')
132 def avatars(profiles):
133     return { 'profiles': profiles }
134 
135 
136 @register.inclusion_tag("course/_profiles.html")
137 def profiles(
138         profiles: Union[UserProfile, List[UserProfile], models.QuerySet[UserProfile]],
139         instance: CourseInstance,
140         is_teacher: bool
141         ) -> Dict[str, Any]:
142     if isinstance(profiles, UserProfile):
143         profiles = [profiles]
144     elif isinstance(profiles, models.QuerySet):
145         # Avoid re-fetching the queryset
146         profiles = list(profiles)
147     return {
148         'instance': instance,
149         'profiles': profiles,
150         'is_teacher': is_teacher,
151     }
152 
153 
154 @register.simple_tag
155 def tags(profile, instance):
156     tags = UserTagging.objects.get_all(profile, instance)
157     return mark_safe(' '.join(tag.html_label for tag in tags))
158 
159 
160 @register.filter
161 def enrollment_audience(enrollment_audience_val):
162     # convert enrollment audience Enum value to the string description
163     return CourseInstance.ENROLLMENT_AUDIENCE[enrollment_audience_val]
164 
165 
166 @register.filter
167 def view_content_to(view_content_to_val):
168     # convert "view content to" Enum value to the string description
169     return CourseInstance.VIEW_ACCESS[view_content_to_val]
170 
171 
172 @register.filter
173 def is_banned_student(profile, course_instance):
174     return course_instance.is_banned(profile.user)
175
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/course/renders.py b/course/renders.py
--- a/course/renders.py
+++ b/course/renders.py
@@ -1,18 +1,12 @@
 from django.template import loader
 
 
-def render_avatars(profiles):
-    template = loader.get_template("course/_avatars.html")
-    return template.render({ 'profiles': profiles })
-
-
 def group_info_context(group, profile):
     if not group:
         return { 'id': None }
     return {
         'id': group.id,
-        'collaborators': group.collaborator_names(profile),
-        'avatars': render_avatars(group.members.all()),
+        'collaborators': group.collaborator_names(profile)
     }
 
 
diff --git a/course/templatetags/course.py b/course/templatetags/course.py
--- a/course/templatetags/course.py
+++ b/course/templatetags/course.py
@@ -128,11 +128,6 @@
     return ", ".join(p.user.get_full_name() for p in profiles)
 
 
-@register.inclusion_tag('course/_avatars.html')
-def avatars(profiles):
-    return { 'profiles': profiles }
-
-
 @register.inclusion_tag("course/_profiles.html")
 def profiles(
         profiles: Union[UserProfile, List[UserProfile], models.QuerySet[UserProfile]],
diff --git a/userprofile/models.py b/userprofile/models.py
--- a/userprofile/models.py
+++ b/userprofile/models.py
@@ -1,4 +1,3 @@
-import hashlib
 from typing import Any, Optional, TYPE_CHECKING
 
 from aplus_auth.payload import Permission
@@ -112,14 +111,6 @@
         token, _created = Token.objects.get_or_create(user=self.user)
         return token.key
 
-    @cached_property
-    def avatar_url(self):
-        """
-        URL address for gravatar image based on the user email.
-        """
-        hash_key = hashlib.md5(self.user.email.encode('utf-8')).hexdigest()
-        return "http://www.gravatar.com/avatar/" + hash_key + "?d=identicon"
-
     @cached_property
     def shortname(self):
         """

{"golden_diff": "diff --git a/course/renders.py b/course/renders.py\n--- a/course/renders.py\n+++ b/course/renders.py\n@@ -1,18 +1,12 @@\n from django.template import loader\n \n \n-def render_avatars(profiles):\n-    template = loader.get_template(\"course/_avatars.html\")\n-    return template.render({ 'profiles': profiles })\n-\n-\n def group_info_context(group, profile):\n     if not group:\n         return { 'id': None }\n     return {\n         'id': group.id,\n-        'collaborators': group.collaborator_names(profile),\n-        'avatars': render_avatars(group.members.all()),\n+        'collaborators': group.collaborator_names(profile)\n     }\n \n \ndiff --git a/course/templatetags/course.py b/course/templatetags/course.py\n--- a/course/templatetags/course.py\n+++ b/course/templatetags/course.py\n@@ -128,11 +128,6 @@\n     return \", \".join(p.user.get_full_name() for p in profiles)\n \n \n-@register.inclusion_tag('course/_avatars.html')\n-def avatars(profiles):\n-    return { 'profiles': profiles }\n-\n-\n @register.inclusion_tag(\"course/_profiles.html\")\n def profiles(\n         profiles: Union[UserProfile, List[UserProfile], models.QuerySet[UserProfile]],\ndiff --git a/userprofile/models.py b/userprofile/models.py\n--- a/userprofile/models.py\n+++ b/userprofile/models.py\n@@ -1,4 +1,3 @@\n-import hashlib\n from typing import Any, Optional, TYPE_CHECKING\n \n from aplus_auth.payload import Permission\n@@ -112,14 +111,6 @@\n         token, _created = Token.objects.get_or_create(user=self.user)\n         return token.key\n \n-    @cached_property\n-    def avatar_url(self):\n-        \"\"\"\n-        URL address for gravatar image based on the user email.\n-        \"\"\"\n-        hash_key = hashlib.md5(self.user.email.encode('utf-8')).hexdigest()\n-        return \"http://www.gravatar.com/avatar/\" + hash_key + \"?d=identicon\"\n-\n     @cached_property\n     def shortname(self):\n         \"\"\"\n", "issue": "Remove the user profile avatar icons from the student group menus\nThe user profile avatar icon was removed from the user profile page in #590. Now the profile icon needs to be removed from the student group menus that include it.\r\n\r\nWe decided to remove the avatar in order to remove the dependency on the external service gravatar.com and Wordpress accounts.\n", "before_files": [{"content": "import hashlib\nfrom typing import Any, Optional, TYPE_CHECKING\n\nfrom aplus_auth.payload import Permission\nfrom django.conf import settings\nfrom django.contrib.auth.models import User, AnonymousUser\nfrom django.urls import reverse\nfrom django.db import models\nfrom django.db.models.signals import post_save\nfrom django.utils.functional import cached_property\nfrom django.utils.translation import gettext_lazy as _\nfrom rest_framework.authtoken.models import Token\n\nfrom authorization.object_permissions import ObjectPermissions\n\nif TYPE_CHECKING:\n    from django.db.models.manager import RelatedManager\n\n    from course.models import CourseInstance\n    from exercise.models import BaseExercise, Submission, SubmissionDraft\n    from external_services.models import LTIService\n\n\nclass UserProfileQuerySet(models.QuerySet['UserProfile']):\n    def prefetch_tags(self, instance: 'CourseInstance', to_attr: str = 'instance_taggings') -> 'UserProfileQuerySet':\n        return self.prefetch_related(\n            models.Prefetch(\n                'taggings',\n                instance.taggings.select_related('tag'),\n                to_attr,\n            ),\n        )\n\n\nclass UserProfileManager(models.Manager):\n    _queryset_class = UserProfileQuerySet\n\n    def get_queryset(self):\n        return super().get_queryset().select_related(\"user\")\n\n    def prefetch_tags(self, instance: 'CourseInstance', to_attr: str = 'instance_taggings') -> UserProfileQuerySet:\n        return self.all().prefetch_tags(instance, to_attr)\n\n\nclass UserProfile(models.Model):\n    \"\"\"\n    Additional user information and methods.\n    \"\"\"\n\n    @classmethod\n    def get_by_student_id(cls, student_id, org=settings.LOCAL_ORGANIZATION):\n        # Because student ID is unique only within organization,\n        # also organization needs to be specified.\n        return cls.objects.get(student_id=student_id, organization=org)\n\n    @classmethod\n    def get_by_email(cls, email):\n        return User.objects.filter(email=email).first().userprofile\n\n    @classmethod\n    def get_by_request(cls, request):\n        user = request.user\n        if user.is_authenticated:\n            return user.userprofile\n        raise RuntimeError(\"Seeking user profile without authenticated user.\")\n\n    user = models.OneToOneField(User,\n        verbose_name=_('LABEL_USER'),\n        on_delete=models.CASCADE,\n    )\n    language = models.CharField(\n        verbose_name=_('LABEL_LANGUAGE'),\n        max_length=5,\n        blank=True,\n        default='',\n    )\n    student_id = models.CharField(\n        verbose_name=_('LABEL_STUDENT_ID'),\n        max_length=25,\n        blank=True, null=True,\n    )\n    organization = models.CharField(\n        verbose_name=_('LABEL_ORGANIZATION'),\n        max_length=80,\n        blank=True,\n    )\n    objects = UserProfileManager()\n\n    if TYPE_CHECKING:\n        id: models.AutoField\n        submissions: RelatedManager['Submission']\n        graded_submissions: RelatedManager['Submission']\n        submission_drafts: RelatedManager['SubmissionDraft']\n\n    class Meta:\n        verbose_name = _('MODEL_NAME_USER_PROFILE')\n        verbose_name_plural = _('MODEL_NAME_USER_PROFILE_PLURAL')\n        ordering = ['id']\n\n    def __str__(self):\n        if self.student_id is None:\n            return f\"{self.user.username} ({self.user.first_name} {self.user.last_name}, {self.user.email})\"\n        return (\n            f\"{self.user.username} ({self.user.first_name} {self.user.last_name}, \"\n            f\"{self.user.email}, \"\n            f\"{self.student_id})\"\n        )\n\n    @cached_property\n    def api_token(self):\n        # FIXME: implement support for more than 1 token\n        token, _created = Token.objects.get_or_create(user=self.user)\n        return token.key\n\n    @cached_property\n    def avatar_url(self):\n        \"\"\"\n        URL address for gravatar image based on the user email.\n        \"\"\"\n        hash_key = hashlib.md5(self.user.email.encode('utf-8')).hexdigest()\n        return \"http://www.gravatar.com/avatar/\" + hash_key + \"?d=identicon\"\n\n    @cached_property\n    def shortname(self):\n        \"\"\"\n        A short version of the user's name in form \"John D.\"\n        \"\"\"\n        try:\n            return self.user.first_name + \" \" + self.user.last_name[0] + \".\"\n        except: # pylint: disable=bare-except\n            return self.user.username\n\n    @cached_property\n    def name_with_student_id(self):\n        name = self.user.get_full_name()\n        if self.student_id:\n            return name + ', ' + self.student_id\n        return name\n\n    @cached_property\n    def is_external(self):\n        \"\"\"\n        Is this an external rather than internal account.\n        \"\"\"\n        return (self.organization != settings.LOCAL_ORGANIZATION)\n\n    def get_url(self, instance):\n        kwargs = dict(user_id=self.user.id, **instance.get_url_kwargs()) # pylint: disable=use-dict-literal\n        return reverse('user-results', kwargs=kwargs)\n\n    def regenerate_api_token(self):\n        # FIXME: implement support for more than 1 token\n        token, created = Token.objects.get_or_create(user=self.user)\n        if not created:\n            token.delete()\n            Token.objects.create(user=self.user)\n\n\ndef create_user_profile(sender, instance, created, **kwargs): # pylint: disable=unused-argument\n    \"\"\"\n    This function automatically creates an user profile for all new User models. The profiles\n    are used for extending the User models with domain specific attributes and behavior.\n\n    @param sender: the signal that invoked the function\n    @param instance: the User object that was just created\n    @param created: a boolean whether the object was created and not just updated\n    \"\"\"\n    if created:\n        UserProfile.objects.get_or_create(user=instance)\n\n\n# Attach to the post_save signal.\npost_save.connect(create_user_profile, sender=User)\n\n\nclass GraderUser(AnonymousUser):\n    def __init__(self, username: str, permissions: ObjectPermissions, **extra: Any):\n        self.username = username\n        self.permissions = permissions\n        self._extra = extra\n\n    @property\n    def is_anonymous(self):\n        \"\"\"GraderUser is anonymous, but not AnonymousUser\"\"\"\n        return True\n\n    @property\n    def is_authenticated(self):\n        return True\n\n    # A-plus interface\n    @property\n    def userprofile(self):\n        \"\"\"Compatibilty with User.userprofile\"\"\"\n        return self\n\n\nclass LTIServiceUser(GraderUser):\n    def __init__(self, submission: \"Submission\" = None,\n            exercise: \"BaseExercise\" = None,\n            lti_service: Optional[\"LTIService\"] = None,\n            user_id: int = None,\n            **kwargs: Any,\n            ) -> None:\n        \"\"\"exercise and student_id must both be present if one is\"\"\"\n        self.lti_service = lti_service\n        permissions = ObjectPermissions()\n        if submission:\n            permissions.submissions.add(Permission.WRITE, submission)\n        if exercise:\n            if user_id:\n                permissions.submissions.add_create(exercise=exercise, user_id=user_id)\n            else:\n                permissions.submissions.add_create(exercise=exercise)\n        super().__init__(\"LTI\", permissions, **kwargs)\n", "path": "userprofile/models.py"}, {"content": "from django.template import loader\n\n\ndef render_avatars(profiles):\n    template = loader.get_template(\"course/_avatars.html\")\n    return template.render({ 'profiles': profiles })\n\n\ndef group_info_context(group, profile):\n    if not group:\n        return { 'id': None }\n    return {\n        'id': group.id,\n        'collaborators': group.collaborator_names(profile),\n        'avatars': render_avatars(group.members.all()),\n    }\n\n\ndef render_group_info(group, profile):\n    template = loader.get_template(\"course/_group_info.html\")\n    return template.render(group_info_context(group, profile))\n", "path": "course/renders.py"}, {"content": "from typing import Any, Dict, List, Union\nfrom copy import deepcopy\n\nfrom django import template\nfrom django.db import models\nfrom django.utils.safestring import mark_safe\nfrom django.utils.translation import get_language\n\nfrom exercise.cache.content import CachedContent\nfrom course.models import CourseInstance, UserTagging\nfrom lib.localization_syntax import pick_localized\nfrom userprofile.models import UserProfile\nfrom ..cache.menu import CachedTopMenu\n\n\nregister = template.Library()\n\n\ndef _prepare_topmenu(context):\n    if 'topmenu' not in context:\n        request = context.get('request', None)\n        context['topmenu'] = CachedTopMenu(request.user if request else None)\n    return context['topmenu']\n\n\ndef _deadline_extended_exercise_open(entry, now):\n    personal_deadline = entry.get('personal_deadline')\n    return personal_deadline is not None and entry['opening_time'] <= now <= personal_deadline\n\n\n@register.inclusion_tag(\"course/_course_dropdown_menu.html\", takes_context=True)\ndef course_menu(context):\n    menu = _prepare_topmenu(context)\n    return { \"instances\": menu.courses() }\n\n\n@register.inclusion_tag('course/_group_select.html', takes_context=True)\ndef group_select(context):\n    instance = context.get('instance', None)\n    if not instance:\n        return { 'groups': [] }\n    menu = _prepare_topmenu(context)\n    groups, selected = menu.groups(instance)\n    return {\n        'instance': instance,\n        'groups': groups,\n        'selected': selected,\n    }\n\n\n@register.filter\ndef escape_slashes(string):\n    return str(string).replace('/', '\\/') # noqa: W605\n\n@register.filter\ndef parse_localization(entry):\n    return pick_localized(entry, get_language())\n\n\n@register.filter\ndef list_unselected(langs):\n    listed = list(filter(lambda x: x and x != get_language(), langs.split(\"|\")))\n    return listed\n\n\n@register.filter\ndef is_visible(entry):\n    return CachedContent.is_visible(entry)\n\n\n@register.filter\ndef is_visible_to(entry, user):\n    return entry.is_visible_to(user)\n\n\n@register.filter\ndef is_listed(entry):\n    return CachedContent.is_listed(entry)\n\n\n@register.filter\ndef len_listed(entries):\n    return len([e for e in entries if CachedContent.is_listed(e)])\n\n\n@register.filter\ndef is_in_maintenance(entry):\n    return CachedContent.is_in_maintenance(entry)\n\n\n@register.filter\ndef exercises_open(entry, now):\n    return entry['opening_time'] <= now <= entry['closing_time']\n\n\n@register.filter\ndef deadline_extended_exercise_open(entry, now):\n    return _deadline_extended_exercise_open(entry, now)\n\n\n@register.filter\ndef deadline_extended_exercises_open(entry, now):\n    entries = deepcopy(entry['flatted'])\n    return any(_deadline_extended_exercise_open(entry, now) for entry in entries)\n\n\n@register.filter\ndef exercises_submittable(entry, now):\n    if entry['late_allowed']:\n        return entry['opening_time'] <= now <= entry['late_time']\n    return entry['opening_time'] <= now <= entry['closing_time']\n\n\n@register.filter\ndef has_opened(entry, now):\n    return entry['opening_time'] <= now\n\n\n@register.filter\ndef url(model_object, name=None):\n    if name:\n        return model_object.get_url(name)\n    return model_object.get_display_url()\n\n\n@register.filter\ndef names(profiles):\n    return \", \".join(p.user.get_full_name() for p in profiles)\n\n\n@register.inclusion_tag('course/_avatars.html')\ndef avatars(profiles):\n    return { 'profiles': profiles }\n\n\n@register.inclusion_tag(\"course/_profiles.html\")\ndef profiles(\n        profiles: Union[UserProfile, List[UserProfile], models.QuerySet[UserProfile]],\n        instance: CourseInstance,\n        is_teacher: bool\n        ) -> Dict[str, Any]:\n    if isinstance(profiles, UserProfile):\n        profiles = [profiles]\n    elif isinstance(profiles, models.QuerySet):\n        # Avoid re-fetching the queryset\n        profiles = list(profiles)\n    return {\n        'instance': instance,\n        'profiles': profiles,\n        'is_teacher': is_teacher,\n    }\n\n\n@register.simple_tag\ndef tags(profile, instance):\n    tags = UserTagging.objects.get_all(profile, instance)\n    return mark_safe(' '.join(tag.html_label for tag in tags))\n\n\n@register.filter\ndef enrollment_audience(enrollment_audience_val):\n    # convert enrollment audience Enum value to the string description\n    return CourseInstance.ENROLLMENT_AUDIENCE[enrollment_audience_val]\n\n\n@register.filter\ndef view_content_to(view_content_to_val):\n    # convert \"view content to\" Enum value to the string description\n    return CourseInstance.VIEW_ACCESS[view_content_to_val]\n\n\n@register.filter\ndef is_banned_student(profile, course_instance):\n    return course_instance.is_banned(profile.user)\n", "path": "course/templatetags/course.py"}], "after_files": [{"content": "from typing import Any, Optional, TYPE_CHECKING\n\nfrom aplus_auth.payload import Permission\nfrom django.conf import settings\nfrom django.contrib.auth.models import User, AnonymousUser\nfrom django.urls import reverse\nfrom django.db import models\nfrom django.db.models.signals import post_save\nfrom django.utils.functional import cached_property\nfrom django.utils.translation import gettext_lazy as _\nfrom rest_framework.authtoken.models import Token\n\nfrom authorization.object_permissions import ObjectPermissions\n\nif TYPE_CHECKING:\n    from django.db.models.manager import RelatedManager\n\n    from course.models import CourseInstance\n    from exercise.models import BaseExercise, Submission, SubmissionDraft\n    from external_services.models import LTIService\n\n\nclass UserProfileQuerySet(models.QuerySet['UserProfile']):\n    def prefetch_tags(self, instance: 'CourseInstance', to_attr: str = 'instance_taggings') -> 'UserProfileQuerySet':\n        return self.prefetch_related(\n            models.Prefetch(\n                'taggings',\n                instance.taggings.select_related('tag'),\n                to_attr,\n            ),\n        )\n\n\nclass UserProfileManager(models.Manager):\n    _queryset_class = UserProfileQuerySet\n\n    def get_queryset(self):\n        return super().get_queryset().select_related(\"user\")\n\n    def prefetch_tags(self, instance: 'CourseInstance', to_attr: str = 'instance_taggings') -> UserProfileQuerySet:\n        return self.all().prefetch_tags(instance, to_attr)\n\n\nclass UserProfile(models.Model):\n    \"\"\"\n    Additional user information and methods.\n    \"\"\"\n\n    @classmethod\n    def get_by_student_id(cls, student_id, org=settings.LOCAL_ORGANIZATION):\n        # Because student ID is unique only within organization,\n        # also organization needs to be specified.\n        return cls.objects.get(student_id=student_id, organization=org)\n\n    @classmethod\n    def get_by_email(cls, email):\n        return User.objects.filter(email=email).first().userprofile\n\n    @classmethod\n    def get_by_request(cls, request):\n        user = request.user\n        if user.is_authenticated:\n            return user.userprofile\n        raise RuntimeError(\"Seeking user profile without authenticated user.\")\n\n    user = models.OneToOneField(User,\n        verbose_name=_('LABEL_USER'),\n        on_delete=models.CASCADE,\n    )\n    language = models.CharField(\n        verbose_name=_('LABEL_LANGUAGE'),\n        max_length=5,\n        blank=True,\n        default='',\n    )\n    student_id = models.CharField(\n        verbose_name=_('LABEL_STUDENT_ID'),\n        max_length=25,\n        blank=True, null=True,\n    )\n    organization = models.CharField(\n        verbose_name=_('LABEL_ORGANIZATION'),\n        max_length=80,\n        blank=True,\n    )\n    objects = UserProfileManager()\n\n    if TYPE_CHECKING:\n        id: models.AutoField\n        submissions: RelatedManager['Submission']\n        graded_submissions: RelatedManager['Submission']\n        submission_drafts: RelatedManager['SubmissionDraft']\n\n    class Meta:\n        verbose_name = _('MODEL_NAME_USER_PROFILE')\n        verbose_name_plural = _('MODEL_NAME_USER_PROFILE_PLURAL')\n        ordering = ['id']\n\n    def __str__(self):\n        if self.student_id is None:\n            return f\"{self.user.username} ({self.user.first_name} {self.user.last_name}, {self.user.email})\"\n        return (\n            f\"{self.user.username} ({self.user.first_name} {self.user.last_name}, \"\n            f\"{self.user.email}, \"\n            f\"{self.student_id})\"\n        )\n\n    @cached_property\n    def api_token(self):\n        # FIXME: implement support for more than 1 token\n        token, _created = Token.objects.get_or_create(user=self.user)\n        return token.key\n\n    @cached_property\n    def shortname(self):\n        \"\"\"\n        A short version of the user's name in form \"John D.\"\n        \"\"\"\n        try:\n            return self.user.first_name + \" \" + self.user.last_name[0] + \".\"\n        except: # pylint: disable=bare-except\n            return self.user.username\n\n    @cached_property\n    def name_with_student_id(self):\n        name = self.user.get_full_name()\n        if self.student_id:\n            return name + ', ' + self.student_id\n        return name\n\n    @cached_property\n    def is_external(self):\n        \"\"\"\n        Is this an external rather than internal account.\n        \"\"\"\n        return (self.organization != settings.LOCAL_ORGANIZATION)\n\n    def get_url(self, instance):\n        kwargs = dict(user_id=self.user.id, **instance.get_url_kwargs()) # pylint: disable=use-dict-literal\n        return reverse('user-results', kwargs=kwargs)\n\n    def regenerate_api_token(self):\n        # FIXME: implement support for more than 1 token\n        token, created = Token.objects.get_or_create(user=self.user)\n        if not created:\n            token.delete()\n            Token.objects.create(user=self.user)\n\n\ndef create_user_profile(sender, instance, created, **kwargs): # pylint: disable=unused-argument\n    \"\"\"\n    This function automatically creates an user profile for all new User models. The profiles\n    are used for extending the User models with domain specific attributes and behavior.\n\n    @param sender: the signal that invoked the function\n    @param instance: the User object that was just created\n    @param created: a boolean whether the object was created and not just updated\n    \"\"\"\n    if created:\n        UserProfile.objects.get_or_create(user=instance)\n\n\n# Attach to the post_save signal.\npost_save.connect(create_user_profile, sender=User)\n\n\nclass GraderUser(AnonymousUser):\n    def __init__(self, username: str, permissions: ObjectPermissions, **extra: Any):\n        self.username = username\n        self.permissions = permissions\n        self._extra = extra\n\n    @property\n    def is_anonymous(self):\n        \"\"\"GraderUser is anonymous, but not AnonymousUser\"\"\"\n        return True\n\n    @property\n    def is_authenticated(self):\n        return True\n\n    # A-plus interface\n    @property\n    def userprofile(self):\n        \"\"\"Compatibilty with User.userprofile\"\"\"\n        return self\n\n\nclass LTIServiceUser(GraderUser):\n    def __init__(self, submission: \"Submission\" = None,\n            exercise: \"BaseExercise\" = None,\n            lti_service: Optional[\"LTIService\"] = None,\n            user_id: int = None,\n            **kwargs: Any,\n            ) -> None:\n        \"\"\"exercise and student_id must both be present if one is\"\"\"\n        self.lti_service = lti_service\n        permissions = ObjectPermissions()\n        if submission:\n            permissions.submissions.add(Permission.WRITE, submission)\n        if exercise:\n            if user_id:\n                permissions.submissions.add_create(exercise=exercise, user_id=user_id)\n            else:\n                permissions.submissions.add_create(exercise=exercise)\n        super().__init__(\"LTI\", permissions, **kwargs)\n", "path": "userprofile/models.py"}, {"content": "from django.template import loader\n\n\ndef group_info_context(group, profile):\n    if not group:\n        return { 'id': None }\n    return {\n        'id': group.id,\n        'collaborators': group.collaborator_names(profile)\n    }\n\n\ndef render_group_info(group, profile):\n    template = loader.get_template(\"course/_group_info.html\")\n    return template.render(group_info_context(group, profile))\n", "path": "course/renders.py"}, {"content": "from typing import Any, Dict, List, Union\nfrom copy import deepcopy\n\nfrom django import template\nfrom django.db import models\nfrom django.utils.safestring import mark_safe\nfrom django.utils.translation import get_language\n\nfrom exercise.cache.content import CachedContent\nfrom course.models import CourseInstance, UserTagging\nfrom lib.localization_syntax import pick_localized\nfrom userprofile.models import UserProfile\nfrom ..cache.menu import CachedTopMenu\n\n\nregister = template.Library()\n\n\ndef _prepare_topmenu(context):\n    if 'topmenu' not in context:\n        request = context.get('request', None)\n        context['topmenu'] = CachedTopMenu(request.user if request else None)\n    return context['topmenu']\n\n\ndef _deadline_extended_exercise_open(entry, now):\n    personal_deadline = entry.get('personal_deadline')\n    return personal_deadline is not None and entry['opening_time'] <= now <= personal_deadline\n\n\n@register.inclusion_tag(\"course/_course_dropdown_menu.html\", takes_context=True)\ndef course_menu(context):\n    menu = _prepare_topmenu(context)\n    return { \"instances\": menu.courses() }\n\n\n@register.inclusion_tag('course/_group_select.html', takes_context=True)\ndef group_select(context):\n    instance = context.get('instance', None)\n    if not instance:\n        return { 'groups': [] }\n    menu = _prepare_topmenu(context)\n    groups, selected = menu.groups(instance)\n    return {\n        'instance': instance,\n        'groups': groups,\n        'selected': selected,\n    }\n\n\n@register.filter\ndef escape_slashes(string):\n    return str(string).replace('/', '\\/') # noqa: W605\n\n@register.filter\ndef parse_localization(entry):\n    return pick_localized(entry, get_language())\n\n\n@register.filter\ndef list_unselected(langs):\n    listed = list(filter(lambda x: x and x != get_language(), langs.split(\"|\")))\n    return listed\n\n\n@register.filter\ndef is_visible(entry):\n    return CachedContent.is_visible(entry)\n\n\n@register.filter\ndef is_visible_to(entry, user):\n    return entry.is_visible_to(user)\n\n\n@register.filter\ndef is_listed(entry):\n    return CachedContent.is_listed(entry)\n\n\n@register.filter\ndef len_listed(entries):\n    return len([e for e in entries if CachedContent.is_listed(e)])\n\n\n@register.filter\ndef is_in_maintenance(entry):\n    return CachedContent.is_in_maintenance(entry)\n\n\n@register.filter\ndef exercises_open(entry, now):\n    return entry['opening_time'] <= now <= entry['closing_time']\n\n\n@register.filter\ndef deadline_extended_exercise_open(entry, now):\n    return _deadline_extended_exercise_open(entry, now)\n\n\n@register.filter\ndef deadline_extended_exercises_open(entry, now):\n    entries = deepcopy(entry['flatted'])\n    return any(_deadline_extended_exercise_open(entry, now) for entry in entries)\n\n\n@register.filter\ndef exercises_submittable(entry, now):\n    if entry['late_allowed']:\n        return entry['opening_time'] <= now <= entry['late_time']\n    return entry['opening_time'] <= now <= entry['closing_time']\n\n\n@register.filter\ndef has_opened(entry, now):\n    return entry['opening_time'] <= now\n\n\n@register.filter\ndef url(model_object, name=None):\n    if name:\n        return model_object.get_url(name)\n    return model_object.get_display_url()\n\n\n@register.filter\ndef names(profiles):\n    return \", \".join(p.user.get_full_name() for p in profiles)\n\n\n@register.inclusion_tag(\"course/_profiles.html\")\ndef profiles(\n        profiles: Union[UserProfile, List[UserProfile], models.QuerySet[UserProfile]],\n        instance: CourseInstance,\n        is_teacher: bool\n        ) -> Dict[str, Any]:\n    if isinstance(profiles, UserProfile):\n        profiles = [profiles]\n    elif isinstance(profiles, models.QuerySet):\n        # Avoid re-fetching the queryset\n        profiles = list(profiles)\n    return {\n        'instance': instance,\n        'profiles': profiles,\n        'is_teacher': is_teacher,\n    }\n\n\n@register.simple_tag\ndef tags(profile, instance):\n    tags = UserTagging.objects.get_all(profile, instance)\n    return mark_safe(' '.join(tag.html_label for tag in tags))\n\n\n@register.filter\ndef enrollment_audience(enrollment_audience_val):\n    # convert enrollment audience Enum value to the string description\n    return CourseInstance.ENROLLMENT_AUDIENCE[enrollment_audience_val]\n\n\n@register.filter\ndef view_content_to(view_content_to_val):\n    # convert \"view content to\" Enum value to the string description\n    return CourseInstance.VIEW_ACCESS[view_content_to_val]\n\n\n@register.filter\ndef is_banned_student(profile, course_instance):\n    return course_instance.is_banned(profile.user)\n", "path": "course/templatetags/course.py"}]}