We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Change the header photos
### Is your feature request related to a problem? Please describe.
The header photos are a bit outdated 

### Describe the solution you'd like
Choose some new photos

### Motivation
<img width="1306" alt="image" src="https://user-images.githubusercontent.com/7915741/164710169-e2d4ac27-04f0-48e6-bceb-bc05a1d3c30f.png">
these guys are old
<img width="1306" alt="image" src="https://user-images.githubusercontent.com/7915741/164710211-ad6fa23f-b021-4dab-9f4f-de45b11c56a2.png">
this image is lowres and old

### Describe alternatives you've considered
Not

### Additional context
I have access to the RU Brandportal with nice stock images of the university that we can use

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `website/thaliawebsite/templatetags/pick_header_image.py`
Content:
```
1 """Get a random header image."""
2 import random
3 
4 from django import template
5 from django.contrib.staticfiles.storage import staticfiles_storage
6 
7 register = template.Library()
8 
9 HEADERS = [
10     "img/headers/banner_default.jpg",
11     "img/headers/banner2.jpg",
12     "img/headers/banner4.jpg",
13     "img/headers/banner5.jpg",
14     "img/headers/banner6.jpg",
15 ]
16 
17 
18 @register.simple_tag
19 def pick_header_image():
20     """Render a random header image."""
21     return staticfiles_storage.url(random.choice(HEADERS))
22
```

Path: `website/photos/models.py`
Content:
```
1 import hashlib
2 import logging
3 import os
4 import random
5 from secrets import token_hex
6 
7 from django.conf import settings
8 from django.core.exceptions import ValidationError
9 from django.db import models
10 from django.db.models import Count, IntegerField, Value
11 from django.db.models.functions import Coalesce
12 from django.urls import reverse
13 from django.utils.functional import cached_property
14 from django.utils.translation import gettext_lazy as _
15 
16 from queryable_properties.managers import QueryablePropertiesManager
17 from queryable_properties.properties import AnnotationProperty
18 from thumbnails.fields import ImageField
19 
20 from members.models import Member
21 
22 COVER_FILENAME = "cover.jpg"
23 
24 
25 logger = logging.getLogger(__name__)
26 
27 
28 def photo_uploadto(instance, filename):
29     ext = os.path.splitext(filename)[1]
30     return f"photos/{instance.album.dirname}/{token_hex(8)}{ext}"
31 
32 
33 class DuplicatePhotoException(Exception):
34     """Raised when a photo with the same digest already exists in a given album."""
35 
36 
37 class Photo(models.Model):
38     """Model for a Photo object."""
39 
40     objects = QueryablePropertiesManager()
41 
42     album = models.ForeignKey(
43         "Album", on_delete=models.CASCADE, verbose_name=_("album")
44     )
45 
46     file = ImageField(
47         _("file"),
48         upload_to=photo_uploadto,
49         resize_source_to="source",
50     )
51 
52     rotation = models.IntegerField(
53         verbose_name=_("rotation"),
54         default=0,
55         choices=((x, x) for x in (0, 90, 180, 270)),
56         help_text=_("This does not modify the original image file."),
57     )
58 
59     _digest = models.CharField(
60         "digest",
61         max_length=40,
62         blank=True,
63         editable=False,
64     )
65 
66     num_likes = AnnotationProperty(
67         Coalesce(Count("likes"), Value(0), output_field=IntegerField())
68     )
69 
70     def __init__(self, *args, **kwargs):
71         """Initialize Photo object and set the file if it exists."""
72         super().__init__(*args, **kwargs)
73         if self.file:
74             self.original_file = self.file.name
75         else:
76             self.original_file = ""
77 
78     def __str__(self):
79         """Return the filename of a Photo object."""
80         return os.path.basename(self.file.name)
81 
82     def clean(self):
83         if not self.file._committed:
84             hash_sha1 = hashlib.sha1()
85             for chunk in iter(lambda: self.file.read(4096), b""):
86                 hash_sha1.update(chunk)
87             digest = hash_sha1.hexdigest()
88             self._digest = digest
89 
90             if (
91                 Photo.objects.filter(album=self.album, _digest=digest)
92                 .exclude(pk=self.pk)
93                 .exists()
94             ):
95                 raise ValidationError(
96                     {"file": "This photo already exists in this album."}
97                 )
98 
99         return super().clean()
100 
101     def delete(self, using=None, keep_parents=False):
102         removed = super().delete(using, keep_parents)
103         if self.file.name:
104             self.file.delete()
105         return removed
106 
107     class Meta:
108         """Meta class for Photo."""
109 
110         # Photos are created in order of their filename.
111         ordering = ("pk",)
112 
113 
114 class Like(models.Model):
115     photo = models.ForeignKey(
116         Photo, null=False, blank=False, related_name="likes", on_delete=models.CASCADE
117     )
118     member = models.ForeignKey(
119         Member, null=True, blank=False, on_delete=models.SET_NULL
120     )
121 
122     def __str__(self):
123         return str(self.member) + " " + _("likes") + " " + str(self.photo)
124 
125     class Meta:
126         unique_together = ["photo", "member"]
127 
128 
129 class Album(models.Model):
130     """Model for Album objects."""
131 
132     title = models.CharField(
133         _("title"),
134         blank=True,
135         max_length=200,
136         help_text=_("Leave empty to take over the title of the event"),
137     )
138 
139     dirname = models.CharField(
140         verbose_name=_("directory name"),
141         max_length=200,
142     )
143 
144     date = models.DateField(
145         verbose_name=_("date"),
146         blank=True,
147         help_text=_("Leave empty to take over the date of the event"),
148     )
149 
150     slug = models.SlugField(
151         verbose_name=_("slug"),
152         unique=True,
153     )
154 
155     hidden = models.BooleanField(verbose_name=_("hidden"), default=False)
156 
157     event = models.ForeignKey(
158         "events.Event",
159         on_delete=models.SET_NULL,
160         blank=True,
161         null=True,
162     )
163 
164     _cover = models.OneToOneField(
165         Photo,
166         on_delete=models.SET_NULL,
167         blank=True,
168         null=True,
169         related_name="covered_album",
170         verbose_name=_("cover image"),
171     )
172 
173     shareable = models.BooleanField(verbose_name=_("shareable"), default=False)
174 
175     photosdir = "photos"
176     photospath = os.path.join(settings.MEDIA_ROOT, photosdir)
177 
178     @cached_property
179     def cover(self):
180         """Return cover of Album.
181 
182         If a cover is not set, return a random photo or None if there are no photos.
183         """
184         cover = None
185         if self._cover is not None:
186             return self._cover
187 
188         # Not prefetched because this should be rare and is a lot of data
189         # `exists` is faster in theory, but requires everything to be fetched later anyways
190         if self.photo_set.exists():
191             random.seed(self.dirname)
192             cover = random.choice(self.photo_set.all())
193         return cover
194 
195     def __str__(self):
196         """Get string representation of Album."""
197         return f"{self.date:%Y-%m-%d} {self.title}"
198 
199     def get_absolute_url(self):
200         """Get url of Album."""
201         return reverse("photos:album", args=[str(self.slug)])
202 
203     def clean(self):
204         super().clean()
205         errors = {}
206 
207         if not self.title and not self.event:
208             errors.update(
209                 {"title": _("This field is required if there is no event selected.")}
210             )
211 
212         if not self.date and not self.event:
213             errors.update(
214                 {"date": _("This field is required if there is no event selected.")}
215             )
216 
217         if errors:
218             raise ValidationError(errors)
219 
220     def save(self, **kwargs):
221         """Save album and send appropriate notifications."""
222         # dirname is only set for new objects, to avoid ever changing it
223         if self.pk is None:
224             self.dirname = self.slug
225 
226         if not self.title and self.event:
227             self.title = self.event.title
228 
229         if not self.date:
230             self.date = self.event.start.date()
231 
232         super().save(**kwargs)
233 
234     @property
235     def access_token(self):
236         """Return access token for album."""
237         return hashlib.sha256(
238             f"{settings.SECRET_KEY}album{self.pk}".encode()
239         ).hexdigest()
240 
241     class Meta:
242         """Meta class for Album."""
243 
244         ordering = ("-date", "title")
245
```
--- 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/website/photos/models.py b/website/photos/models.py
--- a/website/photos/models.py
+++ b/website/photos/models.py
@@ -188,8 +188,8 @@
         # Not prefetched because this should be rare and is a lot of data
         # `exists` is faster in theory, but requires everything to be fetched later anyways
         if self.photo_set.exists():
-            random.seed(self.dirname)
-            cover = random.choice(self.photo_set.all())
+            r = random.Random(self.dirname)
+            cover = r.choice(self.photo_set.all())
         return cover
 
     def __str__(self):
diff --git a/website/thaliawebsite/templatetags/pick_header_image.py b/website/thaliawebsite/templatetags/pick_header_image.py
--- a/website/thaliawebsite/templatetags/pick_header_image.py
+++ b/website/thaliawebsite/templatetags/pick_header_image.py
@@ -3,19 +3,40 @@
 
 from django import template
 from django.contrib.staticfiles.storage import staticfiles_storage
+from django.utils.safestring import mark_safe
 
 register = template.Library()
 
 HEADERS = [
-    "img/headers/banner_default.jpg",
-    "img/headers/banner2.jpg",
-    "img/headers/banner4.jpg",
-    "img/headers/banner5.jpg",
-    "img/headers/banner6.jpg",
+    "img/headers/banner_default",
+    "img/headers/banner_bicycles",
+    "img/headers/banner_huygenshall",
+    "img/headers/banner_huygensfront",
+    "img/headers/banner_huygens",
+    "img/headers/banner_huygenstent",
+    "img/headers/banner_robot",
+    "img/headers/banner_tent",
+]
+
+HEADERS_FUN = [
+    "img/headers/banner_wine",
+    "img/headers/banner_winetasting",
+    "img/headers/banner_christmas",
+    "img/headers/banner_huygenstent",
+    "img/headers/banner_bingo",
+    "img/headers/banner_food",
 ]
 
 
 @register.simple_tag
-def pick_header_image():
+def pick_header_image(type="normal"):
     """Render a random header image."""
-    return staticfiles_storage.url(random.choice(HEADERS))
+    if type == "fun":
+        headers = HEADERS_FUN
+    else:
+        headers = HEADERS
+    header = random.choice(headers)
+    header_2k = staticfiles_storage.url(header + "-2k.webp")
+    header_5k = staticfiles_storage.url(header + "-5k.webp")
+
+    return mark_safe(f"{header_2k}, {header_5k} 3x")

{"golden_diff": "diff --git a/website/photos/models.py b/website/photos/models.py\n--- a/website/photos/models.py\n+++ b/website/photos/models.py\n@@ -188,8 +188,8 @@\n         # Not prefetched because this should be rare and is a lot of data\n         # `exists` is faster in theory, but requires everything to be fetched later anyways\n         if self.photo_set.exists():\n-            random.seed(self.dirname)\n-            cover = random.choice(self.photo_set.all())\n+            r = random.Random(self.dirname)\n+            cover = r.choice(self.photo_set.all())\n         return cover\n \n     def __str__(self):\ndiff --git a/website/thaliawebsite/templatetags/pick_header_image.py b/website/thaliawebsite/templatetags/pick_header_image.py\n--- a/website/thaliawebsite/templatetags/pick_header_image.py\n+++ b/website/thaliawebsite/templatetags/pick_header_image.py\n@@ -3,19 +3,40 @@\n \n from django import template\n from django.contrib.staticfiles.storage import staticfiles_storage\n+from django.utils.safestring import mark_safe\n \n register = template.Library()\n \n HEADERS = [\n-    \"img/headers/banner_default.jpg\",\n-    \"img/headers/banner2.jpg\",\n-    \"img/headers/banner4.jpg\",\n-    \"img/headers/banner5.jpg\",\n-    \"img/headers/banner6.jpg\",\n+    \"img/headers/banner_default\",\n+    \"img/headers/banner_bicycles\",\n+    \"img/headers/banner_huygenshall\",\n+    \"img/headers/banner_huygensfront\",\n+    \"img/headers/banner_huygens\",\n+    \"img/headers/banner_huygenstent\",\n+    \"img/headers/banner_robot\",\n+    \"img/headers/banner_tent\",\n+]\n+\n+HEADERS_FUN = [\n+    \"img/headers/banner_wine\",\n+    \"img/headers/banner_winetasting\",\n+    \"img/headers/banner_christmas\",\n+    \"img/headers/banner_huygenstent\",\n+    \"img/headers/banner_bingo\",\n+    \"img/headers/banner_food\",\n ]\n \n \n @register.simple_tag\n-def pick_header_image():\n+def pick_header_image(type=\"normal\"):\n     \"\"\"Render a random header image.\"\"\"\n-    return staticfiles_storage.url(random.choice(HEADERS))\n+    if type == \"fun\":\n+        headers = HEADERS_FUN\n+    else:\n+        headers = HEADERS\n+    header = random.choice(headers)\n+    header_2k = staticfiles_storage.url(header + \"-2k.webp\")\n+    header_5k = staticfiles_storage.url(header + \"-5k.webp\")\n+\n+    return mark_safe(f\"{header_2k}, {header_5k} 3x\")\n", "issue": "Change the header photos\n### Is your feature request related to a problem? Please describe.\r\nThe header photos are a bit outdated \r\n\r\n### Describe the solution you'd like\r\nChoose some new photos\r\n\r\n### Motivation\r\n<img width=\"1306\" alt=\"image\" src=\"https://user-images.githubusercontent.com/7915741/164710169-e2d4ac27-04f0-48e6-bceb-bc05a1d3c30f.png\">\r\nthese guys are old\r\n<img width=\"1306\" alt=\"image\" src=\"https://user-images.githubusercontent.com/7915741/164710211-ad6fa23f-b021-4dab-9f4f-de45b11c56a2.png\">\r\nthis image is lowres and old\r\n\r\n### Describe alternatives you've considered\r\nNot\r\n\r\n### Additional context\r\nI have access to the RU Brandportal with nice stock images of the university that we can use\n", "before_files": [{"content": "\"\"\"Get a random header image.\"\"\"\nimport random\n\nfrom django import template\nfrom django.contrib.staticfiles.storage import staticfiles_storage\n\nregister = template.Library()\n\nHEADERS = [\n    \"img/headers/banner_default.jpg\",\n    \"img/headers/banner2.jpg\",\n    \"img/headers/banner4.jpg\",\n    \"img/headers/banner5.jpg\",\n    \"img/headers/banner6.jpg\",\n]\n\n\n@register.simple_tag\ndef pick_header_image():\n    \"\"\"Render a random header image.\"\"\"\n    return staticfiles_storage.url(random.choice(HEADERS))\n", "path": "website/thaliawebsite/templatetags/pick_header_image.py"}, {"content": "import hashlib\nimport logging\nimport os\nimport random\nfrom secrets import token_hex\n\nfrom django.conf import settings\nfrom django.core.exceptions import ValidationError\nfrom django.db import models\nfrom django.db.models import Count, IntegerField, Value\nfrom django.db.models.functions import Coalesce\nfrom django.urls import reverse\nfrom django.utils.functional import cached_property\nfrom django.utils.translation import gettext_lazy as _\n\nfrom queryable_properties.managers import QueryablePropertiesManager\nfrom queryable_properties.properties import AnnotationProperty\nfrom thumbnails.fields import ImageField\n\nfrom members.models import Member\n\nCOVER_FILENAME = \"cover.jpg\"\n\n\nlogger = logging.getLogger(__name__)\n\n\ndef photo_uploadto(instance, filename):\n    ext = os.path.splitext(filename)[1]\n    return f\"photos/{instance.album.dirname}/{token_hex(8)}{ext}\"\n\n\nclass DuplicatePhotoException(Exception):\n    \"\"\"Raised when a photo with the same digest already exists in a given album.\"\"\"\n\n\nclass Photo(models.Model):\n    \"\"\"Model for a Photo object.\"\"\"\n\n    objects = QueryablePropertiesManager()\n\n    album = models.ForeignKey(\n        \"Album\", on_delete=models.CASCADE, verbose_name=_(\"album\")\n    )\n\n    file = ImageField(\n        _(\"file\"),\n        upload_to=photo_uploadto,\n        resize_source_to=\"source\",\n    )\n\n    rotation = models.IntegerField(\n        verbose_name=_(\"rotation\"),\n        default=0,\n        choices=((x, x) for x in (0, 90, 180, 270)),\n        help_text=_(\"This does not modify the original image file.\"),\n    )\n\n    _digest = models.CharField(\n        \"digest\",\n        max_length=40,\n        blank=True,\n        editable=False,\n    )\n\n    num_likes = AnnotationProperty(\n        Coalesce(Count(\"likes\"), Value(0), output_field=IntegerField())\n    )\n\n    def __init__(self, *args, **kwargs):\n        \"\"\"Initialize Photo object and set the file if it exists.\"\"\"\n        super().__init__(*args, **kwargs)\n        if self.file:\n            self.original_file = self.file.name\n        else:\n            self.original_file = \"\"\n\n    def __str__(self):\n        \"\"\"Return the filename of a Photo object.\"\"\"\n        return os.path.basename(self.file.name)\n\n    def clean(self):\n        if not self.file._committed:\n            hash_sha1 = hashlib.sha1()\n            for chunk in iter(lambda: self.file.read(4096), b\"\"):\n                hash_sha1.update(chunk)\n            digest = hash_sha1.hexdigest()\n            self._digest = digest\n\n            if (\n                Photo.objects.filter(album=self.album, _digest=digest)\n                .exclude(pk=self.pk)\n                .exists()\n            ):\n                raise ValidationError(\n                    {\"file\": \"This photo already exists in this album.\"}\n                )\n\n        return super().clean()\n\n    def delete(self, using=None, keep_parents=False):\n        removed = super().delete(using, keep_parents)\n        if self.file.name:\n            self.file.delete()\n        return removed\n\n    class Meta:\n        \"\"\"Meta class for Photo.\"\"\"\n\n        # Photos are created in order of their filename.\n        ordering = (\"pk\",)\n\n\nclass Like(models.Model):\n    photo = models.ForeignKey(\n        Photo, null=False, blank=False, related_name=\"likes\", on_delete=models.CASCADE\n    )\n    member = models.ForeignKey(\n        Member, null=True, blank=False, on_delete=models.SET_NULL\n    )\n\n    def __str__(self):\n        return str(self.member) + \" \" + _(\"likes\") + \" \" + str(self.photo)\n\n    class Meta:\n        unique_together = [\"photo\", \"member\"]\n\n\nclass Album(models.Model):\n    \"\"\"Model for Album objects.\"\"\"\n\n    title = models.CharField(\n        _(\"title\"),\n        blank=True,\n        max_length=200,\n        help_text=_(\"Leave empty to take over the title of the event\"),\n    )\n\n    dirname = models.CharField(\n        verbose_name=_(\"directory name\"),\n        max_length=200,\n    )\n\n    date = models.DateField(\n        verbose_name=_(\"date\"),\n        blank=True,\n        help_text=_(\"Leave empty to take over the date of the event\"),\n    )\n\n    slug = models.SlugField(\n        verbose_name=_(\"slug\"),\n        unique=True,\n    )\n\n    hidden = models.BooleanField(verbose_name=_(\"hidden\"), default=False)\n\n    event = models.ForeignKey(\n        \"events.Event\",\n        on_delete=models.SET_NULL,\n        blank=True,\n        null=True,\n    )\n\n    _cover = models.OneToOneField(\n        Photo,\n        on_delete=models.SET_NULL,\n        blank=True,\n        null=True,\n        related_name=\"covered_album\",\n        verbose_name=_(\"cover image\"),\n    )\n\n    shareable = models.BooleanField(verbose_name=_(\"shareable\"), default=False)\n\n    photosdir = \"photos\"\n    photospath = os.path.join(settings.MEDIA_ROOT, photosdir)\n\n    @cached_property\n    def cover(self):\n        \"\"\"Return cover of Album.\n\n        If a cover is not set, return a random photo or None if there are no photos.\n        \"\"\"\n        cover = None\n        if self._cover is not None:\n            return self._cover\n\n        # Not prefetched because this should be rare and is a lot of data\n        # `exists` is faster in theory, but requires everything to be fetched later anyways\n        if self.photo_set.exists():\n            random.seed(self.dirname)\n            cover = random.choice(self.photo_set.all())\n        return cover\n\n    def __str__(self):\n        \"\"\"Get string representation of Album.\"\"\"\n        return f\"{self.date:%Y-%m-%d} {self.title}\"\n\n    def get_absolute_url(self):\n        \"\"\"Get url of Album.\"\"\"\n        return reverse(\"photos:album\", args=[str(self.slug)])\n\n    def clean(self):\n        super().clean()\n        errors = {}\n\n        if not self.title and not self.event:\n            errors.update(\n                {\"title\": _(\"This field is required if there is no event selected.\")}\n            )\n\n        if not self.date and not self.event:\n            errors.update(\n                {\"date\": _(\"This field is required if there is no event selected.\")}\n            )\n\n        if errors:\n            raise ValidationError(errors)\n\n    def save(self, **kwargs):\n        \"\"\"Save album and send appropriate notifications.\"\"\"\n        # dirname is only set for new objects, to avoid ever changing it\n        if self.pk is None:\n            self.dirname = self.slug\n\n        if not self.title and self.event:\n            self.title = self.event.title\n\n        if not self.date:\n            self.date = self.event.start.date()\n\n        super().save(**kwargs)\n\n    @property\n    def access_token(self):\n        \"\"\"Return access token for album.\"\"\"\n        return hashlib.sha256(\n            f\"{settings.SECRET_KEY}album{self.pk}\".encode()\n        ).hexdigest()\n\n    class Meta:\n        \"\"\"Meta class for Album.\"\"\"\n\n        ordering = (\"-date\", \"title\")\n", "path": "website/photos/models.py"}], "after_files": [{"content": "\"\"\"Get a random header image.\"\"\"\nimport random\n\nfrom django import template\nfrom django.contrib.staticfiles.storage import staticfiles_storage\nfrom django.utils.safestring import mark_safe\n\nregister = template.Library()\n\nHEADERS = [\n    \"img/headers/banner_default\",\n    \"img/headers/banner_bicycles\",\n    \"img/headers/banner_huygenshall\",\n    \"img/headers/banner_huygensfront\",\n    \"img/headers/banner_huygens\",\n    \"img/headers/banner_huygenstent\",\n    \"img/headers/banner_robot\",\n    \"img/headers/banner_tent\",\n]\n\nHEADERS_FUN = [\n    \"img/headers/banner_wine\",\n    \"img/headers/banner_winetasting\",\n    \"img/headers/banner_christmas\",\n    \"img/headers/banner_huygenstent\",\n    \"img/headers/banner_bingo\",\n    \"img/headers/banner_food\",\n]\n\n\n@register.simple_tag\ndef pick_header_image(type=\"normal\"):\n    \"\"\"Render a random header image.\"\"\"\n    if type == \"fun\":\n        headers = HEADERS_FUN\n    else:\n        headers = HEADERS\n    header = random.choice(headers)\n    header_2k = staticfiles_storage.url(header + \"-2k.webp\")\n    header_5k = staticfiles_storage.url(header + \"-5k.webp\")\n\n    return mark_safe(f\"{header_2k}, {header_5k} 3x\")\n", "path": "website/thaliawebsite/templatetags/pick_header_image.py"}, {"content": "import hashlib\nimport logging\nimport os\nimport random\nfrom secrets import token_hex\n\nfrom django.conf import settings\nfrom django.core.exceptions import ValidationError\nfrom django.db import models\nfrom django.db.models import Count, IntegerField, Value\nfrom django.db.models.functions import Coalesce\nfrom django.urls import reverse\nfrom django.utils.functional import cached_property\nfrom django.utils.translation import gettext_lazy as _\n\nfrom queryable_properties.managers import QueryablePropertiesManager\nfrom queryable_properties.properties import AnnotationProperty\nfrom thumbnails.fields import ImageField\n\nfrom members.models import Member\n\nCOVER_FILENAME = \"cover.jpg\"\n\n\nlogger = logging.getLogger(__name__)\n\n\ndef photo_uploadto(instance, filename):\n    ext = os.path.splitext(filename)[1]\n    return f\"photos/{instance.album.dirname}/{token_hex(8)}{ext}\"\n\n\nclass DuplicatePhotoException(Exception):\n    \"\"\"Raised when a photo with the same digest already exists in a given album.\"\"\"\n\n\nclass Photo(models.Model):\n    \"\"\"Model for a Photo object.\"\"\"\n\n    objects = QueryablePropertiesManager()\n\n    album = models.ForeignKey(\n        \"Album\", on_delete=models.CASCADE, verbose_name=_(\"album\")\n    )\n\n    file = ImageField(\n        _(\"file\"),\n        upload_to=photo_uploadto,\n        resize_source_to=\"source\",\n    )\n\n    rotation = models.IntegerField(\n        verbose_name=_(\"rotation\"),\n        default=0,\n        choices=((x, x) for x in (0, 90, 180, 270)),\n        help_text=_(\"This does not modify the original image file.\"),\n    )\n\n    _digest = models.CharField(\n        \"digest\",\n        max_length=40,\n        blank=True,\n        editable=False,\n    )\n\n    num_likes = AnnotationProperty(\n        Coalesce(Count(\"likes\"), Value(0), output_field=IntegerField())\n    )\n\n    def __init__(self, *args, **kwargs):\n        \"\"\"Initialize Photo object and set the file if it exists.\"\"\"\n        super().__init__(*args, **kwargs)\n        if self.file:\n            self.original_file = self.file.name\n        else:\n            self.original_file = \"\"\n\n    def __str__(self):\n        \"\"\"Return the filename of a Photo object.\"\"\"\n        return os.path.basename(self.file.name)\n\n    def clean(self):\n        if not self.file._committed:\n            hash_sha1 = hashlib.sha1()\n            for chunk in iter(lambda: self.file.read(4096), b\"\"):\n                hash_sha1.update(chunk)\n            digest = hash_sha1.hexdigest()\n            self._digest = digest\n\n            if (\n                Photo.objects.filter(album=self.album, _digest=digest)\n                .exclude(pk=self.pk)\n                .exists()\n            ):\n                raise ValidationError(\n                    {\"file\": \"This photo already exists in this album.\"}\n                )\n\n        return super().clean()\n\n    def delete(self, using=None, keep_parents=False):\n        removed = super().delete(using, keep_parents)\n        if self.file.name:\n            self.file.delete()\n        return removed\n\n    class Meta:\n        \"\"\"Meta class for Photo.\"\"\"\n\n        # Photos are created in order of their filename.\n        ordering = (\"pk\",)\n\n\nclass Like(models.Model):\n    photo = models.ForeignKey(\n        Photo, null=False, blank=False, related_name=\"likes\", on_delete=models.CASCADE\n    )\n    member = models.ForeignKey(\n        Member, null=True, blank=False, on_delete=models.SET_NULL\n    )\n\n    def __str__(self):\n        return str(self.member) + \" \" + _(\"likes\") + \" \" + str(self.photo)\n\n    class Meta:\n        unique_together = [\"photo\", \"member\"]\n\n\nclass Album(models.Model):\n    \"\"\"Model for Album objects.\"\"\"\n\n    title = models.CharField(\n        _(\"title\"),\n        blank=True,\n        max_length=200,\n        help_text=_(\"Leave empty to take over the title of the event\"),\n    )\n\n    dirname = models.CharField(\n        verbose_name=_(\"directory name\"),\n        max_length=200,\n    )\n\n    date = models.DateField(\n        verbose_name=_(\"date\"),\n        blank=True,\n        help_text=_(\"Leave empty to take over the date of the event\"),\n    )\n\n    slug = models.SlugField(\n        verbose_name=_(\"slug\"),\n        unique=True,\n    )\n\n    hidden = models.BooleanField(verbose_name=_(\"hidden\"), default=False)\n\n    event = models.ForeignKey(\n        \"events.Event\",\n        on_delete=models.SET_NULL,\n        blank=True,\n        null=True,\n    )\n\n    _cover = models.OneToOneField(\n        Photo,\n        on_delete=models.SET_NULL,\n        blank=True,\n        null=True,\n        related_name=\"covered_album\",\n        verbose_name=_(\"cover image\"),\n    )\n\n    shareable = models.BooleanField(verbose_name=_(\"shareable\"), default=False)\n\n    photosdir = \"photos\"\n    photospath = os.path.join(settings.MEDIA_ROOT, photosdir)\n\n    @cached_property\n    def cover(self):\n        \"\"\"Return cover of Album.\n\n        If a cover is not set, return a random photo or None if there are no photos.\n        \"\"\"\n        cover = None\n        if self._cover is not None:\n            return self._cover\n\n        # Not prefetched because this should be rare and is a lot of data\n        # `exists` is faster in theory, but requires everything to be fetched later anyways\n        if self.photo_set.exists():\n            r = random.Random(self.dirname)\n            cover = r.choice(self.photo_set.all())\n        return cover\n\n    def __str__(self):\n        \"\"\"Get string representation of Album.\"\"\"\n        return f\"{self.date:%Y-%m-%d} {self.title}\"\n\n    def get_absolute_url(self):\n        \"\"\"Get url of Album.\"\"\"\n        return reverse(\"photos:album\", args=[str(self.slug)])\n\n    def clean(self):\n        super().clean()\n        errors = {}\n\n        if not self.title and not self.event:\n            errors.update(\n                {\"title\": _(\"This field is required if there is no event selected.\")}\n            )\n\n        if not self.date and not self.event:\n            errors.update(\n                {\"date\": _(\"This field is required if there is no event selected.\")}\n            )\n\n        if errors:\n            raise ValidationError(errors)\n\n    def save(self, **kwargs):\n        \"\"\"Save album and send appropriate notifications.\"\"\"\n        # dirname is only set for new objects, to avoid ever changing it\n        if self.pk is None:\n            self.dirname = self.slug\n\n        if not self.title and self.event:\n            self.title = self.event.title\n\n        if not self.date:\n            self.date = self.event.start.date()\n\n        super().save(**kwargs)\n\n    @property\n    def access_token(self):\n        \"\"\"Return access token for album.\"\"\"\n        return hashlib.sha256(\n            f\"{settings.SECRET_KEY}album{self.pk}\".encode()\n        ).hexdigest()\n\n    class Meta:\n        \"\"\"Meta class for Album.\"\"\"\n\n        ordering = (\"-date\", \"title\")\n", "path": "website/photos/models.py"}]}